* Copyright 2008-2016, 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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)
{
char localHostName[MAXHOSTNAMELEN + 1];
if (gethostname(localHostName, MAXHOSTNAMELEN + 1) == 0) {
fLocalHostName = localHostName;
} else {
fLocalHostName = kDefaultLocalHostName;
}
char* user = getenv("USER");
if (user == NULL)
fLocalUserName = "unknown";
else
fLocalUserName = user;
if (_ParseAddress(cddbServer) == B_OK)
fInitialized = true;
}
status_t
CDDBServer::Query(uint32 cddbID, const scsi_toc_toc* toc,
QueryResponseList& queryResponses)
{
if (_OpenConnection() != B_OK)
return B_ERROR;
char hexCddbId[9];
sprintf(hexCddbId, "%08" B_PRIx32, cddbID);
int32 numTracks = toc->last_track + 1 - toc->first_track;
BString cddbCommand("cddb query ");
cddbCommand << hexCddbId << " " << numTracks << " ";
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 << " ";
}
const scsi_cd_msf& lastTrack = toc->tracks[numTracks].start.time;
uint32 totalTimeInSeconds = lastTrack.minute * 60 + lastTrack.second;
cddbCommand << totalTimeInSeconds;
BString output;
status_t result = _SendCommand(cddbCommand, output);
if (result == B_OK) {
output.Remove(0, output.FindFirst("\r\n\r\n") + 4);
BString statusCode;
output.MoveInto(statusCode, 0, 3);
if (statusCode == "210" || statusCode == "211") {
if (statusCode == "211")
printf("Warning : Inexact match found.\n");
output.Remove(0, output.FindFirst("\r\n") + 2);
} else if (statusCode == "200") {
output.Remove(0, 1);
} else if (statusCode == "202") {
printf("Error : CDDB entry for id %s not found.\n", hexCddbId);
return B_ENTRY_NOT_FOUND;
} else {
if (statusCode.Trim() != "") {
printf("Error : CDDB server status code is %s.\n",
statusCode.String());
} else {
printf("Error : Could not find any status code.\n");
}
return B_ERROR;
}
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);
queryResponses.AddItem(responseData);
if (output == "" || output == ".\r\n") {
done = true;
}
}
} else {
printf("Error sending CDDB command : \"%s\".\n", cddbCommand.String());
}
_CloseConnection();
return result;
}
status_t
CDDBServer::Read(const QueryResponseData& diskData,
ReadResponseData& readResponse, bool verbose)
{
return Read(diskData.category, diskData.cddbID, diskData.artist,
readResponse, verbose);
}
status_t
CDDBServer::Read(const BString& category, const BString& cddbID,
const BString& artist, ReadResponseData& readResponse, bool verbose)
{
if (_OpenConnection() != B_OK)
return B_ERROR;
BString cddbCommand("cddb read ");
cddbCommand << category << " " << cddbID;
BString output;
status_t result = _SendCommand(cddbCommand, output);
if (result == B_OK) {
if (verbose)
puts(output);
output.Remove(0, output.FindFirst("\r\n\r\n") + 4);
BString statusCode;
output.MoveInto(statusCode, 0, 3);
if (statusCode == "210") {
output.Remove(0, output.FindFirst("\r\n") + 2);
} else {
return B_ERROR;
}
bool done = false;
while (!done) {
if (output[0] == '#') {
output.Remove(0, output.FindFirst("\r\n") + 2);
continue;
}
BString line;
output.MoveInto(line, 0, output.FindFirst("\r\n"));
output.Remove(0, 2);
BString prefix;
line.MoveInto(prefix, 0, line.FindFirst("="));
line.Remove(0, 1);
if (prefix == "DTITLE") {
BString artist;
line.MoveInto(artist, 0, line.FindFirst(" / "));
line.Remove(0, 3);
readResponse.title = line;
readResponse.artist = artist;
} else if (prefix == "DYEAR") {
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)) {
printf("Year out of range: %s\n", line.String());
year = 0;
}
if (firstInvalid == line.String()) {
printf("Invalid year: %s\n", line.String());
year = 0;
}
readResponse.year = year;
} else if (prefix == "DGENRE") {
readResponse.genre = line;
} else if (prefix.FindFirst("TTITLE") == 0) {
BString index;
prefix.MoveInto(index, 6, prefix.Length() - 6);
char* firstInvalid;
errno = 0;
uint32 track = strtoul(index.String(), &firstInvalid, 10);
if (errno != 0 || track > 99) {
printf("Track out of range: %s\n", index.String());
return B_ERROR;
}
if (firstInvalid == index.String()) {
printf("Invalid track: %s\n", index.String());
return B_ERROR;
}
BString trackArtist;
int32 pos = line.FindFirst(" / ");
if (pos >= 0 && artist.ICompare("Various") == 0) {
line.MoveInto(trackArtist, 0, pos);
line.Remove(0, 3);
} else {
trackArtist = artist;
}
TrackData* trackData = _Track(readResponse, track);
trackData->artist += trackArtist;
trackData->title += line;
}
if (output == "" || output == ".\r\n") {
done = true;
}
}
} else {
printf("Error sending CDDB command : \"%s\".\n", cddbCommand.String());
}
_CloseConnection();
return B_OK;
}
status_t
CDDBServer::_ParseAddress(const BString& cddbServer)
{
int32 pos = cddbServer.FindFirst(":");
if (pos == B_ERROR) {
fServerHostname.SetTo(cddbServer);
fServerPort = kDefaultPortNumber;
return B_OK;
} else {
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 == INT32_MAX || port == INT32_MIN))
|| (errno != 0 && port == 0)) {
return B_ERROR;
}
if (firstInvalid == portString.String()) {
return B_ERROR;
}
newCddbServer.RemoveAll(":");
fServerHostname.SetTo(newCddbServer);
fServerPort = port;
return B_OK;
}
}
return B_ERROR;
}
status_t
CDDBServer::_OpenConnection()
{
if (!fInitialized)
return B_ERROR;
if (fConnected)
return B_OK;
if (fServerAddress.InitCheck() != B_OK) {
if (fServerAddress.SetTo(fServerHostname, fServerPort) != B_OK)
return B_ERROR;
}
if (fConnection.Connect(fServerAddress) == B_OK) {
fConnected = true;
return B_OK;
}
return B_ERROR;
}
void
CDDBServer::_CloseConnection()
{
if (!fConnected)
return;
fConnection.Close();
fConnected = false;
}
status_t
CDDBServer::_SendCommand(const BString& command, BString& output)
{
if (!fConnected)
return B_ERROR;
BString fullCommand;
fullCommand << command << "&hello=" << fLocalUserName << " " <<
fLocalHostName << " cddb_lookup 1.0&proto=6";
fullCommand.ReplaceAll(" ", "+");
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, 1024) != 0) {
}
netBuffer.AppendString("");
output.SetTo((char*)netBuffer.Data(), netBuffer.Size());
return B_OK;
}
return B_ERROR;
}
TrackData*
CDDBServer::_Track(ReadResponseData& response, uint32 track) const
{
for (int32 i = 0; i < response.tracks.CountItems(); i++) {
TrackData* trackData = response.tracks.ItemAt(i);
if (trackData->trackNumber == track)
return trackData;
}
TrackData* trackData = new TrackData();
trackData->trackNumber = track;
response.tracks.AddItem(trackData);
return trackData;
}