Roman Senn | 1 Dec 23:13
Picon

Re: Re: patch: discogs.com API fetcher

> What bugs me specifically is that the blocking
> FileHandler::readXMLFile()-call in the DiscogsFetcher::getTracks()
> method won't work

Won't work how? It should, as far as I know. Is it returning a valid XML
file?
 
Sorry, I wasn't to specific about that one...
Originally it just returned immediately, indicating a failure. It turned out that this was due to my system lacking NSCD (name server caching daemon) on which kdelibs (through glibc) seem to rely. Adding discogs.com and yahoo.com to /etc/hosts made those fetchers work then..
However on another system with a functional NSCD it seemed to hang occasionally when selecting a search result (also with the yahoo fetcher), in turn blocking the whole application. From this we could recover only through a "killall tellico" command.
So I chose to set off another async request through KIO::Job when a search result is clicked. This request will retrieve details about the Discogs-Release (Tracklist, Cover-Image etc.), but as expected they won't be shown unless the search result is clicked a 2nd time. Of course this is because when the async request is completed it won't update any GUI objects. This leads me to the question whether there's a possibility to update them after the async request completed or if I have to reconsider implementing it using the blocking ::readXMLFile() call?

Sounds good. I'd love to include for the upcoming version 1.3 release.

When do you think will tellico-1.3 be released approximately? I'll try to keep up by providing a working and tested discogs-fetcher by then..

> The API key in the patch is hard-coded and the one we're using here
> currently. You can use it for testing, but please don't abuse it!

Is the intent to ask each user to get their own?

