diff --git a/src/servers/cddb_daemon/Jamfile b/src/servers/cddb_daemon/Jamfile index 8b769a4ed0..236822059d 100644 --- a/src/servers/cddb_daemon/Jamfile +++ b/src/servers/cddb_daemon/Jamfile @@ -4,5 +4,6 @@ UsePrivateHeaders drivers ; Server cddb_daemon : cddb_daemon.cpp - : be + cddb_server.cpp + : be bnetapi ; diff --git a/src/servers/cddb_daemon/cddb_daemon.cpp b/src/servers/cddb_daemon/cddb_daemon.cpp index 9d3a10c280..36f26a3771 100644 --- a/src/servers/cddb_daemon/cddb_daemon.cpp +++ b/src/servers/cddb_daemon/cddb_daemon.cpp @@ -8,12 +8,16 @@ #include "cddb_daemon.h" +#include "cddb_server.h" + #include #include #include #include +#include #include +#include #include #include @@ -31,16 +35,9 @@ CDDBDaemon::CDDBDaemon() BVolume volume; while (fVolumeRoster->GetNextVolume(&volume) == B_OK) { - scsi_toc_toc* toc = (scsi_toc_toc*)malloc(kMaxTocSize); - if (toc == NULL) + if (_Lookup(volume.Device()) != B_OK) { continue; - - uint32 cddbId; - if (_CanLookup(volume.Device(), &cddbId, toc)) { - printf("CD can be looked up. CDDB id = %lu.\n", cddbId); - // TODO(bga): Implement and enable CDDB database lookup. } - free(toc); } } @@ -62,19 +59,8 @@ CDDBDaemon::MessageReceived(BMessage* message) if (opcode == B_DEVICE_MOUNTED) { dev_t device; if (message->FindInt32("new device", &device) == B_OK) { - scsi_toc_toc* toc = - (scsi_toc_toc *)malloc(kMaxTocSize); - if (toc == NULL) + if (_Lookup(device) != B_OK) break; - - uint32 cddbId; - if (_CanLookup(device, &cddbId, toc)) { - printf("CD can be looked up. CDDB id = %lu.\n", - cddbId); - // TODO(bga): Implement and enable CDDB - // database lookup. - } - free(toc); } } } @@ -85,6 +71,69 @@ CDDBDaemon::MessageReceived(BMessage* message) } +status_t +CDDBDaemon::_Lookup(const dev_t device) +{ + scsi_toc_toc* toc = (scsi_toc_toc*)malloc(kMaxTocSize); + if (toc == NULL) + return B_NO_MEMORY; + + uint32 cddbId; + if (!_CanLookup(device, &cddbId, toc)) { + free(toc); + return B_BAD_TYPE; + } + + printf("CD can be looked up. CDDB id = %08lx.\n", cddbId); + + CDDBServer cddb_server("freedb.freedb.org:80"); + + status_t result; + + BList queryResponse; + if ((result = cddb_server.Query(cddbId, toc, &queryResponse)) != B_OK) { + free(toc); + return result; + } + + free(toc); + + QueryResponseData* diskData = _SelectResult(&queryResponse); + if (diskData == NULL) { + return B_BAD_INDEX; + } + + ReadResponseData readResponse; + if ((result = cddb_server.Read(diskData, &readResponse)) != B_OK) { + return result; + } + + if (_WriteCDData(device, diskData, &readResponse) == B_OK) { + printf("CD data saved.\n"); + } else { + printf("CD data not saved.\n" ); + } + + // Delete itens in the query response BList; + int32 count = queryResponse.CountItems(); + for (int32 i = 0; i < count; ++i) { + delete (QueryResponseData*)queryResponse.RemoveItem(0L); + } + + queryResponse.MakeEmpty(); + + // Delete itens in the track data BList in the read response data; + count = readResponse.tracks.CountItems(); + for (int32 i = 0; i < count; ++i) { + delete (TrackData*)readResponse.tracks.RemoveItem(0L); + } + + readResponse.tracks.MakeEmpty(); + + return B_OK; +} + + bool CDDBDaemon::_CanLookup(const dev_t device, uint32* cddbId, scsi_toc_toc* toc) const @@ -122,6 +171,37 @@ CDDBDaemon::_CanLookup(const dev_t device, uint32* cddbId, } +QueryResponseData* +CDDBDaemon::_SelectResult(BList* response) const +{ + // Select a single CD match from the response and return it. + // + // TODO(bga):Right now it just picks the first entry on the list but + // someday we may want to let the user choose one. + if (response->CountItems() != 0) + return (QueryResponseData*)response->ItemAt(0L); + + return NULL; +} + + +status_t +CDDBDaemon::_WriteCDData(dev_t device, QueryResponseData* diskData, + ReadResponseData* readResponse) +{ + // Rename volume. + BVolume volume(device); + + status_t result; + if ((result = volume.SetName((diskData->title).String())) != B_OK) { + printf("Can't set volume name.\n"); + return result; + } + + return B_ERROR; +} + + int main(void) { CDDBDaemon* cddbDaemon = new CDDBDaemon(); cddbDaemon->Run(); diff --git a/src/servers/cddb_daemon/cddb_daemon.h b/src/servers/cddb_daemon/cddb_daemon.h index ed61b865af..8718c7afe9 100644 --- a/src/servers/cddb_daemon/cddb_daemon.h +++ b/src/servers/cddb_daemon/cddb_daemon.h @@ -10,23 +10,32 @@ #define _CDDB_DAEMON_H #include -#include -#include #include +struct ReadResponseData; +struct QueryResponseData; + +class BList; +class BMessage; +class BVolumeRoster; + class CDDBDaemon : public BApplication { public: - CDDBDaemon(); - virtual ~CDDBDaemon(); + CDDBDaemon(); + virtual ~CDDBDaemon(); - virtual void MessageReceived(BMessage* message); + virtual void MessageReceived(BMessage* message); private: - bool _CanLookup(const dev_t device, uint32* cddbId, - scsi_toc_toc* toc) const; + status_t _Lookup(const dev_t device); + bool _CanLookup(const dev_t device, uint32* cddbId, + scsi_toc_toc* toc) const; + QueryResponseData* _SelectResult(BList* response) const; + status_t _WriteCDData(dev_t device, QueryResponseData* diskData, + ReadResponseData* readResponse); - BVolumeRoster* fVolumeRoster; + BVolumeRoster* fVolumeRoster; }; #endif // _CDDB_DAEMON_H diff --git a/src/servers/cddb_daemon/cddb_server.cpp b/src/servers/cddb_daemon/cddb_server.cpp new file mode 100644 index 0000000000..3162f75fab --- /dev/null +++ b/src/servers/cddb_daemon/cddb_server.cpp @@ -0,0 +1,376 @@ +/* + * Copyright 2008-2009, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Bruno Albuquerque, bga@bug-br.org.br + */ + +#include "cddb_server.h" + +#include +#include +#include +#include + + +static const char* kDefaultLocalHostName = "unknown"; +static const uint32 kDefaultPortNumber = 80; + +static const uint32 kFramesPerSecond = 75; +static const uint32 kFramesPerMinute = kFramesPerSecond * 60; + + +CDDBServer::CDDBServer(const BString& cddbServer): + fInitialized(false), + fConnected(false) +{ + + // Set up local host name. + char localHostName[MAXHOSTNAMELEN + 1]; + if (gethostname(localHostName, MAXHOSTNAMELEN + 1) == 0) { + fLocalHostName = localHostName; + } else { + fLocalHostName = kDefaultLocalHostName; + } + + // Set up local user name. + char* user = getenv("USER"); + if (user == NULL) { + fLocalUserName = "unknown"; + } else { + fLocalUserName = user; + } + + // Set up server address; + if (_ParseAddress(cddbServer) == B_OK) + fInitialized = true; +} + + +status_t +CDDBServer::Query(uint32 cddbId, const scsi_toc_toc* toc, BList* queryResponse) +{ + if (_OpenConnection() != B_OK) + return B_ERROR; + + // Convert CDDB id to hexadecimal format. + char hexCddbId[9]; + sprintf(hexCddbId, "%08lx", cddbId); + + // Assemble the Query command. + int32 numTracks = toc->last_track + 1 - toc->first_track; + + BString cddbCommand("cddb query "); + cddbCommand << hexCddbId << " " << numTracks << " "; + + // Add track offsets in frames. + for (int32 i = 0; i < numTracks; ++i) { + const scsi_cd_msf& start = toc->tracks[i].start.time; + + uint32 startFrameOffset = start.minute * kFramesPerMinute + + start.second * kFramesPerSecond + start.frame; + + cddbCommand << startFrameOffset << " "; + } + + // Add total disc time in seconds. Last track is lead-out. + const scsi_cd_msf& lastTrack = toc->tracks[numTracks].start.time; + uint32 totalTimeInSeconds = lastTrack.minute * 60 + lastTrack.second; + cddbCommand << totalTimeInSeconds; + + BString output; + status_t result; + result = _SendCddbCommand(cddbCommand, &output); + + if (result == B_OK) { + // Remove the header from the reply. + output.Remove(0, output.FindFirst("\r\n\r\n") + 4); + + // Check status code. + BString statusCode; + output.MoveInto(statusCode, 0, 3); + if (statusCode == "210") { + // Multiple results, remove the first line and parse the others. + output.Remove(0, output.FindFirst("\r\n") + 2); + } else if (statusCode == "200") { + // Remove the first char which is a left over space. + output.Remove(0, 1); + } else { + // Something bad happened. + return B_ERROR; + } + + // Process all entries. + bool done = false; + while (!done) { + QueryResponseData* responseData = new QueryResponseData; + + output.MoveInto(responseData->category, 0, output.FindFirst(" ")); + output.Remove(0, 1); + + output.MoveInto(responseData->cddbId, 0, output.FindFirst(" ")); + output.Remove(0, 1); + + output.MoveInto(responseData->artist, 0, output.FindFirst(" / ")); + output.Remove(0, 3); + + output.MoveInto(responseData->title, 0, output.FindFirst("\r\n")); + output.Remove(0, 2); + + queryResponse->AddItem(responseData); + + if (output == "" || output == ".\r\n") { + // All returned data was processed exit the loop. + done = true; + } + } + } else { + printf("Error sending CDDB command : \"%s\".\n", cddbCommand.String()); + } + + _CloseConnection(); + return result; +} + + +status_t +CDDBServer::Read(QueryResponseData* diskData, ReadResponseData* readResponse) +{ + if (_OpenConnection() != B_OK) + return B_ERROR; + + // Assemble the Read command. + BString cddbCommand("cddb read "); + cddbCommand << diskData->category << " " << diskData->cddbId; + + BString output; + status_t result; + result = _SendCddbCommand(cddbCommand, &output); + + if (result == B_OK) { + // Remove the header from the reply. + output.Remove(0, output.FindFirst("\r\n\r\n") + 4); + + // Check status code. + BString statusCode; + output.MoveInto(statusCode, 0, 3); + if (statusCode == "210") { + // Remove first line and parse the others. + output.Remove(0, output.FindFirst("\r\n") + 2); + } else { + // Something bad happened. + return B_ERROR; + } + + // Process all entries. + bool done = false; + while (!done) { + if (output[0] == '#') { + // Comment. Remove it. + output.Remove(0, output.FindFirst("\r\n") + 2); + continue; + } + + // Extract one line to reduce the scope of processing to it. + BString line; + output.MoveInto(line, 0, output.FindFirst("\r\n")); + output.Remove(0, 2); + + // Obtain prefix. + BString prefix; + line.MoveInto(prefix, 0, line.FindFirst("=")); + line.Remove(0, 1); + + if (prefix == "DTITLE") { + // Disk title. + BString artist; + line.MoveInto(artist, 0, line.FindFirst(" / ")); + line.Remove(0, 3); + readResponse->title = line; + readResponse->artist = artist; + } else if (prefix == "DYEAR") { + // Disk year. + char* firstInvalid; + errno = 0; + uint32 year = strtoul(line.String(), &firstInvalid, 10); + if ((errno == ERANGE && + (year == (uint32)LONG_MAX || year == (uint32)LONG_MIN)) + || (errno != 0 && year == 0)) { + // Year out of range. + printf("Year out of range: %s\n", line.String()); + return B_ERROR; + } + + if (firstInvalid == line.String()) { + printf("Invalid year: %s\n", line.String()); + return B_ERROR; + } + + readResponse->year = year; + } else if (prefix == "DGENRE") { + // Disk genre. + readResponse->genre = line; + } else if (prefix.FindFirst("TTITLE") == 0) { + // Track title. + BString index; + prefix.MoveInto(index, 6, prefix.Length() - 6); + + TrackData* trackData = new TrackData; + + char* firstInvalid; + errno = 0; + uint32 track = strtoul(index.String(), &firstInvalid, 10); + if ((errno == ERANGE && + (track == (uint32)LONG_MAX || track == (uint32)LONG_MIN)) + || (errno != 0 && track == 0)) { + // Track out of range. + printf("Track out of range: %s\n", index.String()); + delete trackData; + return B_ERROR; + } + + if (firstInvalid == index.String()) { + printf("Invalid track: %s\n", index.String()); + delete trackData; + return B_ERROR; + } + + trackData->trackNumber = track; + + int32 pos = line.FindFirst(" / " ); + if (pos != B_ERROR) { + // We have track specific artist information. + BString artist; + line.MoveInto(artist, 0, pos); + line.Remove(0, 3); + trackData->artist = artist; + } else { + trackData->artist = diskData->artist; + } + + trackData->title = line; + + (readResponse->tracks).AddItem(trackData); + } else { + printf("Unhandled or unknown prefix: %s\n", prefix.String()); + } + + if (output == "" || output == ".\r\n") { + // All returned data was processed exit the loop. + done = true; + } + } + } else { + printf("Error sending CDDB command : \"%s\".\n", cddbCommand.String()); + } + + _CloseConnection(); + return B_OK; +} + + +status_t +CDDBServer::_ParseAddress(const BString& cddbServer) +{ + // Set up server address. + int32 pos = cddbServer.FindFirst(":"); + if (pos == B_ERROR) { + // It seems we do not have the address:port format. Use hostname as-is. + fCddbServerAddr.SetTo(cddbServer.String(), kDefaultPortNumber); + if (fCddbServerAddr.InitCheck() == B_OK) + return B_OK; + } else { + // Parse address:port format. + int32 port; + BString newCddbServer(cddbServer); + BString portString; + newCddbServer.MoveInto(portString, pos + 1, + newCddbServer.CountChars() - pos + 1); + if (portString.CountChars() > 0) { + char* firstInvalid; + errno = 0; + port = strtol(portString.String(), &firstInvalid, 10); + if ((errno == ERANGE && (port == LONG_MAX || port == LONG_MIN)) + || (errno != 0 && port == 0)) { + return B_ERROR; + } + if (firstInvalid == portString.String()) { + return B_ERROR; + } + + newCddbServer.RemoveAll(":"); + fCddbServerAddr.SetTo(newCddbServer.String(), port); + if (fCddbServerAddr.InitCheck() == B_OK) + return B_OK; + } + } + + return B_ERROR; +} + + +status_t +CDDBServer::_OpenConnection() +{ + if (!fInitialized) + return B_ERROR; + + if (fConnected) + return B_OK; + + if (fConnection.Connect(fCddbServerAddr) == B_OK) { + fConnected = true; + return B_OK; + } + + return B_ERROR; +} + + +void +CDDBServer::_CloseConnection() +{ + if (!fConnected) + return; + + fConnection.Close(); + fConnected = false; +} + + +status_t +CDDBServer::_SendCddbCommand(const BString& command, BString* output) +{ + if (!fConnected) + return B_ERROR; + + // Assemble full command string. + BString fullCommand; + fullCommand << command << "&hello=" << fLocalUserName << " " << + fLocalHostName << " cddb_daemon 1.0&proto=6"; + + // Replace spaces by + signs. + fullCommand.ReplaceAll(" ", "+"); + + // And now add command header and footer. + fullCommand.Prepend("GET /~cddb/cddb.cgi?cmd="); + fullCommand << " HTTP 1.0\n\n"; + + int32 result = fConnection.Send((void*)fullCommand.String(), + fullCommand.Length()); + if (result == fullCommand.Length()) { + BNetBuffer netBuffer; + while (fConnection.Receive(netBuffer, 10) != 0) { + // Do nothing. Data is automatically appended to the NetBuffer. + } + + // AppendString automatically adds the terminating \0. + netBuffer.AppendString(""); + + output->SetTo((char*)netBuffer.Data(), netBuffer.Size()); + return B_OK; + } + + return B_ERROR; +} diff --git a/src/servers/cddb_daemon/cddb_server.h b/src/servers/cddb_daemon/cddb_server.h new file mode 100644 index 0000000000..6f72f29bb1 --- /dev/null +++ b/src/servers/cddb_daemon/cddb_server.h @@ -0,0 +1,73 @@ +/* + * Copyright 2008-2009, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Bruno Albuquerque, bga@bug-br.org.br + */ + +#ifndef _CDDB_SERVER_H +#define _CDDB_SERVER_H + +#include +#include +#include +#include + +#include + + +// CD track specific data. +struct TrackData { + uint32 trackNumber; + BString title; + BString artist; +}; + + +// Query command response. +struct QueryResponseData { + BString category; + BString cddbId; + BString artist; + BString title; +}; + + +// Read command response. +struct ReadResponseData { + BString title; + BString artist; + BString genre; + uint32 year; + BList tracks; // TrackData items. +}; + + +class CDDBServer { +public: + CDDBServer(const BString& cddbServer); + + // CDDB commands interface. + status_t Query(uint32 cddbId, const scsi_toc_toc* toc, + BList* queryResponse); + status_t Read(QueryResponseData* diskData, + ReadResponseData* readResponse); + +private: + status_t _ParseAddress(const BString& cddbServer); + + status_t _OpenConnection(); + void _CloseConnection(); + + status_t _SendCddbCommand(const BString& command, BString* output); + + BString fLocalHostName; + BString fLocalUserName; + BNetAddress fCddbServerAddr; + BNetEndpoint fConnection; + bool fInitialized; + bool fConnected; +}; + +#endif // _CDDB_SERVER_H