3
* kscd -- A simple CD player for the KDE project
5
* $Id: cddb.cpp,v 1.40.2.1 2001/11/06 06:07:47 dfoerste Exp $
7
* Copyright (C) 1997 Bernd Johannes Wuebben
8
* wuebben@math.cornell.edu
11
* This library is free software; you can redistribute it and/or
12
* modify it under the terms of the GNU Library General Public
13
* License as published by the Free Software Foundation; either
14
* version 2 of the License, or (at your option) any later version.
16
* This library is distributed in the hope that it will be useful,
17
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
* Library General Public License for more details.
21
* You should have received a copy of the GNU Library General Public
22
* License along with this program; if not, write to the Free
23
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
33
#include <sys/types.h>
35
#include <sys/utsname.h>
37
#include <qtextstream.h>
40
#include <qfileinfo.h>
49
extern KApplication *mykapp;
51
void cddb_decode(QString& str);
52
void cddb_encode(QString& str, QStringList &returnlist);
53
bool cddb_playlist_decode(QStringList& playlist,QString& value);
54
void cddb_playlist_encode(QStringList& playlist,QString& value);
57
extern char cddbbasedirtext[4096];
59
CDDB::CDDB(char *host, unsigned short int _port, unsigned short int _timeout)
73
// for direct connections assuming CDDB protocol level 1
75
// get current user/host name
79
domainname = uts.nodename;
82
if(domainname.isEmpty())
83
domainname = "somemachine.nowhere.org";
85
pw = getpwuid(getuid());
87
username = pw->pw_name;
89
username = "anonymous";
90
//printf("cddb info: host[%s] port[%d] connected[%d] readonly[%d] timeout[%d]\n", host, port, connected, readonly, timeout);
91
//printf("attemping to connect to cddb...\n");
94
QObject::connect(&starttimer,SIGNAL(timeout()),this,SLOT(cddb_connect_internal()));
95
QObject::connect(&timeouttimer,SIGNAL(timeout()),this,SLOT(cddb_timed_out_slot()));
111
CDDB::setTimeout(unsigned short int _timeout)
117
CDDB::getTimeout( void )
123
CDDB::sighandler(int signum)
127
/* if (signum == SIGALRM && connecting == true ){
128
mykapp->processEvents();
130
signal( SIGALRM , CDDB::sighandler );
132
*/ fprintf(stderr,"SIGALRM\n");
141
struct itimerval val1;
142
struct timeval tval1;
143
struct timeval tval2;
146
tval1.tv_usec = 100000; // 0.1 secs
148
tval2.tv_usec = 100000;
150
val1.it_interval = tval1;
151
val1.it_value = tval2;
153
setitimer(ITIMER_REAL, &val1, 0);
157
CDDB::cddbgetServerList(QString& _server)
159
char ser [CDDB_FIELD_BUFFER_LEN];
160
char por [CDDB_FIELD_BUFFER_LEN];
161
char proto [CDDB_FIELD_BUFFER_LEN];
162
char extra [CDDB_FIELD_BUFFER_LEN];
164
sscanf(_server.ascii(),"%s %s %s %s",ser,proto,por,extra);
170
protocol=decodeTransport(proto);
172
kdDebug() << "GETTING SERVERLIST\n" << endl;
174
mode = SERVER_LIST_GET;
176
if(protocol==CDDBHTTP)
178
cddb_connect_internal();
182
send_http_command(cmd);
185
saved_state=SERVER_LIST_WAIT;
188
state = SERVER_LIST_WAIT;
192
starttimer.start(100,TRUE);
194
} // cddbgetServerList
197
CDDB::cddb_connect(QString& _server)
199
char ser[CDDB_FIELD_BUFFER_LEN];
200
char por[CDDB_FIELD_BUFFER_LEN];
201
char proto[CDDB_FIELD_BUFFER_LEN];
202
char extra[CDDB_FIELD_BUFFER_LEN];
204
sscanf(_server.ascii(),"%s %s %s %s",ser,proto,por,extra);
209
protocol = decodeTransport(proto);
215
starttimer.start(100,TRUE);
222
CDDB::cddb_connect_internal()
225
timeouttimer.start(timeout*1000,TRUE);
227
// kdDebug() << "cddb_connect_internal_timeout = " << timeout*1000 << "\n" << endl;
235
// signal( SIGALRM , CDDB::sighandler );
238
fprintf(stderr, "proto = %d, proxy=%s\n", protocol, use_http_proxy?"true":"false");
239
if(protocol==CDDBHTTP && use_http_proxy)
241
fprintf(stderr, "PROX\n");
242
kdDebug() << "CONNECTING TO " << proxyhost << ":" << proxyport << " ....\n" << endl;
243
sock = new KSocket(proxyhost.ascii(),proxyport, timeout);
244
kdDebug() << "SOCKET SET" << endl;
246
kdDebug() << "CONNECTING TO " << hostname << ":" << port << " ....\n" << endl;
247
sock = new KSocket(hostname.local8Bit(),port, timeout);
248
kdDebug() << "SOCKET SET" << endl;
251
//signal( SIGALRM , SIG_DFL );
253
if(sock == 0L || sock->socket() < 0)
257
kdDebug() << "CONNECT FAILED\n" << endl;
261
else // mode == SERVER_LIST_GET
262
emit get_server_list_failed();
271
connect(sock,SIGNAL(readEvent(KSocket*)),this,SLOT(cddb_read(KSocket*)));
272
connect(sock,SIGNAL(closeEvent(KSocket*)),this,SLOT(cddb_close(KSocket*)));
273
sock->enableRead(true);
275
if(protocol==CDDBHTTP)
284
kdDebug() << "CONNECTED\n" << endl;
285
} // cddb_connect_internal
288
CDDB::send_http_command(QString &command)
293
QString identification;
295
prot.setNum(protocol_level);
296
identification="&hello="+username+"+"+domainname+"+Kscd+"+KSCDVERSION+"&proto="+prot;
299
QString base = "http://"+hostname+":"+prt;
301
cddb_http_xlat(command);
304
request="GET "+base+cgi+"?cmd="+command+identification+" HTTP/1.0\r\n\r\n";
306
request="GET "+cgi+"?cmd="+command+identification+"\r\n";
308
kdDebug() << "Sending HTTP request: " << request << endl;
310
write(sock->socket(),request.ascii(),request.length());
312
timeouttimer.start(timeout*1000,TRUE);
313
} // send_http_command
316
CDDB::cddb_timed_out_slot()
321
sock->enableRead(false);
323
if( mode == REGULAR )
324
emit cddb_timed_out();
325
else // mode == SERVER_LIST_GET
326
emit get_server_list_failed();
328
state = CDDB_TIMEDOUT;
329
kdDebug() << "SOCKET CONNECTION TIMED OUT\n" << endl;
331
} // cddb_timed_out_slot
333
// called externally if we want to close or interrupt the cddb connection
335
CDDB::close_connection()
342
} // close_connection
345
CDDB::cddb_close(KSocket *socket)
348
disconnect(socket,SIGNAL(readEvent(KSocket*)),this,SLOT(cddb_read(KSocket*)));
349
disconnect(socket,SIGNAL(closeEvent(KSocket*)),this,SLOT(cddb_close(KSocket*)));
350
socket->enableRead(false);
351
kdDebug() << "SOCKET CONNECTION TERMINATED\n" << endl;
361
#include <sys/types.h>
362
#include <sys/stat.h>
364
#include <kstddirs.h>
368
CDDB::cddb_read(KSocket *socket)
371
char buffer[CDDB_READ_BUFFER_LEN];
373
if(socket == 0L || socket->socket() < 0)
376
memset(buffer,0,CDDB_READ_BUFFER_LEN);
377
n = read(socket->socket(), buffer, CDDB_READ_BUFFER_LEN-1 );
379
tempbuffer += buffer;
381
// kdDebug() << "BUFFER: '" << buffer << "'" << endl;
390
int newlinepos = tempbuffer.find('\n');
393
lastline = tempbuffer.left(newlinepos);
394
tempbuffer = tempbuffer.right(tempbuffer.length() - newlinepos -1);
402
CDDB::queryCD(unsigned long _magicID,QStringList& querylist)
404
// if(state == DO_NOTHING)
406
// state = DO_NOTHING;
407
if((sock == 0L || sock->socket() < 0) && protocol==CDDBP)
415
str = str.sprintf("cddb query %08lx %u ",magicID,querylist.count()-1);
416
str += querylist.join(" ") + ' ';
418
if(protocol==CDDBHTTP)
420
cddb_connect_internal();
424
send_http_command(param);
428
state = HTTP_REQUEST;
436
timeouttimer.start(timeout*1000,TRUE);
438
kdDebug() << "strdata: " << str << "\n" << endl;
439
write(sock->socket(),str.ascii(),str.length());
445
CDDB::query_exact(QString line)
447
int category_start = 0;
448
int category_end = 0;
454
category_start = line.find(" ",0,true) + 1;
455
category_end = line.find(" ",category_start,true);
456
category = line.mid(category_start,category_end-category_start);
458
magic_start = category_end + 1;
459
magic_end = line.find(" ",magic_start,true);
460
magicstr = line.mid( magic_start, magic_end - magic_start);
462
title = line.mid(magic_end + 1,line.length());
465
if((sock == 0L || sock ->socket() < 0) && protocol==CDDBP)
467
kdDebug() << "sock = 0L!!!\n" << endl;
471
if(protocol==CDDBHTTP)
473
cddb_connect_internal();
476
readstring = QString::fromLatin1("cddb read %1 %2")
477
.arg(category).arg(magicstr);
478
send_http_command(readstring);
483
timeouttimer.start(timeout*1000,TRUE);
484
// readstring.sprintf("cddb read %s %lx \n",category.data(),magicID);
485
readstring = QString::fromLatin1("cddb read %1 %2 \n")
488
write(sock->socket(),readstring.ascii(),readstring.length());
493
sock->enableRead(true);
498
CDDB::do_state_machine()
500
static int cddbfh = 0;
504
kdDebug() << "STATE MACHINE: State: " << (int)state << " Got: " << lastline << "\n" << endl;
510
if(lastline.stripWhiteSpace()==QString(""))
513
kdDebug() << "HTTP Header is done. Moving on.\n" << endl;
518
//Parse responce and check numeric code.
519
char proto [CDDB_FIELD_BUFFER_LEN];
520
char code [CDDB_FIELD_BUFFER_LEN];
521
sscanf(lastline.ascii(),"%s %s",proto,code);
522
if(strcmp(code,"200")==0)
527
kdDebug() << "HTTP request is OK. Reading HTTP header.\n" << endl;
530
kdDebug() << "HTTP request is OK. Mooving on.\n" << endl;
533
kdDebug() << "HTTP error: " << lastline << "\n" << endl;
534
if(saved_state==SERVER_LIST_WAIT)
536
emit get_server_list_failed();
538
state=CDDB_DONE; //TODO: some error state
543
kdDebug() << "case INIT == true\n" << endl;
544
if((lastline.left(3) == QString("201")) ||(lastline.left(3) == QString("200")) )
546
kdDebug() << "next if == true\n" << endl;
549
// cddb hello username hostname clientname version
550
hellostr = QString("cddb hello %1 %2 Kscd %3\n")
554
kdDebug() << "hellostr: " << hellostr << "\n" << endl;
556
Ret = write(sock->socket(),hellostr.ascii(),hellostr.length());
557
kdDebug() << "write() returned: " << Ret << " [" << strerror(errno) << "]\n" << endl;
562
kdDebug() << "ERROR_INIT\n" << endl;
570
if(lastline.left(3) == QString("200"))
572
// Negotiate protocol level
574
// Let's try to request protocol level 3
575
// so we'll get list of servers with protocol.
576
write(sock->socket(),"proto 3\n",8);
580
kdDebug() << "ERROR_HELLO\n" << endl;
588
if(lastline.left(3) == QString("201"))
598
write(sock->socket(),"sites\n",6);
599
state = SERVER_LIST_WAIT;
604
if(lastline.left(3) == QString("200"))
606
query_exact(lastline);
608
if(lastline.left(3) == QString("211")) // single or multiple inexact
610
inexact_list.clear();
613
if(lastline.left(3) == QString("202"))
620
if(lastline.left(3) == QString("210")) // multiple exact
622
inexact_list.clear();
627
kdDebug() << "ERROR_QUERY\n" << endl;
636
if(lastline.at(0) == '.')
640
emit cddb_inexact_read();
642
inexact_list.append(lastline);
647
if(lastline.at(0) == '.')
651
emit cddb_inexact_read();
653
inexact_list.append(lastline);
658
if(lastline.at(0) == '.')
662
if(protocol!=CDDBHTTP)
663
write(sock->socket(),"quit\n",6);
672
file.sprintf("%s/%08lx", category.utf8().data(), magicID);
673
file = locate("cddb", file);
675
kdDebug() << "dir/file path: " << file << "\n" << endl;
676
cddbfh = open(QFile::encodeName(file), O_CREAT|O_WRONLY|O_TRUNC,
677
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
679
cddblinelen = lastline.length();
680
write(cddbfh, lastline.ascii(), cddblinelen);
681
write(cddbfh, "\n", strlen("\n"));
682
// kdDebug() << "line written: " << cddblinelen << "\n" << endl;
684
respbuffer.prepend("\n");
685
respbuffer.prepend(lastline);
691
if(lastline.at(0) == '4')
693
state = ERROR_CDDB_READ;
694
kdDebug() << "ERROR_CDDB_READ\n" << endl;
699
state = CDDB_READING;
703
case SERVER_LIST_WAIT:
705
if(lastline.left(3) == QString("210"))
708
state=GETTING_SERVER_LIST;
711
emit get_server_list_failed();
715
case GETTING_SERVER_LIST:
716
if(lastline.at(0) == '.')
718
kdDebug() << "GOT SERVERLIST\n" << endl;
719
if(protocol!=CDDBHTTP)
720
write(sock->socket(),"quit\n",6);
722
emit get_server_list_done();
725
parse_serverlist_entry();
733
} // do_state_machine
737
CDDB::serverList(QStringList& list)
743
CDDB::parse_serverlist_entry()
745
char serv [CDDB_FIELD_BUFFER_LEN];
746
char po [CDDB_FIELD_BUFFER_LEN];
747
char proto [CDDB_FIELD_BUFFER_LEN];
748
char extra [CDDB_FIELD_BUFFER_LEN];
754
sscanf(lastline.ascii(),"%s %s",serv,po);
755
tempstr = tempstr.sprintf("%s cddbp %s -",serv,po);
756
serverlist.append(tempstr);
758
sscanf(lastline.ascii(),"%s %s %s %s",serv,proto,po,extra);
759
tempstr = tempstr.sprintf("%s %s %s %s",serv,proto,po,extra);
760
// transport tr=decodeTransport(proto);
761
// if(tr==CDDBP || tr==CDDBHTTP)
762
serverlist.append(tempstr);
764
} // parse_serverlist_entry
767
CDDB::get_inexact_list(QStringList& p_inexact_list)
769
p_inexact_list=inexact_list;
770
} // get_inexact_list
774
unsigned long magicID,
776
QStringList& titlelist,
777
QStringList& extlist,
779
QStringList& discidlist,
781
QStringList& playlist
785
// pre-set revision to 0
787
QStringList pathlist = KGlobal::dirs()->resourceDirs("cddb");
789
if(pathlist.count() == 0)
794
for(QStringList::ConstIterator it = pathlist.begin() ; it != pathlist.end(); it++)
797
d.setFilter( QDir::Dirs);
798
d.setSorting( QDir::Size);
800
QStringList list = d.entryList();
801
QStringList::Iterator it2;
803
for(it2 = list.begin(); it2 != list.end(); it2++)
805
if (*it2 != "." && *it2 != "..")
807
if(checkDir(magicID, *it + *it2))
811
getData(data,titlelist,extlist,_category,discidlist,revision,playlist);
821
CDDB::checkDir(unsigned long magicID, const QString& dir)
824
mag.sprintf("%s/%08lx",dir.utf8().data(),magicID);
827
if(!info.isReadable())
834
if( !file.open( IO_ReadOnly ))
839
QTextStream t(&file);
843
QString s = t.readLine() + "\n";
844
// The following swallowed the last line of the file
845
// no matter if it's just a newline or a whole entry.
854
// scan the relevant parts of the cddba database entry in the the provied structures
859
QStringList& extlist,
861
QStringList& discidlist,
863
QStringList& playlist
874
int pos1,pos2,pos3,pos4 = 0;
876
int revtmp = data.find("Revision:",0,true);
879
revision = 0; // no Revision -> we are first (ZERO)!
881
// Well, I'll see if there's a simpler way for this.
884
revtmp2 = data.find("\n",revtmp,true);
885
if(revtmp2 - revtmp - 9 >=0)
886
revstr = data.mid(revtmp +9,revtmp2 - revtmp -9);
887
revstr.stripWhiteSpace();
889
revision = revstr.toInt(&ok);
890
if(!ok) // Bogus Revision -> we claim to be first (ZERO)!
894
// lets get all DISCID's in the data. Remeber there can be many DISCID's on
895
// several lines separated by commas on each line
897
// DISCID= 47842934,4h48393,47839492
898
// DISCID= 47fd2934,4h48343,47839492,43879074
900
while((pos3 = data.find("DISCID=",pos4,true))!= -1)
903
pos2 = data.find("\n",pos1,true);
908
if( pos2 - pos1 -7 >= 0)
910
discidtemp = data.mid(pos1 + 7,pos2- pos1 -7);
912
kdDebug() << "ANOMALY 1\n" << endl;
915
kdDebug() << "DISCDID " << discidtemp << "\n" << endl;
918
while((pos2 = discidtemp.find(",",pos1,true)) != -1)
920
if( pos2 - pos1 >= 0)
922
temp3 = discidtemp.mid(pos1,pos2-pos1);
924
kdDebug() << "ANOMALY 2\n" << endl;
927
temp3 = temp3.stripWhiteSpace();
930
discidlist.append(temp3);
934
temp3 = discidtemp.mid(pos1,discidtemp.length());
935
temp3.stripWhiteSpace();
939
discidlist.append(temp3);
944
kdDebug() << "FOUND " << discidlist.count() << " DISCID's\n" << endl;
952
getValue(key,value,data);
953
titles.append(value);
957
key = key.sprintf("TTITLE%d=",counter);
958
while(getValue(key,value,data))
960
titles.append(value);
961
key = key.sprintf("TTITLE%d=",++counter);
965
getValue(key,value,data);
966
extlist.append(value);
969
key = key.sprintf("EXTT%d=",counter);
970
while(getValue(key,value,data))
972
extlist.append(value);
973
key = key.sprintf("EXTT%d=",++counter);
977
getValue(key,value,data);
978
cddb_playlist_decode(playlist, value);
982
CDDB::getCategoryFromPathName(const QString& pathname){
984
QString path = pathname;
985
path = path.stripWhiteSpace();
987
while(path.right(1).at(1) == '/'){
988
path = path.left(path.length() - 1);
992
pos = path.findRev("/",-1,true);
996
return path.mid(pos+1,path.length());
998
} // getCategoryFromPathName
1001
CDDB::getValue(QString& key,QString& value, QString& data)
1004
bool found_one = false;
1010
while(( pos1 = data.find(key,pos1,true)) != -1)
1013
pos2 = data.find("\n",pos1,true);
1014
if( (pos2 - pos1 - (int)key.length()) >= 0)
1016
value += data.mid(pos1 + key.length(), pos2 - pos1 - key.length());
1018
kdDebug() << "GET VALUE ANOMALY 1\n" << endl;
1031
cddb_playlist_encode(QStringList& list,QString& playstr)
1033
playstr = list.join(",");
1034
} // cddb_playlist_encode
1038
cddb_playlist_decode(QStringList& list, QString& str)
1047
list = QStringList::split(',', str);
1053
for ( QStringList::Iterator it = list.begin();
1058
check = check.stripWhiteSpace();
1060
if (check.isEmpty())
1062
it = list.remove(it);
1066
num = check.toInt(&ok1);
1069
it = list.remove(it);
1077
/* for(uint i = 0; i < list.count(); i++){
1078
printf("playlist %d=%s\n",i,list.at(i));
1081
} // cddb_playlist_decode
1084
cddb_decode(QString& str)
1089
while((pos2 = str.find("\\n",pos1,true)) !=-1 )
1093
if(str.mid(pos2-1,3) == QString("\\\\n"))
1099
str.replace(pos2 , 2 , "\n");
1106
while((pos2 = str.find("\\t",pos1,true)) !=-1)
1110
if(str.mid(pos2-1,3) == QString("\\\\t"))
1116
str.replace(pos2 , 2 , "\t");
1123
while((pos2 = str.find("\\\\",pos1,true)) !=-1)
1125
str.replace(pos2 , 2 , "\\");
1131
cddb_encode(QString& str, QStringList &returnlist)
1138
while((pos2 = str.find("\\",pos1,true)) !=-1)
1140
str.replace(pos2 , 1 , "\\\\");
1147
while((pos2 = str.find("\n",pos1,true)) !=-1)
1149
str.replace(pos2 , 1 , "\\n");
1156
while((pos2 = str.find("\t",pos1,true)) !=-1)
1158
str.replace(pos2 , 1 , "\\t");
1162
while(str.length() > 70)
1164
returnlist.append(str.left(70));
1165
str = str.mid(70,str.length());
1168
returnlist.append(str);
1171
// This function converts server list entry from "Server Port" format
1172
// To "Server Protocol Port Address".
1173
// The fields are as follows:
1175
// The Internet address of the remote site.
1177
// The transfer protocol used to access the site.
1179
// The port at which the server resides on that site.
1181
// Any additional addressing information needed to access the
1182
// server. For example, for HTTP protocol servers, this would be
1183
// the path to the CDDB server CGI script. This field will be
1184
// "-" if no additional addressing information is needed.
1186
// Returns 'true' if format have been converted.
1188
CDDB::normalize_server_list_entry(QString &entry)
1190
char serv [CDDB_FIELD_BUFFER_LEN];
1191
char proto[CDDB_FIELD_BUFFER_LEN];
1192
char po [CDDB_FIELD_BUFFER_LEN];
1193
char extra[CDDB_FIELD_BUFFER_LEN];
1195
if(sscanf(entry.ascii(),"%s %s %s %s",serv,proto,po,extra)==2)
1198
sprintf(extra,"%s cddbp %s -",serv, proto);
1202
// Otherwise let us leave the item unchanged.
1205
} // normalize_server_list_entry
1208
CDDB::setHTTPProxy(QString host, unsigned short int port)
1215
CDDB::useHTTPProxy(bool flag)
1217
use_http_proxy=flag;
1218
} // useHTTPProxy(bool)
1221
CDDB::useHTTPProxy()
1223
return use_http_proxy;
1227
int CDDB::getHTTPProxyPort()
1230
} // getHTTPProxyPort
1233
CDDB::getHTTPProxyHost()
1236
} // getHTTPProxyHost
1239
CDDB::decodeTransport(const char *proto)
1241
if(strcasecmp(proto,"cddbp")==0)
1244
if(strcasecmp(proto,"http")==0)
1247
if(strcasecmp(proto,"smtp")==0)
1251
} // decodeTransport
1254
CDDB::cddb_http_xlat(QString &s)
1262
while(pos < s.length()+1)
1264
switch (s[pos].latin1())
1275
(void) sprintf(q, "%%%02X", (char) s[pos].latin1());
1288
CDDB::setPathList(QStringList& _paths)
1290
pathlist = _paths; // automatically makes deep copies is _paths has deep copies