Yes, the API key is required for their XML-based interface (See http://www.discogs.com/help/api ). You'll receive a key permitting 50'000 requests per day after (free) subscription.
Even before the appearance of the Discogs XML API it seems to have been common amongst vinyl collectors/traders to have a Discogs account
.
Here we're also considering writing a fetcher for "Discogs Marketplace", because it inclues pricing and rating information. This wouldn't be possible through the XML API and as such we'd have to parse the HTML pages (using Regex) which won't require any key.

Btw. The second revision of the patch will arrive shortly, now providing a configuration dialog with the API key setting.
_______________________________________________
tellico-users mailing list
tellico-users@...
http://forge.novell.com/mailman/listinfo/tellico-users
Roman Senn | 1 Dec 23:19
Picon

Discogs.com API patch revision 2

Discogs.com fetcher revision 2:

- Removed bogus #include <boost/lexical_cast.hpp> which i used for some debugging tweaks..
- Added configuration dialog for setting the API key
- Cover images
- Artist info within track list
- Duration within track list
- Cleanup, removal of std::cerr debugging messages
diff -ruN tellico-branch/src/fetch/discogsfetcher.cpp tellico-1.3.x/src/fetch/discogsfetcher.cpp
--- tellico-branch/src/fetch/discogsfetcher.cpp	1970-01-01 01:00:00.000000000 +0100
+++ tellico-1.3.x/src/fetch/discogsfetcher.cpp	2007-12-01 22:34:55.000000000 +0100
@@ -0,0 +1,722 @@
+/***************************************************************************
+    copyright            : (C) 2007 by Robby Stephenson
+    email                : robby <at> periapsis.org
+  
+    discogs fetcher contributed by Roman L. Senn <roman.l.senn <at> gmail.com>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   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 <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kio/job.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qfile.h>
+#include <qwhatsthis.h>
+
+#include <iostream>
+
+namespace {
+  static const char* DISCOGS_API_URL = "http://www.discogs.com";
+  static const int DISCOGS_MAX_RETURNS_TOTAL = 20;
+//  static const char* DISCOGS_API_KEY = "aa507659db";
+//  static const char* DISCOGS_APP_ID = "tellico-robby";
+}
+
+using Tellico::Fetch::DiscogsFetcher;
+
+DiscogsFetcher::DiscogsFetcher(QObject* parent_, const char* name_)
+    : Fetcher(parent_, name_), m_xsltHandler(0),
+      m_limit(DISCOGS_MAX_RETURNS_TOTAL), m_job(0), m_started(false) {
+}
+
+DiscogsFetcher::~DiscogsFetcher() {
+  delete m_xsltHandler;
+  m_xsltHandler = 0;
+}
+
+QString DiscogsFetcher::defaultName() {
+  return i18n("Discogs Audio Search");
+}
+
+QString 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_) {
+  QString 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 QString& 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(QString::fromLatin1(DISCOGS_API_URL));
+  u.setFileName(QString::fromLatin1("search"));
+  u.addQueryItem(QString::fromLatin1("api_key"), m_apiKey);
+  u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("release"));
+  u.addQueryItem(QString::fromLatin1("f"), QString::fromLatin1("xml"));
+  u.addQueryItem(QString::fromLatin1("q"), m_value);
+  
+  if(!canFetch(Kernel::self()->collectionType())) {
+    message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+    stop();
+    return;
+  }
+
+/*  switch(m_key) {
+    case Title:
+      u.addQueryItem(QString::fromLatin1("album"), m_value);
+      break;
+
+    case Person:
+      u.addQueryItem(QString::fromLatin1("artist"), m_value);
+      break;
+
+    // raw is used for the entry updates
+    case Raw:
+//      u.removeQueryItem(QString::fromLatin1("type"));
+//      u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("phrase"));
+      u.setQuery(u.query() + '&' + m_value);
+      break;
+
+    default:
+      kdWarning() << "DiscogsFetcher::search() - key not recognized: " << m_key << endl;
+      stop();
+      return;
+  }*/
+
+  m_job = KIO::get(u, false, false);
+  connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+          SLOT(slotData(KIO::Job*, const QByteArray&)));
+  connect(m_job, SIGNAL(result(KIO::Job*)),
+          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 QByteArray& data_) {
+  QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+  stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void DiscogsFetcher::slotComplete(KIO::Job* job_) {
+//  myDebug() << "DiscogsFetcher::slotComplete()" << endl;
+//  
+  QDomDocument dom;
+  
+  if(job_->error()) {
+    job_->showErrorDialog(Kernel::self()->widget());
+    stop();
+    return;
+  }
+    
+  if(m_data.isEmpty()) {
+    myDebug() << "DiscogsFetcher::slotComplete() - no data" << endl;
+    stop();
+    return;
+  }
+    
+#if 1
+  kdWarning() << "Remove debug from discogsfetcher.cpp" << endl;
+  QFile f(QString::fromLatin1(m_jobentries.contains(job_)?"/tmp/release.xml":"/tmp/result.xml"));
+  if(f.open(IO_WriteOnly)) {
+    QTextStream t(&f);
+    t.setEncoding(QTextStream::UnicodeUTF8);
+    t << QCString(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;
+    }
+  }
+
+  /* Process search results */
+  if(job_ == m_job)
+  {
+    // since the fetch is done, don't worry about holding the job pointer
+    m_job = 0;
+    
+    
+    if(m_total == -1) {
+      if(!dom.setContent(m_data, false)) {
+        kdWarning() << "DiscogsFetcher::slotComplete() - server did not return valid XML." << endl;
+        return;
+      }
+      // total is top level element, with attribute totalResultsAvailable
+      QDomElement e = dom.documentElement();
+      if(!e.isNull()) {
+        m_total = e.attribute(QString::fromLatin1("totalResultsAvailable")).toInt();
+      }
+    }
+    
+    // assume discogs is always utf-8
+    QString str = m_xsltHandler->applyStylesheet(QString::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;
+      }
+      QString desc 
+        = entry->field(QString::fromLatin1("artist"))
+        + QChar('/')
+        + entry->field(QString::fromLatin1("album"))
+        + QChar('/')
+        + entry->field(QString::fromLatin1("label"));
+
+      SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("discogs")));
+      m_entries.insert(r->uid, Data::EntryPtr(entry));
+      emit signalResultFound(r);
+    }
+    m_start = m_entries.count() + 1;
+    m_hasMoreResults = m_start <= m_total;
+  }
+  
+  /* Process Discogs Release details... */
+  if(m_jobentries.contains(job_))
+  {
+    Data::EntryPtr entry = m_jobentries[job_];
+    
+    if(!dom.setContent(m_data, false)) {
+      kdWarning() << "DiscogsFetcher::slotComplete() - server did not return valid XML." << endl;
+      return;
+    }
+    
+    QDomElement doc = dom.documentElement();
+    QDomElement rel = doc.firstChild().toElement();
+    QString year;
+    
+    for(uint k = 0; k < rel.childNodes().length(); ++k)
+    {
+      QDomElement child = rel.childNodes().item(k).toElement();
+      
+      if(child.nodeName() == "released")
+      {
+        year = child.text();
+        
+        if(year[4] == '-')
+          year = year.left(4);
+        
+        entry->setField(QString::fromLatin1("year"), year);
+        continue;
+
+      } else if(child.nodeName() == "country") {
+        
+        entry->setField(QString::fromLatin1("country"), child.text());
+
+      } else if(child.nodeName() == "notes") {
+        
+        entry->setField(QString::fromLatin1("notes"), child.text());
+
+      } else if(child.nodeName() == "images") {
+      
+        doCover(entry, child);
+        
+      }
+      
+//      std::cerr << "elm: " << child.nodeName() << std::endl;
+    }
+    
+    
+    // Get genre
+    QDomNodeList nodes = dom.elementsByTagName(QString::fromLatin1("genre"));
+    QString genre;
+    
+    for(uint i = 0; i < nodes.count(); ++i) {
+      if(genre.length() != 0)
+        genre += ", ";
+
+      genre += nodes.item(i).toElement().text();
+    }
+
+    if(genre.length())
+      entry->setField(QString::fromLatin1("genre"), genre);
+
+    // Get style
+    nodes = dom.elementsByTagName(QString::fromLatin1("style"));
+    QString style;
+    
+    for(uint i = 0; i < nodes.count(); ++i) {
+      if(style.length() != 0)
+        style += ", ";
+
+      style += nodes.item(i).toElement().text();
+    }
+
+    if(style.length())
+    {
+      if(style.length())
+        entry->setField(QString::fromLatin1("style"), style);
+      
+      genre += QString(" (") + style + ")";
+    }
+
+    // Get label
+    nodes = dom.elementsByTagName(QString::fromLatin1("label"));
+    QString label;
+
+    for(uint i = 0; i < nodes.count(); ++i) {
+      if(label.length() != 0)
+        label += ", ";
+
+      label += nodes.item(i).toElement().attribute("name");
+    }
+    
+    if(label.length()) {
+      entry->setField(QString::fromLatin1("label"), label);
+    }
+
+    // Get format
+    nodes = dom.elementsByTagName(QString::fromLatin1("format"));
+    QString format;
+    QString medium;
+    
+    for(uint i = 0; i < nodes.count(); ++i) {
+      if(format.length() != 0)
+        format += ", ";
+
+      format += nodes.item(i).toElement().attribute("name");
+
+      if(!medium.length())
+        medium = format;
+      
+      QDomNodeList dnodes = nodes.item(i).toElement().elementsByTagName("description");
+
+      for(uint j = 0; j < dnodes.count(); ++j) {
+        format += ", ";
+        format += dnodes.item(j).toElement().text();
+      }
+
+      int q = nodes.item(i).toElement().attribute("qty").toInt();
+
+      if(q > 1) {
+        format += " (";
+        format += QString::number(q) + " discs)";
+      }
+    }
+
+    if(medium.length()) {
+      if(medium.left(2) == QString::fromLatin1("CD")) {
+        medium = "Compact Disc";
+      }
+      
+      entry->setField(QString::fromLatin1("medium"), medium);
+    }
+
+    // get the tracks
+    QString track = QString::fromLatin1("track");
+    nodes = dom.elementsByTagName(track);
+    int n = 1;
+    
+    for(uint i = 0; i < nodes.count(); ++i) {
+      QDomElement e = nodes.item(i).toElement();
+      if(e.isNull()) {
+        continue;
+      }
+      
+      QString pos = e.namedItem(QString::fromLatin1("position")).toElement().text();
+      QString title = e.namedItem(QString::fromLatin1("title")).toElement().text();
+      QString dur = e.namedItem(QString::fromLatin1("duration")).toElement().text();
+      
+      bool ok;
+      int trackNum = Tellico::toUInt(pos, &ok);
+      if(!ok) trackNum = Tellico::toUInt(pos.mid(1), &ok);
+      if(!ok) trackNum = Tellico::toUInt(pos.mid(2), &ok);
+      
+      // trackNum might be 0
+      if(title.isEmpty() || !ok || trackNum < 1) {
+        continue;
+      }
+      
+      if(trackNum != n || QString::number(trackNum) != pos)
+        title = pos + " - " + title;
+      
+      QDomNodeList artists = e.elementsByTagName(QString::fromLatin1("artist"));
+      QString addArtist;
+      
+      for(uint j = 0; j < artists.count(); ++j) {
+        e = artists.item(j).toElement();
+        QString name = e.namedItem(QString::fromLatin1("name")).toElement().text();
+        QString role = e.namedItem(QString::fromLatin1("role")).toElement().text();
+        
+        if(name == entry->field(QString::fromLatin1("artist"))) {
+          name = "";
+        }
+        if(name.right(5) == ", The") {
+          name = QString("The ") + name.left(name.length() - 5);
+        }
+        if(addArtist.length() && (name.length() || role.length())) {
+          addArtist += ", ";
+        }
+        if(role == QString::fromLatin1("Featuring")) {
+          addArtist += "feat. " + name;
+        } else if(role.length()) {
+          if(name.length() && role != QString::fromLatin1("Remix")) {
+            addArtist += role + " by " + name;
+          } else {
+            if(name.left(4) == "The ") {
+              name = name.mid(4);
+            }
+            addArtist += name + " Mix";
+          }
+        } else {
+          addArtist += name;
+        }
+      }
+
+      title += QString::fromLatin1("::");
+      
+      if(addArtist.length() && !title.contains(addArtist)) {
+        title += /*QString(" (") +*/ addArtist /*+ ")"*/;
+      }
+      if(!dur.isEmpty()) {
+        title += QString::fromLatin1("::") + dur;
+      }
+
+      entry->setField(track, insertValue(entry->field(track), title, trackNum));
+      ++n;
+    }
+    
+    m_jobentries.erase(job_);
+  }
+  
+  stop(); // required
+}
+
+void DiscogsFetcher::getTracks(Data::EntryPtr entry_) {
+  // get album id
+  if(!entry_ || entry_->field(QString::fromLatin1("discogs")).isEmpty()) {
+    return;
+  }
+
+  /* For the detailed track listing and more specific infos about the record
+   * we are requesting the Discogs Release page by another async request. */
+  const QString releaseURL = entry_->field(QString::fromLatin1("discogs"));
+  KURL u(releaseURL);
+
+  u.addQueryItem(QString::fromLatin1("key"), m_apiKey);
+  u.addQueryItem(QString::fromLatin1("f"),  QString::fromLatin1("xml"));
+
+  KIO::Job *job;
+  m_jobentries[(job = KIO::get(u, false, false))] = entry_;
+  connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+          SLOT(slotData(KIO::Job*, const QByteArray&)));
+  connect(job, SIGNAL(result(KIO::Job*)),
+          SLOT(slotComplete(KIO::Job*)));
+
+  m_started = true;
+  
+  return;
+
+  QDomDocument dom = FileHandler::readXMLFile(u, false, false);
+  if(dom.isNull()) {
+//    std::cerr << "null dom" << std::endl;
+    myDebug() << "DiscogsFetcher::getTracks() - null dom returned" << endl;
+    return;
+  }
+  
+//  myDebug() << "DiscogsFetcher::getTracks() - url: " << u.url() << endl;
+
+#if 0
+  kdWarning() << "Remove debug from discogsfetcher.cpp" << endl;
+  QFile f(QString::fromLatin1("/tmp/test.xml"));
+  if(f.open(IO_WriteOnly)) {
+    QTextStream t(&f);
+    t.setEncoding(QTextStream::UnicodeUTF8);
+    t << dom.toString();
+  }
+  f.close();
+#endif
+
+  const QString track = QString::fromLatin1("track");
+
+  QDomNodeList nodes = dom.documentElement().childNodes();
+  for(uint i = 0; i < nodes.count(); ++i) {
+    QDomElement e = nodes.item(i).toElement();
+    if(e.isNull()) {
+      continue;
+    }
+    QString t = e.namedItem(QString::fromLatin1("Title")).toElement().text();
+    QString n = e.namedItem(QString::fromLatin1("Track")).toElement().text();
+    bool ok;
+    int trackNum = Tellico::toUInt(n, &ok);
+    // trackNum might be 0
+    if(t.isEmpty() || !ok || trackNum < 1) {
+      continue;
+    }
+    QString a = e.namedItem(QString::fromLatin1("Artist")).toElement().text();
+    QString l = e.namedItem(QString::fromLatin1("Length")).toElement().text();
+
+    int len = Tellico::toUInt(l, &ok);
+    QString value = t + "::" + a;
+    if(ok && len > 0) {
+      value += + "::" + Tellico::minutes(len);
+    }
+    entry_->setField(track, insertValue(entry_->field(track), value, trackNum));
+  }
+}
+
+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;
+  }
+
+  KURL imageURL = entry->field(QString::fromLatin1("image"));
+  if(!imageURL.isEmpty()) {
+    QString id = ImageFactory::addImage(imageURL, true);
+    if(id.isEmpty()) {
+    // rich text causes layout issues
+//      emit signalStatus(i18n("<qt>The cover image for <i>%1</i> could not be loaded.</qt>").arg(
+//                              entry->field(QString::fromLatin1("title"))));
+      message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
+    } else {
+      entry->setField(QString::fromLatin1("cover"), id);
+    }
+  }
+
+  getTracks(entry);
+
+  // don't want to show image urls in the fetch dialog
+  entry->setField(QString::fromLatin1("image"),  QString::null);
+  return entry;
+}
+
+void DiscogsFetcher::initXSLTHandler() {
+  QString xsltfile = locate("appdata", QString::fromLatin1("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;
+  }
+}
+
+// not zero-based
+QString DiscogsFetcher::insertValue(const QString& str_, const QString& value_, uint pos_) {
+  QStringList list = Data::Field::split(str_, true);
+  for(uint i = list.count(); i < pos_; ++i) {
+    list += QString::null;
+  }
+  bool write = true;
+  if(!list[pos_-1].isNull()) {
+    // for some reason, some songs are repeated from discogs, with 0 length, don't overwrite that
+    if(value_.contains(QString::fromLatin1("::")) < 2) { // means no length value
+      write = false;
+    }
+  }
+  if(!value_.isEmpty() && write) {
+    list[pos_-1] = value_;
+  }
+  return list.join(QString::fromLatin1("; "));
+}
+
+/* The Discogs Release page (in XML format) can contain several (cover-)images
+ * in the form:
+ * 
+ * <images>
+ *   <image type="[primary/secondary]" width="[width]" height="[height]" uri="[uri]">
+ *   ...
+ * </images>
+ * 
+ * A detailed description of these records can be found at
+ * http://www.discogs.com/help/api in the section "About Images".
+ */
+void DiscogsFetcher::doCover(Data::EntryPtr entry_, const QDomElement& elm_) {
+  const QString cover = QString::fromLatin1("cover");
+  const QDomNodeList images = elm_.elementsByTagName(QString::fromLatin1("image"));
+  long maxSize = 0.0;
+  bool havePrimary = false;
+  KURL u;
+  
+  /* We're after the biggest primary image... */
+  for(uint i = 0; i < images.count(); ++i) {
+    QDomElement imgElem = images.item(i).toElement();
+    QString imageURL = imgElem.attribute(QString::fromLatin1("uri"));
+    long size = imgElem.attribute(QString::fromLatin1("width")).toDouble() *
+                imgElem.attribute(QString::fromLatin1("height")).toDouble();
+    bool pri = (imgElem.attribute(QString::fromLatin1("type")) == QString::fromLatin1("primary"));
+    if(!imageURL.isEmpty() && size >= maxSize && pri >= havePrimary) {
+      u = imageURL;
+      maxSize = size;
+      havePrimary = pri;
+    }
+  }
+  
+  QString id = ImageFactory::addImage(u, true);
+  if(!id.isEmpty()) {
+//    std::cerr << "Cover image: " << u.url() << std::endl;
+    entry_->setField(cover, id);
+    entry_->setField(QString::fromLatin1("image"), u.url());
+  }
+} 
+
+void DiscogsFetcher::updateEntry(Data::EntryPtr entry_) {
+//  myDebug() << "DiscogsFetcher::updateEntry()" << endl;
+  // limit to top 5 results
+  m_limit = 5;
+
+  QString value;
+  QString title = entry_->field(QString::fromLatin1("title"));
+  if(!title.isEmpty()) {
+    value += /*QString::fromLatin1("album=") +*/ title;
+  }
+  QString artist = entry_->field(QString::fromLatin1("artist"));
+  if(!artist.isEmpty()) {
+    if(!value.isEmpty()) {
+      value += ' ';
+    }
+    value += /*QString::fromLatin1("artist=") +*/ artist;
+  }
+  if(!value.isEmpty()) {
+    search(Fetch::Raw, value);
+    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(QWidget* parent_) const {
+  return new DiscogsFetcher::ConfigWidget(parent_, this);
+}
+
+DiscogsFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const DiscogsFetcher* fetcher_)
+    : Fetch::ConfigWidget(parent_) {
+  QGridLayout* l = new QGridLayout(optionsWidget(), 2, 2);      
+  l->setSpacing(4);
+  l->setColStretch(1, 10);
+   
+  int row = -1;
+  QLabel* label = new QLabel(i18n("API &key: "), optionsWidget());
+  l->addWidget(label, ++row, 0);
+      
+  m_apiKeyEdit = new KLineEdit(optionsWidget());
+  connect(m_apiKeyEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+  l->addWidget(m_apiKeyEdit, row, 1);
+  QString 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).");
+  QWhatsThis::add(label, w);
+  QWhatsThis::add(m_apiKeyEdit, w);
+  label->setBuddy(m_apiKeyEdit);
+
+  m_fetchImageCheck = new QCheckBox(i18n("Download cover &image"), optionsWidget());
+  connect(m_fetchImageCheck, SIGNAL(clicked()), 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.");
+  QWhatsThis::add(m_fetchImageCheck, w);
+            
+  l->setRowStretch(++row, 10);
+      
+  // now add additional fields widget
+  addFieldsWidget(DiscogsFetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+      
+  if(fetcher_) {
+    m_apiKeyEdit->setText(fetcher_->m_apiKey);
+    m_fetchImageCheck->setChecked(fetcher_->m_fetchImages);
+  } else {
+    m_apiKeyEdit->setText(QString::fromLatin1(""));
+    m_fetchImageCheck->setChecked(true);
+  }
+}
+
+void DiscogsFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+  QString apiKey = m_apiKeyEdit->text().stripWhiteSpace();
+  if(!apiKey.isEmpty()) {
+    config_.writeEntry("API key", apiKey);
+  }
+  config_.writeEntry("Fetch Images", m_fetchImageCheck->isChecked());
+  
+  saveFieldsConfig(config_);
+  slotSetModified(false);
+}
+  
+QString DiscogsFetcher::ConfigWidget::preferredName() const {
+  return DiscogsFetcher::defaultName();
+}
+
+Tellico::StringMap DiscogsFetcher::customFields() {
+  StringMap map;
+  map[QString::fromLatin1("discogs")]             = i18n("Discogs Link");
+  return map;
+}
+
+#include "discogsfetcher.moc"
diff -ruN tellico-branch/src/fetch/discogsfetcher.h tellico-1.3.x/src/fetch/discogsfetcher.h
--- tellico-branch/src/fetch/discogsfetcher.h	1970-01-01 01:00:00.000000000 +0100
+++ tellico-1.3.x/src/fetch/discogsfetcher.h	2007-12-01 03:50:32.000000000 +0100
@@ -0,0 +1,123 @@
+/***************************************************************************
+    copyright            : (C) 2006 by Robby Stephenson
+    email                : robby <at> 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;                            *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef DISCOGSFETCHER_H
+#define DISCOGSFETCHER_H
+
+namespace Tellico {
+  class XSLTHandler;
+}
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+#include <klineedit.h>
+
+#include <qdom.h>
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace KIO {
+  class Job;
+}
+
+namespace Tellico {
+  namespace Fetch {
+
+/**
+ * A fetcher for Amazon.com.
+ *
+ * @author Robby Stephenson
+ */
+class DiscogsFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+  /**
+   */
+  DiscogsFetcher(QObject* parent, const char* name = 0);
+  /**
+   */
+  virtual ~DiscogsFetcher();
+
+  /**
+   */
+  virtual QString source() const;
+  virtual bool isSearching() const { return m_started; }
+  virtual void search(FetchKey key, const QString& value);
+  virtual void continueSearch();
+  // amazon can search title or person
+  virtual bool canSearch(FetchKey k) const { return k == Title || k == Person; }
+  virtual void stop();
+  virtual Data::EntryPtr fetchEntry(uint uid);
+  virtual Type type() const { return Discogs; }
+  virtual bool canFetch(int type) const;
+  virtual void readConfigHook(const KConfigGroup& config);
+
+  virtual void updateEntry(Data::EntryPtr entry);
+
+  /**
+   * Returns a widget for modifying the fetcher's config.
+   */
+  virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+  static StringMap customFields();
+  
+  class ConfigWidget : public Fetch::ConfigWidget {
+  public:
+    ConfigWidget(QWidget* parent_, const DiscogsFetcher* fetcher = 0);
+    virtual void saveConfig(KConfigGroup&);
+    virtual QString preferredName() const;
+  private:
+    KLineEdit *m_apiKeyEdit;
+    QCheckBox* m_fetchImageCheck;    
+  };
+  friend class ConfigWidget;
+
+  static QString defaultName();
+
+private slots:
+  void slotData(KIO::Job* job, const QByteArray& data);
+  void slotComplete(KIO::Job* job);
+
+private:
+  void initXSLTHandler();
+  void doSearch();
+  void getTracks(Data::EntryPtr entry);
+  QString insertValue(const QString& str, const QString& value, uint pos);
+  void doCover(Data::EntryPtr entry, const QDomElement &elm);
+
+  XSLTHandler* m_xsltHandler;
+  int m_limit;
+  int m_start;
+  int m_total;
+
+  void startQuery(Data::EntryPtr entry);
+  
+  QByteArray m_data;
+  QMap<int, Data::EntryPtr> m_entries; // they get modified after collection is created, so can't be const
+  QGuardedPtr<KIO::Job> m_job;
+  QMap< KIO::Job *, Data::EntryPtr > m_jobentries;
+
+  FetchKey m_key;
+  QString m_value;
+  bool m_started;
+  
+  bool m_fetchImages;
+  QString m_apiKey;
+  QStringList m_fields;
+};
+
+  } // end namespace
+} // end namespace
+#endif
diff -ruN tellico-branch/src/fetch/fetch.h tellico-1.3.x/src/fetch/fetch.h
--- tellico-branch/src/fetch/fetch.h	2007-11-18 08:28:14.000000000 +0100
+++ tellico-1.3.x/src/fetch/fetch.h	2007-11-15 20:55:05.000000000 +0100
@@ -52,7 +52,8 @@
   CrossRef,
   Citebase,
   Arxiv,
