summaryrefslogtreecommitdiffstats
path: root/kaffeine/src/input/disc/cddb.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kaffeine/src/input/disc/cddb.cpp')
-rw-r--r--kaffeine/src/input/disc/cddb.cpp572
1 files changed, 572 insertions, 0 deletions
diff --git a/kaffeine/src/input/disc/cddb.cpp b/kaffeine/src/input/disc/cddb.cpp
new file mode 100644
index 0000000..407fbbc
--- /dev/null
+++ b/kaffeine/src/input/disc/cddb.cpp
@@ -0,0 +1,572 @@
+/*
+ * cddb.cpp
+ *
+ * Copyright (C) 2000 Michael Matz <matz@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <qdir.h>
+#include <qtextstream.h>
+#include <qregexp.h>
+#include <qapplication.h>
+#include <qstring.h>
+#include <qcursor.h>
+#include <kdebug.h>
+#include <ksock.h>
+#include <kextsock.h>
+#include <klocale.h>
+#include <kinputdialog.h>
+
+#include "cddb.h"
+#include "cddb.moc"
+
+CDDB::CDDB() : ks(0), port(80), remote(false), save_local(false)
+{
+ QString s = QDir::homeDirPath()+"/.cddb";
+ cddb_dirs +=s;
+}
+
+CDDB::~CDDB()
+{
+ deinit();
+}
+
+bool CDDB::set_server(const char *hostname, unsigned short int _port)
+{
+ if (ks)
+ {
+ if (h_name == hostname && port == _port)
+ return true;
+ deinit();
+ }
+ remote = (hostname != 0) && (*hostname != 0);
+ kdDebug(7101) << "CDDB: set_server, host=" << hostname << "port=" << _port << endl;
+ if (remote)
+ {
+ ks = new KExtendedSocket(hostname, _port);
+ if (ks->connect() < 0)
+ {
+ kdDebug(7101) << "CDDB: Can't connect!" << endl;
+ delete ks;
+ ks = 0;
+ return false;
+ }
+
+ h_name = hostname;
+ port = _port;
+ QCString r;
+ readLine(r); // the server greeting
+ writeLine("cddb hello kde-user blubb kio_audiocd 0.4");
+ readLine(r);
+ }
+ return true;
+}
+
+bool CDDB::deinit()
+{
+ if (ks)
+ {
+ writeLine("quit");
+ QCString r;
+ readLine(r);
+ ks->close();
+ }
+ h_name.resize(0);
+ port = 0;
+ remote = false;
+ ks = 0;
+ return true;
+}
+
+bool CDDB::readLine(QCString& ret)
+{
+ int read_length = 0;
+ char small_b[128];
+ //fd_set set;
+
+ ret.resize(0);
+ while (read_length < 40000)
+ {
+ // Look for a \n in buf
+ int ni = buf.find('\n');
+ if (ni >= 0)
+ {
+ // Nice, so return this substring (without the \n),
+ // and truncate buf accordingly
+ ret = buf.left(ni);
+ if (ret.length() && ret[ret.length()-1] == '\r')
+ ret.resize(ret.length());
+ buf.remove(0, ni+1);
+ kdDebug(7101) << "CDDB: got `" << ret << "'" << endl;
+ return true;
+ }
+
+ // Try to refill the buffer
+ ks->waitForMore(60 * 1000);
+ ssize_t l = ks->readBlock(small_b, sizeof(small_b)-1);
+ if (l <= 0)
+ {
+ // l==0 normally means fd got closed, but we really need a lineend
+ return false;
+ }
+ small_b[l] = 0;
+ read_length += l;
+ buf += small_b;
+ }
+ return false;
+}
+
+bool CDDB::writeLine(const QCString& line)
+{
+ const char *b = line.data();
+ int l = line.length();
+ kdDebug(7101) << "CDDB: send `" << line << "'" << endl;
+ while (l)
+ {
+ ssize_t wl = ks->writeBlock(b, l);
+ if (wl < 0 && errno != EINTR)
+ return false;
+ if (wl < 0)
+ wl = 0;
+ l -= wl;
+ b += wl;
+ }
+ l = line.length();
+ if (l && line.data()[l-1] != '\n')
+ {
+ char c = '\n';
+ ssize_t wl;
+ do {
+ wl = ks->writeBlock(&c, 1);
+ } while (wl <= 0 && errno == EINTR);
+ if (wl<=0 && errno != EINTR)
+ return false;
+ }
+ return true;
+}
+
+unsigned int CDDB::get_discid(QValueList<int>& track_ofs)
+{
+ unsigned int id = 0;
+ int num_tracks = track_ofs.count() - 2;
+
+ // the last two track_ofs[] are disc begin and disc end
+
+ for (int i = num_tracks - 1; i >= 0; i--)
+ {
+ int n = track_ofs[i];
+ n /= 75;
+ while (n > 0)
+ {
+ id += n % 10;
+ n /= 10;
+ }
+ }
+ unsigned int l = track_ofs[num_tracks + 1];
+ l -= track_ofs[num_tracks];
+ l /= 75;
+ id = ((id % 255) << 24) | (l << 8) | num_tracks;
+ return id;
+}
+
+static int get_code (const QCString &s)
+{
+ bool ok;
+ int code = s.left(3).toInt(&ok);
+ if (!ok)
+ code = -1;
+ return code;
+}
+
+static void parse_query_resp (const QCString& _r, QCString& catg, QCString& d_id, QCString& title)
+{
+ QCString r = _r.stripWhiteSpace();
+ int i = r.find(' ');
+ if (i)
+ {
+ catg = r.left(i).stripWhiteSpace();
+ r.remove(0, i+1);
+ r = r.stripWhiteSpace();
+ }
+ i = r.find(' ');
+ if (i)
+ {
+ d_id = r.left(i).stripWhiteSpace();
+ r.remove(0, i+1);
+ r = r.stripWhiteSpace();
+ }
+ title = r;
+}
+
+QString CDDB::track(int i) const
+{
+ if (i < 0 || i >= int(m_names.count()))
+ return QString();
+ return m_names[i].utf8();
+}
+
+bool CDDB::parse_read_resp(QTextStream *stream, QTextStream *write_stream)
+{
+ /* Note, that m_names and m_title should be empty */
+ QCString end = ".";
+
+ /* Fill table, so we can index it below. */
+ for (int i = 0; i < m_tracks; i++)
+ {
+ m_names.append("");
+ }
+ while (1)
+ {
+ QCString r;
+ if (stream)
+ {
+ if (stream->atEnd())
+ break;
+ r = stream->readLine().latin1();
+ }
+ else
+ {
+ if (!readLine(r))
+ return false;
+ }
+ /* Normally the "." is not saved into the local files, but be
+ tolerant about this. */
+ if (r == end)
+ break;
+ if (write_stream)
+ *write_stream << r << endl;
+ r = r.stripWhiteSpace();
+ if (r.isEmpty() || r[0] == '#')
+ continue;
+ if (r.left(7) == "DTITLE=")
+ {
+ r.remove(0, 7);
+ m_title += QString::fromLocal8Bit(r.stripWhiteSpace());
+ }
+ else if (r.left(6) == "TTITLE")
+ {
+ r.remove(0, 6);
+ int e = r.find('=');
+ if (e)
+ {
+ bool ok;
+ int i = r.left(e).toInt(&ok);
+ if (ok && i >= 0 && i < m_tracks)
+ {
+ r.remove(0, e+1);
+ m_names[i] += QString::fromLocal8Bit(r);
+ }
+ }
+ }
+ }
+
+ /* XXX We should canonicalize the strings ("\n" --> '\n' e.g.) */
+
+ int si = m_title.find(" / ");
+ if (si > 0)
+ {
+ m_artist = m_title.left(si).stripWhiteSpace();
+ m_title.remove(0, si+3);
+ m_title = m_title.stripWhiteSpace();
+ }
+
+ if (m_title.isEmpty())
+ m_title = i18n("No Title");
+ /*else
+ m_title.replace(QRegExp("/"), "%2f");*/
+ if (m_artist.isEmpty())
+ m_artist = i18n("Unknown");
+ /*else
+ m_artist.replace(QRegExp("/"), "%2f");*/
+
+ kdDebug(7101) << "CDDB: found Title: `" << m_title << "'" << endl;
+ for (int i = 0; i < m_tracks; i++)
+ {
+ if (m_names[i].isEmpty())
+ m_names[i] += i18n("Track %1").arg(i);
+ m_names[i].replace(QRegExp("/"), "%2f");
+ kdDebug(7101) << "CDDB: found Track " << i+1 << ": `" << m_names[i]
+ << "'" << endl;
+ }
+ return true;
+}
+
+void CDDB::add_cddb_dirs(const QStringList& list)
+{
+ QString s = QDir::homeDirPath()+"/.cddb";
+
+ cddb_dirs = list;
+ if (cddb_dirs.isEmpty())
+ cddb_dirs += s;
+}
+
+/* Locates and opens the local file corresponding to that discid.
+ Returns TRUE, if file is found and ready for reading.
+ Returns FALSE, if file isn't found. In this case ret_file is initialized
+ with a QFile which resides in the first cddb_dir, and has a temp name
+ (the ID + getpid()). You can open it for writing. */
+bool CDDB::searchLocal(unsigned int id, QFile *ret_file)
+{
+ QDir dir;
+ QString filename;
+ filename = QString("%1").arg(id, 0, 16).rightJustify(8, '0');
+ QStringList::ConstIterator it;
+ for (it = cddb_dirs.begin(); it != cddb_dirs.end(); ++it)
+ {
+ dir.setPath(*it);
+ if (!dir.exists())
+ continue;
+ /* First look in dir directly. */
+ ret_file->setName (*it + "/" + filename);
+ if (ret_file->exists() && ret_file->open(IO_ReadOnly))
+ return true;
+ /* And then in the subdirs of dir (representing the categories normally).
+ */
+ const QFileInfoList *subdirs = dir.entryInfoList (QDir::Dirs);
+ QFileInfoListIterator fiit(*subdirs);
+ QFileInfo *fi;
+ while ((fi = fiit.current()) != 0)
+ {
+ ret_file->setName (*it + "/" + fi->fileName() + "/" + filename);
+ if (ret_file->exists() && ret_file->open(IO_ReadOnly))
+ return true;
+ ++fiit;
+ }
+ }
+ QString pid;
+ pid.setNum(::getpid());
+ ret_file->setName (cddb_dirs[0] + "/" + filename + "." + pid);
+ /* Try to create the save location. */
+ dir.setPath(cddb_dirs[0]);
+ if (save_local && !dir.exists())
+ {
+ //dir = QDir::current();
+ dir.mkdir(cddb_dirs[0]);
+ }
+ return false;
+}
+
+bool CDDB::queryCD(QValueList<int>& track_ofs)
+{
+ int num_tracks = track_ofs.count() - 2;
+ if (num_tracks < 1)
+ return false;
+ unsigned int id = get_discid(track_ofs);
+ QFile file;
+ bool local;
+
+ /* Already read this ID. */
+ if (id == m_discid)
+ return true;
+
+ emit cddbMessage(i18n("Searching local cddb entry ..."));
+ qApp->processEvents();
+
+ /* First look for a local file. */
+ local = searchLocal (id, &file);
+ /* If we have no local file, and no remote connection, barf. */
+ if (!local && (!remote || ks == 0))
+ return false;
+
+ m_tracks = num_tracks;
+ m_title = "";
+ m_artist = "";
+ m_names.clear();
+ m_discid = id;
+ if (local)
+ {
+ QTextStream stream(&file);
+ /* XXX Hmm, what encoding is used by CDDB files? local? Unicode?
+ Nothing? */
+ //stream.setEncoding(QTextStream::Locale);
+ parse_read_resp(&stream, 0);
+ file.close();
+ return true;
+ }
+
+ emit cddbMessage(i18n("Searching remote cddb entry ..."));
+ qApp->processEvents();
+
+ /* Remote CDDB query. */
+ unsigned int length = track_ofs[num_tracks+1] - track_ofs[num_tracks];
+ QCString q;
+ q.sprintf("cddb query %08x %d", id, num_tracks);
+ QCString num;
+ for (int i = 0; i < num_tracks; i++)
+ q += " " + num.setNum(track_ofs[i]);
+ q += " " + num.setNum(length / 75);
+ if (!writeLine(q))
+ return false;
+ QCString r;
+ if (!readLine(r))
+ return false;
+ r = r.stripWhiteSpace();
+ int code = get_code(r);
+ if (code == 200)
+ {
+ QCString catg, d_id, title;
+ QDir dir;
+ QCString s, pid;
+
+ emit cddbMessage(i18n("Found exact match cddb entry ..."));
+ qApp->processEvents();
+
+ /* an exact match */
+ r.remove(0, 3);
+ parse_query_resp(r, catg, d_id, title);
+ kdDebug(7101) << "CDDB: found exact CD: category=" << catg << " DiscId="
+ << d_id << " Title=`" << title << "'" << endl;
+ q = "cddb read " + catg + " " + d_id;
+ if (!writeLine(q))
+ return false;
+ if (!readLine(r))
+ return false;
+ r = r.stripWhiteSpace();
+ code = get_code(r);
+ if (code != 210)
+ return false;
+
+ pid.setNum(::getpid());
+ s = cddb_dirs[0].latin1();
+ //s = s + "/" +catg; // xine seems to not search in local subdirs
+ dir.setPath( s );
+ if ( !dir.exists() ) dir.mkdir( s );
+ s = s+"/"+ d_id;
+ file.setName( s );
+
+ if (save_local && file.open(IO_WriteOnly))
+ {
+ kdDebug(7101) << "CDDB: file name to save =" << file.name() << endl;
+ QTextStream stream(&file);
+ if (!parse_read_resp(0, &stream))
+ {
+ file.remove();
+ return false;
+ }
+ file.close();
+ /*QString newname (file.name());
+ newname.truncate(newname.findRev('.'));
+ if (QDir::current().rename(file.name(), newname)) {
+ kdDebug(7101) << "CDDB: rename failed" << endl;
+ file.remove();
+ } */
+ }
+ else if (!parse_read_resp(0, 0))
+ return false;
+ }
+ else if (code == 211)
+ {
+ // Found some close matches. We'll read the query response and ask the user
+ // which one should be fetched from the server.
+ QCString end = ".";
+ QCString catg, d_id, title;
+ QDir dir;
+ QCString s, pid, first_match;
+ QStringList disc_ids;
+
+ /* some close matches */
+ //XXX may be try to find marker based on r
+ emit cddbMessage(i18n("Found close cddb entry ..."));
+ qApp->processEvents();
+
+ int i=0;
+ while (1)
+ {
+ if (!readLine(r))
+ return false;
+ r = r.stripWhiteSpace();
+ if (r == end)
+ break;
+ disc_ids.append(r);
+ if (i == 0)
+ first_match = r;
+ i++;
+ }
+
+ bool ok = false;
+
+ // We don't want to be thinking too much, do we?
+ QApplication::restoreOverrideCursor();
+
+ // Oh, mylord, which match should I serve you?
+ QString _answer = KInputDialog::getItem(i18n("CDDB Matches"), i18n("Several close CDDB entries found. Choose one:"),
+ disc_ids, 0, false, &ok );
+ QCString answer = _answer.utf8();
+
+ if (ok){ // Get user selected match
+ parse_query_resp(answer, catg, d_id, title);
+ }
+ else{ // Get first match
+ parse_query_resp(first_match, catg, d_id, title);
+ }
+
+ // Now we can continue thinking...
+ QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) );
+
+ kdDebug(7101) << "CDDB: found close CD: category=" << catg << " DiscId="
+ << d_id << " Title=`" << title << "'" << endl;
+
+ // ... and forth we go as usual
+
+ q = "cddb read " + catg + " " + d_id;
+ if (!writeLine(q))
+ return false;
+ if (!readLine(r))
+ return false;
+ r = r.stripWhiteSpace();
+ code = get_code(r);
+ if (code != 210)
+ return false;
+
+ pid.setNum(::getpid());
+ s = cddb_dirs[0].latin1();
+ dir.setPath( s );
+ if ( !dir.exists() ) dir.mkdir( s );
+ s = s+"/"+ d_id;
+ file.setName( s );
+
+ if (save_local && file.open(IO_WriteOnly))
+ {
+ kdDebug(7101) << "CDDB: file name to save =" << file.name() << endl;
+ QTextStream stream(&file);
+ if (!parse_read_resp(0, &stream))
+ {
+ file.remove();
+ return false;
+ }
+ file.close();
+ }
+ else if (!parse_read_resp(0, 0))
+ return false;
+
+ }
+ else
+ {
+ /* 202 - no match found
+ 403 - Database entry corrupt
+ 409 - no handshake */
+ kdDebug(7101) << "CDDB: query returned code " << code << endl;
+ return false;
+ }
+
+ return true;
+}