/* cdrdao - write audio CD-Rs in disc-at-once mode * * Copyright (C) 1998-2000 Andreas Mueller * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Toc.h" #include "CdTextItem.h" #include "util.h" #include "Cddb.h" #define CDDB_MAX_LINE_LEN 1024 #define CDDB_DEFAULT_PORT_CDDBP 888 #define CDDB_DEFAULT_PORT_HTTP 80 static int getCode(const char *line, int code[3]); static unsigned int cddbSum(unsigned int n); static void convertEscapeSequences(const char *in, char *out); static int parseQueryResult(char *line, char *category, char *diskId, char *title); static RETSIGTYPE alarmHandler(int sig) { message(0, "ALARM"); #if RETSIGTYPE != void return 0; #endif } Cddb::Cddb(const Toc *t) { toc_ = t; serverList_ = NULL; selectedServer_ = NULL; localCddbDirectory_ = NULL; fd_ = -1; connected_ = 0; queryResults_ = NULL; cddbEntry_ = NULL; httpCmd_ = NULL; httpData_ = NULL; httpMode_ = 0; timeout_ = 20; } Cddb::~Cddb() { ServerList *snext; shutdown(); clearQueryResults(); clearCddbEntry(); delete[] localCddbDirectory_; localCddbDirectory_ = NULL; delete[] httpCmd_; httpCmd_ = NULL; delete[] httpData_; httpData_ = NULL; while (serverList_ != NULL) { snext = serverList_->next; delete[] serverList_->server; serverList_->server = NULL; delete[] serverList_->httpCgiBin; serverList_->httpCgiBin = NULL; delete[] serverList_->httpProxyServer; serverList_->httpProxyServer = NULL; delete serverList_; serverList_ = snext; } } void Cddb::timeout(int t) { if (t > 0) timeout_ = t; } void Cddb::localCddbDirectory(const char *dir) { const char *homeDir; delete[] localCddbDirectory_; // replace ~/ by the path to the home directory as indicated by $HOME if (dir[0] == '~' && dir[1] == '/' && (homeDir = getenv("HOME")) != NULL) { localCddbDirectory_ = strdupvCC(homeDir, dir + 1, NULL); } else { localCddbDirectory_ = strdupCC(dir); } } void Cddb::appendQueryResult(const char *category, const char *diskId, const char *title, int exactMatch) { QueryResults *run, *ent; for (run = queryResults_; run != NULL && run->next != NULL; run = run->next) ; ent = new QueryResults; ent->category = strdupCC(category); ent->diskId = strdupCC(diskId); ent->title = strdupCC(title); ent->exactMatch = (exactMatch != 0) ? 1 : 0; ent->next = NULL; if (run == NULL) queryResults_ = ent; else run->next = ent; } void Cddb::clearQueryResults() { QueryResults *next; while (queryResults_ != NULL) { next = queryResults_->next; delete[] queryResults_->category; queryResults_->category = NULL; delete[] queryResults_->diskId; queryResults_->diskId = NULL; delete[] queryResults_->title; queryResults_->title = NULL; delete queryResults_; queryResults_ = next; } } void Cddb::clearCddbEntry() { int i; if (cddbEntry_ != NULL) { delete[] cddbEntry_->diskTitle; cddbEntry_->diskTitle = NULL; delete[] cddbEntry_->diskArtist; cddbEntry_->diskArtist = NULL; delete[] cddbEntry_->diskExt; cddbEntry_->diskExt = NULL; for (i = 0; i < cddbEntry_->ntracks; i++) { delete[] cddbEntry_->trackTitles[i]; cddbEntry_->trackTitles[i] = NULL; delete[] cddbEntry_->trackExt[i]; cddbEntry_->trackExt[i] = NULL; } delete[] cddbEntry_->trackTitles; cddbEntry_->trackTitles = NULL; delete[] cddbEntry_->trackExt; cddbEntry_->trackExt = NULL; delete cddbEntry_; cddbEntry_ = NULL; } } /* Appends a CDDB server name to the server list. Format of server strings: * * connect to , default cddbp port, use cddbp protocol * * : * connect to , port , use cddbp protocol * * : * connect to , default http port, use http protocol, * url: * * :: * connect to , port , use http protocol, url: * * ::: * connect to , default http port, use http protocol, * url: http://:/ * * :::: * connect to , port , use http protocol, * url: http://:/ */ void Cddb::appendServer(const char *s) { ServerList *run, *ent; char *name; char *port; char *httpCgiBin = NULL; char *httpProxyServer = NULL; char *httpProxyPort = NULL; unsigned short portNr = CDDB_DEFAULT_PORT_CDDBP; unsigned short httpProxyPortNr = CDDB_DEFAULT_PORT_HTTP; if (s == NULL || *s == 0) return; name = strdupCC(s); if ((port = strchr(name, ':')) != NULL) { *port = 0; port++; if (!isdigit(*port)) { httpCgiBin = port; port = NULL; portNr = CDDB_DEFAULT_PORT_HTTP; } else { if ((httpCgiBin = strchr(port, ':')) != NULL) { *httpCgiBin = 0; httpCgiBin++; } } if (httpCgiBin != NULL && (httpProxyServer = strchr(httpCgiBin, ':')) != NULL) { *httpProxyServer = 0; httpProxyServer++; } if (httpProxyServer != NULL && (httpProxyPort = strchr(httpProxyServer, ':')) != NULL) { *httpProxyPort = 0; httpProxyPort++; } } for (run = serverList_; run != NULL && run->next != NULL; run = run->next) { if (strcmp(run->server, name) == 0) { delete[] name; return; } } if (run != NULL && strcmp(run->server, s) == 0) { delete[] name; return; } if (port != NULL) portNr = (unsigned short)strtoul(port, NULL, 0); if (httpProxyPort != NULL) httpProxyPortNr = (unsigned short)strtoul(httpProxyPort, NULL, 0); ent = new ServerList; ent->server = name; ent->port = portNr; ent->httpCgiBin = (httpCgiBin != NULL) ? strdupCC(httpCgiBin) : NULL; if (httpProxyServer != NULL) { ent->httpProxyServer = strdupCC(httpProxyServer); ent->httpProxyPort = httpProxyPortNr; } else { ent->httpProxyServer = NULL; ent->httpProxyPort = 0; } ent->next = NULL; if (run == NULL) serverList_ = ent; else run->next = ent; } /* Tries to connect to a CDDB server. If no server was previously connected * (selectedServer_ == NULL) all servers from the server list will be tested * and the first successful connected server will be taken. * Return: 0: OK * 1: could not connect to any server */ int Cddb::openConnection() { ServerList *run; struct hostent *hostEnt; struct sockaddr_in sockAddr; const char *server; unsigned short port; struct sigaction newAlarmHandler; struct sigaction oldAlarmHandler; #ifndef HAVE_INET_ATON long inetAddr; #endif if (fd_ >= 0) // already connected return 0; memset(&newAlarmHandler, 0, sizeof(newAlarmHandler)); sigemptyset(&(newAlarmHandler.sa_mask)); #ifdef UNIXWARE newAlarmHandler.sa_handler = (void (*)()) alarmHandler; #else newAlarmHandler.sa_handler = alarmHandler; #endif if (sigaction(SIGALRM, &newAlarmHandler, &oldAlarmHandler) != 0) { message(-2, "CDDB: Cannot install alarm signal handler: %s", strerror(errno)); return 1; } alarm(0); for (run = (selectedServer_ != NULL) ? selectedServer_ : serverList_; run != NULL; run = (selectedServer_ != NULL) ? (ServerList*)0 : run->next) { server = run->server; port = run->port; if (run->httpCgiBin != NULL) { if (run->httpProxyServer != NULL) { server = run->httpProxyServer; port = run->httpProxyPort; message(1, "CDDB: Connecting to http://%s:%u%s via proxy %s:%u ...", run->server, run->port, run->httpCgiBin, server, port); } else { message(1, "CDDB: Connecting to http://%s:%u%s ...", server, port, run->httpCgiBin); } } else { message(1, "CDDB: Connecting to cddbp://%s:%u ...", server, port); } #ifdef HAVE_INET_ATON if (!inet_aton(server, &sockAddr.sin_addr)) { #else if ((inetAddr = (long)inet_addr(server)) == -1) { #endif if ((hostEnt = gethostbyname(server)) == NULL || hostEnt->h_addrtype != AF_INET) { alarm(0); message(-1, "CDDB: Cannot resolve hostname '%s' - skipping.", server); continue; } else { memcpy((char*) &sockAddr.sin_addr, hostEnt->h_addr, hostEnt->h_length); } } #ifndef HAVE_INET_ATON else { memcpy((char*)&sockAddr.sin_addr, (char*)&inetAddr, sizeof(inetAddr)); } #endif message(4, "CDDB: Hostname: %s -> IP: %s", server, inet_ntoa(sockAddr.sin_addr)); if ((fd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) { message(-2, "CDDB: Cannot create socket: %s", strerror(errno)); goto fail; } sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(port); alarm(timeout_); if (connect(fd_, (struct sockaddr*)&sockAddr, sizeof(sockAddr)) == 0) { alarm(0); message(1, "CDDB: Ok."); selectedServer_ = run; break; } else { alarm(0); message(-1, "CDDB: Failed to connect to '%s:%u: %s", server, port, strerror(errno)); closeConnection(); } } fail: connected_ = 0; alarm(0); if (sigaction(SIGALRM, &oldAlarmHandler, NULL) != 0) { message(-1, "CDDB: Cannot restore alarm signal handler: %s", strerror(errno)); } if (fd_ < 0) return 1; return 0; } /* Closes connection. */ void Cddb::closeConnection() { if (fd_ >= 0) { close(fd_); fd_ = -1; connected_ = 0; } } /* Create some strings that are used for all communications via the http * protocol. */ void Cddb::setupHttpData(const char *userName, const char *hostName, const char *clientName, const char *version) { delete[] httpCmd_; httpCmd_ = strdupvCC("&hello=", userName, "+", hostName, "+", clientName, "+", version, "&proto=1", NULL); delete[] httpData_; httpData_ = strdupvCC("User-Agent: ", clientName, "/", version, "\r\n", "Accept: text/plain\r\n", NULL); } /* Tries to connect to a server of the internal server list and performs the * the client-server handshake. * Return: 0: OK * 1: could not connect to any server * 2: handshake failed */ int Cddb::connectDb(const char *userName, const char *hostName, const char *clientName, const char *version) { int code[3]; const char *response; const char *cmdArgs[6]; if (connected_) return 0; if (openConnection() != 0) return 1; if (selectedServer_->httpCgiBin != NULL) { httpMode_ = 1; setupHttpData(userName, hostName, clientName, version); return 0; } response = getServerResponse(code); if (response == NULL) { message(-2, "CDDB: EOF while waiting for server greeting."); closeConnection(); return 2; } if (code[0] != 2) { message(-2, "CDDB: Connection to server denied: %s", response); return 2; } message(4, "CDDB: Server greeting: %s", response); connected_ = 1; cmdArgs[0] = "cddb"; cmdArgs[1] = "hello"; cmdArgs[2] = userName; cmdArgs[3] = hostName; cmdArgs[4] = clientName; cmdArgs[5] = version; if (sendCommand(6, cmdArgs) != 0) { message(-2, "CDDB: Failed to send handshake command."); return 2; } response = getServerResponse(code); if (response == NULL) { message(-2, "CDDB: EOF while waiting for server handshake response."); return 2; } if (code[0] != 2) { message(-2, "CDDB: Server handshake failed: %s", response); return 2; } message(4, "CDDB: Handshake response: %s", response); return 0; } /* Print query for current toc */ void Cddb::printDbQuery() { const char *cddbId; int ntracks; const Track *t; Msf start, end; long diskLength; ntracks = toc_->nofTracks(); cddbId = calcCddbId(); printf("%s ", cddbId); printf("%d ", ntracks); TrackIterator itr(toc_); for (t = itr.first(start, end); t != NULL; t = itr.next(start, end)) { long trackStart = start.lba() + 150; printf("%ld ", trackStart); } diskLength = toc_->length().min() * 60 + toc_->length().sec() + 2; printf("%ld\n", diskLength); } bool Cddb::printDbEntry() { if (!cddbEntry_) return false; if (cddbEntry_->diskArtist) printf("Artist: %s\n", cddbEntry_->diskArtist); if (cddbEntry_->diskTitle) printf("Title: %s\n", cddbEntry_->diskTitle); if (cddbEntry_->diskExt) printf("Ext: %s\n", cddbEntry_->diskExt); for (int i = 0; i < cddbEntry_->ntracks; i++) { printf("Track %02d: %s\n", i+1, cddbEntry_->trackTitles[i]); if (cddbEntry_->trackExt && cddbEntry_->trackExt[i]) printf("Trach %02d ext: %s\n", i+1, cddbEntry_->trackExt[i]); } return true; } /* Queries for entries that match the current 'toc_'. * 'results' will be filled with a list of matching diskIds/category/title * triples. 'results' will be NULL if no matching entry is found. * Return: 0: OK * 1: communication error occured */ int Cddb::queryDb(QueryResults **results) { const char *cddbId; const char **args; const char *resp; char qtitle[CDDB_MAX_LINE_LEN]; char qcategory[CDDB_MAX_LINE_LEN]; char qdiskId[CDDB_MAX_LINE_LEN]; char respBuf[CDDB_MAX_LINE_LEN]; char *buf; int code[3]; int err = 0; int nargs; int arg, i; int ntracks; const Track *t; Msf start, end; long diskLength; // clear previous results clearQueryResults(); if (httpMode_) { if (openConnection() != 0) return 1; } ntracks = toc_->nofTracks(); nargs = ntracks + 5; args = new const char*[nargs]; arg = 0; args[arg++] = "cddb"; args[arg++] = "query"; cddbId = calcCddbId(); args[arg++] = cddbId; buf = new char[20]; sprintf(buf, "%d", ntracks); args[arg++] = buf; TrackIterator itr(toc_); for (t = itr.first(start, end); t != NULL; t = itr.next(start, end)) { long trackStart = start.lba() + 150; buf = new char[20]; sprintf(buf, "%ld", trackStart); args[arg++] = buf; } buf = new char[20]; diskLength = toc_->length().min() * 60 + toc_->length().sec() + 2; sprintf(buf, "%ld", diskLength); args[arg++] = buf; if (sendCommand(nargs, args) != 0) { message(-2, "CDDB: Failed to send QUERY command."); err = 1; goto fail; } if ((resp = getServerResponse(code)) == NULL) { message(-2, "CDDB: EOF while waiting for QUERY response."); err = 1; goto fail; } message(4, "CDDB: QUERY response: %s", resp); if (code[0] != 2) { message(-2, "CDDB: QUERY failed: %s", resp); err = 1; goto fail; } else { if (code[2] == 0) { // found exact match strcpy(respBuf, resp + 3); if (parseQueryResult(respBuf, qcategory, qdiskId, qtitle)) { appendQueryResult(qcategory, qdiskId, qtitle, 1); } else { message(-2, "CDDB: Received invalid QUERY response: %s", resp); err = 1; goto fail; } } else if(code[2] == 1) { // found inexact matches while ((resp = readLine()) != NULL && strcmp(resp, ".") != 0) { strcpy(respBuf, resp); message(4, "CDDB: Query data: %s", resp); if (parseQueryResult(respBuf, qcategory, qdiskId, qtitle)) { appendQueryResult(qcategory, qdiskId, qtitle, 0); } else { message(-2, "CDDB: Received invalid QUERY data: %s", resp); err = 1; goto fail; } } if (resp == NULL) { message(-2, "CDDB: EOF while reading QUERY data."); err = 1; goto fail; } } else { // found no match } } fail: if (httpMode_) closeConnection(); for (i = 3; i < arg; i++) delete[] args[i]; delete[] args; *results = queryResults_; return err; } /* Reads CDDB entry for specified category and disk id. 'entry' will be * set to the stucture containing the record data. * Return: 0: OK * 1: communication error or could not retrieve CDDB entry */ int Cddb::readDb(const char *category, const char *diskId, CddbEntry **entry) { int code[3]; const char *args[4]; const char *resp; int localRecordFd = -1; clearCddbEntry(); if (httpMode_) { if (openConnection() != 0) return 1; } args[0] = "cddb"; args[1] = "read"; args[2] = category; args[3] = diskId; if (sendCommand(4, args) != 0) { message(-2, "CDDB: Failed to send READ command."); goto fail; } if ((resp = getServerResponse(code)) == NULL) { message(-2, "CDDB: EOF while waiting for READ response."); goto fail; } message(4, "CDDB: READ response: %s", resp); if (code[0] == 2) { if ((localRecordFd = createLocalCddbFile(category, diskId)) == -2) { message(-1, "Existing local CDDB record for %s/%s will not be overwritten.", category, diskId); } if (readDbEntry(localRecordFd) != 0) { message(-2, "CDDB: Received invalid database entry."); goto fail; } } else { message(-2, "CDDB: READ failed: %s", resp); goto fail; } *entry = cddbEntry_; if (httpMode_) closeConnection(); if (localRecordFd >= 0) close(localRecordFd); return 0; fail: if (httpMode_) closeConnection(); if (localRecordFd >= 0) close(localRecordFd); *entry = NULL; return 1; } /* Shuts down the connection to the CDDB server. */ void Cddb::shutdown() { const char *resp; const char *args[1]; int code[3]; if (fd_ < 0) return; if (!connected_) { closeConnection(); return; } args[0] = "quit"; if (sendCommand(1, args) == 0) { if ((resp = getServerResponse(code)) == NULL) { message(-1, "CDDB: EOF while waiting for QUIT response."); } else { message(4, "CDDB: QUIT response: %s", resp); } } else { message(-1, "CDDB: Failed to send QUIT command."); } closeConnection(); } /* Filter characters of given string so that it is suitable as CD-TEXT data. */ static char *cdTextFilter(char *s) { char *p = s; while (*p != 0) { if (*p == '\n' || *p == '\t') *p = ' '; p++; } return s; } /* Adds the data of the retrieved CDDB record stored in 'cddbEntry_' to * the given toc as CD-TEXT data. * Return: 1: CD-TEXT data was added * 0: toc was not modified */ int Cddb::addAsCdText(Toc *toc) { int havePerformer = 0; int haveTitle = 0; int haveMessage = 0; int trun; CdTextItem *item; if (cddbEntry_ == NULL) return 0; if (cddbEntry_->diskTitle != NULL) haveTitle = 1; if (cddbEntry_->diskArtist != NULL) havePerformer = 1; if (cddbEntry_->diskExt != NULL) haveMessage = 1; for (trun = 0; trun < cddbEntry_->ntracks; trun++) { if (cddbEntry_->trackTitles[trun] != NULL) haveTitle = 1; if (cddbEntry_->trackExt[trun] != NULL) haveMessage = 1; } if (!haveTitle && !havePerformer && !haveMessage) return 0; toc->cdTextLanguage(0, 9); if (haveTitle) { item = new CdTextItem(CdTextItem::CDTEXT_TITLE, 0, (cddbEntry_->diskTitle != NULL) ? cdTextFilter(cddbEntry_->diskTitle) : ""); toc->addCdTextItem(0, item); } if (havePerformer) { item = new CdTextItem(CdTextItem::CDTEXT_PERFORMER, 0, (cddbEntry_->diskArtist != NULL) ? cdTextFilter(cddbEntry_->diskArtist) : ""); toc->addCdTextItem(0, item); } if (haveMessage) { item = new CdTextItem(CdTextItem::CDTEXT_MESSAGE, 0, (cddbEntry_->diskExt != NULL) ? cdTextFilter(cddbEntry_->diskExt) : ""); toc->addCdTextItem(0, item); } for (trun = 0; trun < toc->nofTracks(); trun++) { if (haveTitle) { item = new CdTextItem(CdTextItem::CDTEXT_TITLE, 0, (trun < cddbEntry_->ntracks && cddbEntry_->trackTitles[trun] != NULL) ? cdTextFilter(cddbEntry_->trackTitles[trun]) : ""); toc->addCdTextItem(trun + 1, item); } if (havePerformer) { item = new CdTextItem(CdTextItem::CDTEXT_PERFORMER, 0, (cddbEntry_->diskArtist != NULL) ? cdTextFilter(cddbEntry_->diskArtist) : ""); toc->addCdTextItem(trun + 1, item); } if (haveMessage) { item = new CdTextItem(CdTextItem::CDTEXT_MESSAGE, 0, (trun < cddbEntry_->ntracks && cddbEntry_->trackExt[trun] != NULL) ? cdTextFilter(cddbEntry_->trackExt[trun]) : ""); toc->addCdTextItem(trun + 1, item); } } return 1; } /* Reads a line (until '\n') from 'fd_'. Checks for timeouts. */ const char *Cddb::readLine() { static char buf[CDDB_MAX_LINE_LEN]; int pos = 0; struct timeval tv; fd_set readFds; int ret; char *s; int characterRead = 0; while (pos < CDDB_MAX_LINE_LEN) { FD_ZERO(&readFds); FD_SET(fd_, &readFds); tv.tv_sec = timeout_; tv.tv_usec = 0; ret = select(fd_ + 1, &readFds, NULL, NULL, &tv); if (ret == 0) { message(-2, "CDDB: Timeout while reading data."); return NULL; } if (ret < 0) { message(-2, "CDDB: Error while waiting for data: %s", strerror(errno)); return NULL; } ret = read(fd_, &(buf[pos]), 1); if (ret == 0) { // end of file break; } if (ret < 0) { message(-2, "CDDB: Error while reading data: %s", strerror(errno)); return NULL; } characterRead = 1; if (buf[pos] == '\n') break; pos++; } if (pos >= CDDB_MAX_LINE_LEN) buf[CDDB_MAX_LINE_LEN - 1] = 0; else buf[pos] = 0; if (buf[0] == 0 && !characterRead) { // end of file return NULL; } // skip leading blanks for (s = buf; *s != 0 && isspace(*s); s++) ; // skip trailing blanks for (pos = strlen(s) - 1; pos >= 0 && isspace(s[pos]); pos--) s[pos] = 0; message(5, "CDDB: Data read: %s", s); return s; } /* Checks if 'line' contains a cddb server status and sets 'code' to the * server code. * Return: 0: 'line' is not a cddb server status line * 1: 'line' is a cddb server status line, 'code' contains valid data */ static int getCode(const char *line, int code[3]) { if (isdigit(line[0]) && isdigit(line[1]) && isdigit(line[2]) && isspace(line[3])) { code[0] = line[0] - '0'; code[1] = line[1] - '0'; code[2] = line[2] - '0'; return 1; } return 0; } /* Reads lines from 'fd_' until a valid server status line is encountered. * 'code' is set to the server status code on success. * Return: server status line or 'NULL' on timeout or communication error */ const char *Cddb::getServerResponse(int code[3]) { const char *line; while ((line = readLine()) != NULL && !getCode(line, code)) ; return line; } /* Sends command in 'args' to 'fd_'. cdbbp and http protocols are handled. * Return: 0: OK * 1: communication error occured. */ int Cddb::sendCommand(int nargs, const char *args[]) { char portBuf[20]; int len = 0; int err = 0; char *cmd, *p; char *httpCmd = NULL; int run, ret; struct timeval tv; fd_set writeFds; // build command line for (run = 0; run < nargs; run++) len += strlen(args[run]) + 1; cmd = new char[len + 1]; *cmd = 0; for (run = 0; run < nargs; run++) { strcat(cmd, args[run]); if (run != nargs - 1) { if (httpMode_) strcat(cmd, "+"); else strcat(cmd, " "); } } if (httpMode_) { if (selectedServer_->httpProxyServer != NULL) { sprintf(portBuf, ":%u", selectedServer_->port); httpCmd = strdupvCC("GET http://", selectedServer_->server, portBuf, selectedServer_->httpCgiBin, "?cmd=", cmd, httpCmd_, " HTTP/1.0\r\n", "Host: ", selectedServer_->server, "\r\n", httpData_, "\r\n", NULL); } else { httpCmd = strdupvCC("GET ", selectedServer_->httpCgiBin, "?cmd=", cmd, httpCmd_, " HTTP/1.0\r\n", "Host: ", selectedServer_->server, "\r\n", httpData_, "\r\n", NULL); } delete[] cmd; cmd = httpCmd; httpCmd = NULL; message(4, "CDDB: Sending command '%s'...", cmd); } else { message(4, "CDDB: Sending command '%s'...", cmd); strcat(cmd, "\n"); } len = strlen(cmd); p = cmd; while (len > 0) { FD_ZERO(&writeFds); FD_SET(fd_, &writeFds); tv.tv_sec = timeout_; tv.tv_usec = 0; ret = select(fd_ + 1, NULL, &writeFds, NULL, &tv); if (ret == 0) { message(-2, "CDDB: Timeout while sending data."); err = 1; goto fail; } if (ret < 0) { message(-2, "CDDB: Error while waiting for send: %s", strerror(errno)); err = 1; goto fail; } ret = write(fd_, p, 1); if (ret < 0) { message(-2, "CDDB: Failed to send command '%s': %s", cmd, strerror(errno)); err = 1; goto fail; } if (ret != 1) { message(-2, "CDDB: Failed to send command '%s'.", cmd); err = 1; goto fail; } len--; p++; } message(4, "CDDB: Ok."); fail: delete[] cmd; return err; } static unsigned int cddbSum(unsigned int n) { unsigned int ret; ret = 0; while (n > 0) { ret += (n % 10); n /= 10; } return ret; } const char *Cddb::calcCddbId() { const Track *t; Msf start, end; unsigned int n = 0; unsigned int o = 0; int tcount = 0; static char buf[20]; unsigned long id; TrackIterator itr(toc_); for (t = itr.first(start, end); t != NULL; t = itr.next(start, end)) { if (t->type() == TrackData::AUDIO) { n += cddbSum(start.min() * 60 + start.sec() + 2/* gap offset */); o = end.min() * 60 + end.sec(); tcount++; } } id = (n % 0xff) << 24 | o << 8 | tcount; sprintf(buf, "%08lx", id); return buf; } static void convertEscapeSequences(const char *in, char *out) { while (*in != 0) { if (*in == '\\') { switch (*(in + 1)) { case 'n': *out++ = '\n'; in++; break; case 't': *out++ = '\t'; in++; break; case '\\': *out++ = '\\'; in++; break; default: *out++ = '\\'; break; } } else { *out++ = *in; } in++; } *out = 0; } /* Retrieves the category, disk ID and title from a query response string. * The provided strings 'category', 'diskId' and 'title' must point to * existing buffers with at least the same length as 'line'. * Return: 1 if 'line' was successfully parsed, else 0 */ static int parseQueryResult(char *line, char *category, char *diskId, char *title) { char *sep = " \t"; char *p; if ((p = strtok(line, sep)) != NULL) { strcpy(category, p); if ((p = strtok(NULL, sep)) != NULL) { strcpy(diskId, p); if ((p = strtok(NULL, "")) != NULL) { // remove leading white space while (*p != 0 && isspace(*p)) p++; convertEscapeSequences(p, title); // remove newline from title string if ((p = strchr(title, '\n')) != NULL) *p = 0; return 1; } } } return 0; } /* Reads a CDDB record from 'fd_' and fills 'cddbEntry_' with the required * data. * Return: 0: OK * 1: communication error occured */ int Cddb::readDbEntry(int localRecordFd) { const char *resp; char buf[CDDB_MAX_LINE_LEN]; char *line; char *p, *s; char *val; int ntracks = toc_->nofTracks(); int i, trackNr; cddbEntry_ = new CddbEntry; cddbEntry_->diskTitle = NULL; cddbEntry_->diskArtist = NULL; cddbEntry_->diskExt = NULL; cddbEntry_->ntracks = ntracks; cddbEntry_->trackTitles = new char*[ntracks]; cddbEntry_->trackExt = new char*[ntracks]; for (i = 0; i < ntracks; i++) { cddbEntry_->trackTitles[i] = NULL; cddbEntry_->trackExt[i] = NULL; } while ((resp = readLine()) != NULL && strcmp(resp, ".") != 0) { message(4, "CDDB: READ data: %s", resp); if (localRecordFd >= 0) { // save to local CDDB record file fullWrite(localRecordFd, resp, strlen(resp)); fullWrite(localRecordFd, "\n", 1); } convertEscapeSequences(resp, buf); // remove comments //if ((p = strchr(buf, '#')) != NULL) // *p = 0; if (buf[0] == '#') buf[0] = 0; // xxam! // remove leading blanks for (line = buf; *line != 0 && isspace(*line); line++) ; if ((val = strchr(line, '=')) != NULL) { *val = 0; val++; if (strcmp(line, "DTITLE") == 0) { if (*val != 0) { if (cddbEntry_->diskArtist == NULL) { cddbEntry_->diskArtist = strdupCC(val); } else { s = strdup3CC(cddbEntry_->diskArtist, val, NULL); delete[] cddbEntry_->diskArtist; cddbEntry_->diskArtist = s; } } } else if (strcmp(line, "EXTD") == 0) { if (*val != 0) { if (cddbEntry_->diskExt == NULL) { cddbEntry_->diskExt = strdupCC(val); } else { s = strdup3CC(cddbEntry_->diskExt, val, NULL); delete[] cddbEntry_->diskExt; cddbEntry_->diskExt = s; } } } else if (strncmp(line, "TTITLE", 6) == 0) { if (*val != 0) { trackNr = atoi(line + 6); if (trackNr >= 0 && trackNr < ntracks) { if (cddbEntry_->trackTitles[trackNr] == NULL) { cddbEntry_->trackTitles[trackNr] = strdupCC(val); } else { s = strdup3CC(cddbEntry_->trackTitles[trackNr], val, NULL); delete[] cddbEntry_->trackTitles[trackNr]; cddbEntry_->trackTitles[trackNr] = s; } } } } else if (strncmp(line, "EXTT", 4) == 0) { if (*val != 0) { trackNr = atoi(line + 4); if (trackNr >= 0 && trackNr < ntracks) { if (cddbEntry_->trackExt[trackNr] == NULL) { cddbEntry_->trackExt[trackNr] = strdupCC(val); } else { s = strdup3CC(cddbEntry_->trackExt[trackNr], val, NULL); delete[] cddbEntry_->trackExt[trackNr]; cddbEntry_->trackExt[trackNr] = s; } } } } } } if (resp == NULL) { message(-2, "CDDB: EOF while reading database entry."); goto fail; } if (cddbEntry_->diskArtist != NULL) { if ((p = strchr(cddbEntry_->diskArtist, '/')) != NULL) { *p = 0; // remove leading white space of disk title for (s = p + 1; *s != 0 && isspace(*s); s++) ; cddbEntry_->diskTitle = strdupCC(s); // remove trailing white space of disk artist for (p = p - 1; p >= cddbEntry_->diskArtist && isspace(*p); p--) *p = 0; } else { cddbEntry_->diskTitle = strdupCC(cddbEntry_->diskArtist); } } return 0; fail: clearCddbEntry(); return 1; } int Cddb::createLocalCddbFile(const char *category, const char *diskId) { char *categoryDir = NULL; char *recordFile = NULL; struct stat sbuf; int ret; int fd = -1; if (localCddbDirectory_ == NULL) return -1; ret = stat(localCddbDirectory_, &sbuf); if (ret != 0 && errno == ENOENT) { message(-1, "CDDB: Local CDDB directory \"%s\" does not exist.", localCddbDirectory_); return -1; } else if (ret == 0) { if (!S_ISDIR(sbuf.st_mode)) { message(-2, "CDDB: \"%s\" is not a directory.", localCddbDirectory_); return -1; } } else { message(-2, "CDDB: stat of \"%s\" failed: %s", localCddbDirectory_, strerror(errno)); return -1; } categoryDir = strdup3CC(localCddbDirectory_, "/", category); ret = stat(categoryDir, &sbuf); if (ret != 0 && errno == ENOENT) { if (mkdir(categoryDir, 0777) != 0) { message(-2, "CDDB: Cannot create directory \"%s\": %s", categoryDir, strerror(errno)); goto fail; } } else if (ret == 0) { if (!S_ISDIR(sbuf.st_mode)) { message(-2, "CDDB: \"%s\" is not a directory.", categoryDir); } } else { message(-2, "CDDB: stat of \"%s\" failed: %s", categoryDir, strerror(errno)); goto fail; } recordFile = strdup3CC(categoryDir, "/", diskId); ret = stat(recordFile, &sbuf); if (ret != 0 && errno == ENOENT) { if ((fd = open(recordFile, O_WRONLY|O_CREAT, 0666)) < 0) { message(-2, "CDDB: Cannot create CDDB record file \"%s\": %s", recordFile, strerror(errno)); fd = -1; goto fail; } } else if (ret == 0) { fd = -2; goto fail; } else { message(-2, "CDDB: stat of \"%s\" failed: %s", categoryDir, strerror(errno)); goto fail; } fail: delete[] categoryDir; delete[] recordFile; return fd; }