-  Bibsonomy
+  Bibsonomy,
+  Discogs
 };

   }
diff -ruN tellico-branch/src/fetch/fetchmanager.cpp tellico-1.3.x/src/fetch/fetchmanager.cpp
--- tellico-branch/src/fetch/fetchmanager.cpp	2007-12-01 02:29:48.000000000 +0100
+++ tellico-1.3.x/src/fetch/fetchmanager.cpp	2007-12-01 22:27:10.000000000 +0100
@@ -34,6 +34,7 @@
 #include "entrezfetcher.h"
 #include "execexternalfetcher.h"
 #include "yahoofetcher.h"
+#include "discogsfetcher.h"
 #include "animenfofetcher.h"
 #include "ibsfetcher.h"
 #include "isbndbfetcher.h"
@@ -284,6 +285,10 @@
       f = new YahooFetcher(this);
       break;

+    case Discogs:
+      f = new DiscogsFetcher(this);
+      break;
+
     case AnimeNfo:
       f = new AnimeNfoFetcher(this);
       break;
@@ -338,6 +343,7 @@
   vec.append(SRUFetcher::libraryOfCongress(this));
   vec.append(new ISBNdbFetcher(this));
   vec.append(new YahooFetcher(this));
+  vec.append(new DiscogsFetcher(this));
   vec.append(new AnimeNfoFetcher(this));
   vec.append(new ArxivFetcher(this));
 // only add IBS if user includes italian
@@ -410,6 +416,7 @@
   list.append(TypePair(EntrezFetcher::defaultName(),       Entrez));
   list.append(TypePair(ExecExternalFetcher::defaultName(), ExecExternal));
   list.append(TypePair(YahooFetcher::defaultName(),        Yahoo));
+  list.append(TypePair(DiscogsFetcher::defaultName(),      Discogs));
   list.append(TypePair(AnimeNfoFetcher::defaultName(),     AnimeNfo));
   list.append(TypePair(IBSFetcher::defaultName(),          IBS));
   list.append(TypePair(ISBNdbFetcher::defaultName(),       ISBNdb));
@@ -494,6 +501,9 @@
     case Yahoo:
       w = new YahooFetcher::ConfigWidget(parent_);
       break;
+    case Discogs:
+      w = new DiscogsFetcher::ConfigWidget(parent_);
+      break;
     case AnimeNfo:
       w = new AnimeNfoFetcher::ConfigWidget(parent_);
       break;
@@ -540,6 +550,7 @@
     case Entrez: return EntrezFetcher::defaultName();
     case ExecExternal: return ExecExternalFetcher::defaultName();
     case Yahoo: return YahooFetcher::defaultName();
+    case Discogs: return DiscogsFetcher::defaultName();
     case AnimeNfo: return AnimeNfoFetcher::defaultName();
     case IBS: return IBSFetcher::defaultName();
     case ISBNdb: return ISBNdbFetcher::defaultName();
@@ -609,6 +620,8 @@
       name = QString::fromLatin1("exec"); break;
     case Yahoo:
       name = favIcon("http://yahoo.com"); break;
+    case Discogs:
+      name = favIcon("http://discogs.com"); break;
     case AnimeNfo:
       name = favIcon("http://animenfo.com"); break;
     case IBS:
diff -ruN tellico-branch/src/fetch/Makefile.am tellico-1.3.x/src/fetch/Makefile.am
--- tellico-branch/src/fetch/Makefile.am	2007-11-18 08:28:14.000000000 +0100
+++ tellico-1.3.x/src/fetch/Makefile.am	2007-11-15 19:57:34.000000000 +0100
@@ -7,7 +7,7 @@

 libfetch_a_SOURCES = amazonfetcher.cpp animenfofetcher.cpp arxivfetcher.cpp \
 	bibsonomyfetcher.cpp citebasefetcher.cpp configwidget.cpp crossreffetcher.cpp \
-	entrezfetcher.cpp execexternalfetcher.cpp fetcher.cpp fetchmanager.cpp \
+	discogsfetcher.cpp entrezfetcher.cpp execexternalfetcher.cpp fetcher.cpp fetchmanager.cpp \
 	gcstarpluginfetcher.cpp ibsfetcher.cpp imdbfetcher.cpp isbndbfetcher.cpp messagehandler.cpp \
 	srufetcher.cpp yahoofetcher.cpp z3950connection.cpp z3950fetcher.cpp

@@ -23,7 +23,7 @@
 fetcher.h fetcher.cpp fetchmanager.h fetchmanager.cpp \
 amazonfetcher.h amazonfetcher.cpp z3950fetcher.h z3950fetcher.cpp \
 imdbfetcher.h imdbfetcher.cpp fetch.h configwidget.h configwidget.cpp \
-entrezfetcher.h entrezfetcher.cpp \
+discogsfetcher.h entrezfetcher.h entrezfetcher.cpp \
 execexternalfetcher.h execexternalfetcher.cpp \
 messagehandler.h messagehandler.cpp \
 z3950connection.h z3950connection.cpp \
diff -ruN tellico-branch/xslt/discogs2tellico.xsl tellico-1.3.x/xslt/discogs2tellico.xsl
--- tellico-branch/xslt/discogs2tellico.xsl	1970-01-01 01:00:00.000000000 +0100
+++ tellico-1.3.x/xslt/discogs2tellico.xsl	2007-12-01 22:29:50.000000000 +0100
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns="http://periapsis.org/tellico/"
+                xmlns:exsl="http://exslt.org/common"
+                extension-element-prefixes="exsl"
+                version="1.0">
+
+<!--
+   ===================================================================
+   Tellico XSLT file - used for importing Discogs release search data.
+
+   Copyright (C) 2004-2006 Robby Stephenson - robby <at> periapsis.org
+
+   This XSLT stylesheet is designed to be used with the 'Tellico'
+   application, which can be found at http://www.periapsis.org/tellico/
+
+   ===================================================================
+-->
+
+<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"
+            doctype-public="-//Robby Stephenson/DTD Tellico V10.0//EN"
+            doctype-system="http://periapsis.org/tellico/dtd/v10/tellico.dtd"/>
+
+<xsl:template match="/">
+ <tellico syntaxVersion="10">
+  <collection title="Discogs API" type="4"> <!-- 4 is music -->
+   <fields>
+    <field name="_default"/>
+    <!-- the importer will actually download the image and ignore this field -->
+    <field flags="0" title="Discogs Release" category="General" format="4" type="7" name="discogs"/>
+    <field flags="0" title="Image" category="Images" format="4" type="7" name="image"/>
+   </fields>
+   <xsl:for-each select="resp/searchresults/result[@type='release']">
+    <xsl:apply-templates select="."/>
+   </xsl:for-each>
+  </collection>
+ </tellico>
+</xsl:template>
+
+<xsl:template match="resp/searchresults/result">
+ <entry>
+
+  <title>
+   <xsl:value-of select="title"/>
+  </title>
+
+  <uri>
+   <xsl:value-of select="uri"/>
+  </uri>
+
+  <artists>
+    <artist>
+      <xsl:value-of select="substring-before(title,' - ')"/>
+    </artist>
+  </artists>
+
+  <album>
+   <xsl:value-of select="substring-after(title,' - ')"/>
+  </album>
+
+  <labels>
+    <label>
+      <xsl:value-of select="substring-before(substring-after(normalize-space(summary),'Label:
'),' Catalog#:')"/>
+    </label>
+  </labels>
+ 
+  <discogs>
+    <xsl:value-of select="uri"/>
+  </discogs>
+
+ </entry>
+
+</xsl:template>
+
+<xsl:template name="year">
+ <xsl:param name="value"/>
+ <!-- assume that discogs always puts the year first -->
+ <xsl:value-of select="substring($value, 0, 5)"/>
+</xsl:template>
+
+</xsl:stylesheet>
diff -ruN tellico-branch/xslt/Makefile.am tellico-1.3.x/xslt/Makefile.am
--- tellico-branch/xslt/Makefile.am	2007-11-18 08:28:23.000000000 +0100
+++ tellico-1.3.x/xslt/Makefile.am	2007-11-15 22:47:46.000000000 +0100
@@ -4,7 +4,7 @@
              vhs-logo.png tellico-common.xsl mods2tellico.xsl \
              amazon2tellico.xsl MARC21slim2MODS3.xsl MARC21slimUtils.xsl \
              pubmed2tellico.xsl tellico2onix.xsl UNIMARC2MODS3.xsl \
-             tellico2html.js yahoo2tellico.xsl isbndb2tellico.xsl \
+             tellico2html.js yahoo2tellico.xsl discogs2tellico.xsl isbndb2tellico.xsl \
              bluray-logo.png hddvd-logo.png gcstar2tellico.xsl \
              xmp2tellico.xsl crossref2tellico.xsl arxiv2tellico.xsl \
              referencer2tellico.xsl welcome.html
@@ -15,7 +15,7 @@
         dvd-logo.png record-logo.png vhs-logo.png tellico-common.xsl \
         mods2tellico.xsl amazon2tellico.xsl MARC21slim2MODS3.xsl \
         MARC21slimUtils.xsl pubmed2tellico.xsl tellico2onix.xsl \
-        UNIMARC2MODS3.xsl tellico2html.js yahoo2tellico.xsl \
+        UNIMARC2MODS3.xsl tellico2html.js yahoo2tellico.xsl discogs2tellico.xsl \
         isbndb2tellico.xsl bluray-logo.png hddvd-logo.png gcstar2tellico.xsl \
         xmp2tellico.xsl crossref2tellico.xsl arxiv2tellico.xsl \
         referencer2tellico.xsl welcome.html
_______________________________________________
tellico-users mailing list
tellico-users@...
http://forge.novell.com/mailman/listinfo/tellico-users
Robby Stephenson | 2 Dec 22:52
Gravatar

Re: patch: discogs.com API fetcher

On Saturday 01 December 2007, Roman Senn wrote:
> Originally it just returned immediately, indicating a failure. It turned
> out that this was due to my system lacking NSCD (name server caching
> daemon) on which kdelibs (through glibc) seem to rely. Adding discogs.com
> and yahoo.comto /etc/hosts made those fetchers work then..

I'll take your word for that. I've never heard of NSCD before. It's just a 
regular HTTP GET request, though, right? Does it hang browsers, too?

> So I chose to set off another async request through KIO::Job when a
> search result is clicked. This request will retrieve details about the
> Discogs-Release (Tracklist, Cover-Image etc.), but as expected they won't
> be shown unless the search result is clicked a 2nd time. Of course this
> is because when the async request is completed it won't update any GUI
> objects. This leads me to the question whether there's a possibility to
> update them after the async request completed or if I have to reconsider
> implementing it using the blocking ::readXMLFile() call?

What happens if the user clicks on a search result and immediately clicks 
Add? Just nothing? The visual update would work, I just would expect the 
user to get confused when additional information is added or not. And using 
the blocking call would match all the other fetchers, too, it's not as 
though they're not susceptible to network slowness just as much.

> When do you think will tellico-1.3 be released approximately? I'll try to
> keep up by providing a working and tested discogs-fetcher by then..

Sometime this month, I'd like to have it out before Christmas.

> > The API key in the patch is hard-coded and the one we're using here
> > currently. You can use it for testing, but please don't abuse it!
>
> Is the intent to ask each user to get their own?
>
>
> Yes, the API key is required for their XML-based interface (See
> http://www.discogs.com/help/api). You'll receive a key permitting 50'000
> requests per day after (free) subscription.

Got it. That's similar to what is needed for the CrossRef fetcher, and the 
config widget makes mention of that.

> Btw. The second revision of the patch will arrive shortly, now providing
> a configuration dialog with the API key setting.

Cool, thanks.

Robby
Ernie Rasta | 5 Dec 23:04
Picon

Tellico in museum

Yes, I am using Tellico for cataloguing ethnographic items. I work in
small private museum in Poland. I had made some bash script to some
stuff, f.e. backup of database, automatic conversion to small size
pictures (& backup original) to faster work, auto inserting of
pictures by name,...
Some of them are universal, some must be edited for actual database.
And all of them are very dirty and writen in terrible style ;-).
For now I had something around 300 items in database, and I will think
soon about printing cards for every item (maybe Robby can help with
that?).

Sorry about so late reaction, but I just arrived from Morocco. If any
question - You are welcome!

Leszek
Regis Boudin | 5 Dec 23:44

Some news about packaging

Hi everyone,

For those who don't know yet, I'm the person maintaining the .deb
packaging aspect of tellico with a dedicated repository [1]. There have
been a few changes over the last couple of weeks the Debian and Ubuntu
users might like to know about.

[1] http://imalip.info/tellico/

First, after a very long wait, my Debian account was created last night,
which means I can upload directly to the archive. As a consequence,
people using sid should not need to use my repository anymore. The Etch
and Lenny backports will be kept updated regularly.

Second, I finally added gutsy to the list of Ubuntu releases. I'm also
considering the removal of older releases, namely dapper and edgy, which
are probably not used much anymore. If you do use any of these 2, please
say it so I know roughly how much they are needed.

Last, my packages have a few tweaks for a better freedesktop integration
with Gnome, XFCE and KDE4, in particular mimetype icons and opening of
files with only a double click. Don't worry, the changes are not
Debian-specific, and have been merged into the 1.3.x branch. Some people
are simply benefiting from them a bit earlier than other. :)

Finally if you have any question or issue regarding the .deb packages of
Tellico, please don't hesitate to ask me.

Regis
_______________________________________________
tellico-users mailing list
tellico-users@...
http://forge.novell.com/mailman/listinfo/tellico-users
Benny Malengier | 5 Dec 23:50
Picon

Re: Some news about packaging

2007/12/5, Regis Boudin <regis-7CrDGO3DbAZ7tPAFqOLdPg@public.gmane.org>:
Second, I finally added gutsy to the list of Ubuntu releases. I'm also
considering the removal of older releases, namely dapper and edgy, which
are probably not used much anymore. If you do use any of these 2, please
say it so I know roughly how much they are needed.

I think you greatly overestimate the computer literacy of your users.
Only geeks upgrade an OS. Others are only interested in applications.

As long as an OS is supported, and the workload for you is not too bad, why not support it?

Benny
_______________________________________________
tellico-users mailing list
tellico-users@...
http://forge.novell.com/mailman/listinfo/tellico-users
Regis Boudin | 6 Dec 00:46

Re: Some news about packaging


On Wed, 2007-12-05 at 23:50 +0100, Benny Malengier wrote: 
> As long as an OS is supported, and the workload for you is not too
> bad, why not support it?

The fact that I pay for the storage, have different series of patches
for all the pre-gutsy Ubuntu releases which had buggy KDE packages, and
14 different chrooted environment ?
My building process if fairly automated for the matter, but keep in mind
I'm spending my own time and money to provide this service to users of a
distribution I don't even run. I'm happy to do it, but I would rather
not see it done for nothing when I also have other projects to care
about and work on.
Also note that I'm only asking for feed-back. If people tell me they use
it, I will be happy to continue providing it.

Regis
Benny Malengier | 6 Dec 09:27
Picon

Re: Some news about packaging

2007/12/6, Regis Boudin <regis-7CrDGO3DbAZ7tPAFqOLdPg@public.gmane.org>:


On Wed, 2007-12-05 at 23:50 +0100, Benny Malengier wrote:
> As long as an OS is supported, and the workload for you is not too
> bad, why not support it?

The fact that I pay for the storage, have different series of patches
for all the pre-gutsy Ubuntu releases which had buggy KDE packages, and
14 different chrooted environment ?
My building process if fairly automated for the matter, but keep in mind
I'm spending my own time and money to provide this service to users of a
distribution I don't even run. I'm happy to do it, but I would rather
not see it done for nothing when I also have other projects to care
about and work on.

First, I run from svn or the distro's default, so for me it is not needed.
Sourceforge allows to upload packages to their servers so users can download, shifting bandwith away from you. Does the novell hosting solution tellico uses not allow the same. I find sourceforge quite easy in that respect, packagers can upload their own stuff, and the developer only has to approve it to have it appear.
Anyway, I heard Novell had a package build system that can automatically package for several distributions, also Debian/Ubuntu. You might want to check that out: http://en.opensuse.org/Build_Service although with all the patches you mention that might not be an option.

Benny
_______________________________________________
tellico-users mailing list
tellico-users@...
http://forge.novell.com/mailman/listinfo/tellico-users
Regis Boudin | 6 Dec 10:44

Re: Some news about packaging

On Thu, December 6, 2007 08:27, Benny Malengier wrote:
> 2007/12/6, Regis Boudin <regis@...>:
>>
>>
>> On Wed, 2007-12-05 at 23:50 +0100, Benny Malengier wrote:
>> > As long as an OS is supported, and the workload for you is not too
>> > bad, why not support it?
>>
>> The fact that I pay for the storage, have different series of patches
>> for all the pre-gutsy Ubuntu releases which had buggy KDE packages, and
>> 14 different chrooted environment ?
>> My building process if fairly automated for the matter, but keep in mind
>> I'm spending my own time and money to provide this service to users of a
>> distribution I don't even run. I'm happy to do it, but I would rather
>> not see it done for nothing when I also have other projects to care
>> about and work on.
>
>
> First, I run from svn or the distro's default, so for me it is not needed.

Why do you complain, then ?

> Anyway, I heard Novell had a package build system that can automatically
> package for several distributions, also Debian/Ubuntu. You might want to
> check that out: http://en.opensuse.org/Build_Service although with all the
> patches you mention that might not be an option.

Not going into the details, but they support even less distros than I do,
and the packages still need someone to patch the build-dependencies to
make them build. Not sure the users would gain much in the process...

R.
stfbln | 6 Dec 15:47
Picon

How to switch spell-checking off?

Hi, is there a way to change the spell-checking language inside tellico? The background is: I'm using tellico for my literature database that includes also the abstracts of articles. Those usually are in English, whereas my KDE language is German. If I edit an entry most of the words are highlighted (because unknown in German) what makes the text hardly readable (the more that there are a few words "known" remaining black). So, how can I change the language settings for tellico? Or even better, how can I switch spell checking off (naturally few articles are in German and have an abstract in German)?

View this message in context: How to switch spell-checking off?
Sent from the Tellico mailing list archive at Nabble.com.
_______________________________________________
tellico-users mailing list
tellico-users@...
http://forge.novell.com/mailman/listinfo/tellico-users

Gmane