Refactored CDDB lookup code

CDDB data is written in R5 format with the exception of CD:key (still figuring out what to do about that)
Fixed some update notification problems with changing CDs


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@13777 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
DarkWyrm
2005-07-20 01:04:55 +00:00
parent 1a3441a4a1
commit d1ebd75641
4 changed files with 395 additions and 281 deletions
+314 -218
View File
@@ -12,6 +12,7 @@
#include <File.h>
#include <FindDirectory.h>
#include <NetAddress.h>
#include <fs_attr.h>
#include <Path.h>
#include <Query.h>
#include <Volume.h>
@@ -23,9 +24,24 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
const int kTerminatingSignal = SIGINT; // SIGCONT;
// The SCSI table of contents consists of a 4-byte header followed by 100 track
// descriptors, which are each 8 bytes. We don't really need the first 5 bytes
// of the track descriptor, so we'll just ignore them. All we really want is the
// length of each track, which happen to be the last 3 bytes of the descriptor.
typedef struct TrackRecord
{
int8 unused[5];
int8 min;
int8 sec;
int8 frame;
};
template <class InitCheckable>
void
InitCheck(InitCheckable *item)
@@ -57,8 +73,8 @@ CDDBQuery::CDDBQuery(const char *server, int32 port, bool log)
fServerName(server),
fPort(port),
fConnected(false),
fDiscID(-1),
fState(kInitial)
fState(kInitial),
fDiscID(-1)
{
}
@@ -90,40 +106,40 @@ CDDBQuery::SetToCD(const char *path)
if(result != B_OK)
return;
// Calculate the disc's CDDB ID
if (fState == kInitial)
{
GetDiscID(&toc, fDiscID, fTrackCount, fDiscLength, fFrameOffsetString, fDiscIDStr);
fDiscID = GetDiscID(&toc);
fTrackCount = GetTrackCount(&toc);
cdaudio_time time = GetDiscTime(&toc);
fDiscLength = (time.minutes * 60) + time.seconds;
fFrameOffsetString = OffsetsToString(&toc);
GetTrackTimes(&toc, fTrackTimes);
}
else
{
int32 tmpDiscID;
int32 tmpDiscLength;
int32 tmpNumTracks;
BString tmpFrameOffsetString;
BString tmpDiscIDStr;
int32 discID = GetDiscID(&toc);
GetDiscID(&toc, tmpDiscID, tmpNumTracks, tmpDiscLength, tmpFrameOffsetString,
tmpDiscIDStr);
if (fDiscID == tmpDiscID && fDiscLength == tmpDiscLength && fTrackCount == tmpNumTracks
&& tmpFrameOffsetString == fFrameOffsetString)
if (fDiscID == discID);
return;
fDiscID = tmpDiscID;
fDiscLength = tmpDiscLength;
fTrackCount = tmpNumTracks;
fFrameOffsetString = tmpFrameOffsetString;
fDiscIDStr = tmpDiscIDStr;
fTrackCount = GetTrackCount(&toc);
cdaudio_time time = GetDiscTime(&toc);
fDiscLength = (time.minutes * 60) + time.seconds;
fFrameOffsetString = OffsetsToString(&toc);
GetTrackTimes(&toc, fTrackTimes);
}
result = B_OK;
fState = kReading;
fThread = spawn_thread(&CDDBQuery::LookupBinder, "CDDBLookup", B_NORMAL_PRIORITY, this);
fThread = spawn_thread(&CDDBQuery::QueryThread, "CDDBLookup", B_NORMAL_PRIORITY, this);
if (fThread >= 0)
resume_thread(fThread);
else {
else
{
fState = kError;
result = fThread;
}
@@ -134,65 +150,6 @@ DoNothing(int)
{
}
int32
CDDBQuery::LookupBinder(void *castToThis)
{
CDDBQuery *query = (CDDBQuery *)castToThis;
BFile file;
entry_ref ref;
bool newFile = false;
try
{
signal(kTerminatingSignal, DoNothing);
if (!query->FindOrCreateContentFileForDisk(&file, &ref, query->fDiscID))
{
// new content file, read it in from the server
Connector connection(query);
query->ReadFromServer(&file);
file.Seek(0, SEEK_SET);
newFile = true;
}
query->ParseResult(&file);
query->fState = kDone;
query->fThread = -1;
query->fResult = B_OK;
if (newFile)
{
BString newTitle(query->fTitle);
int32 length = newTitle.Length();
for (int32 index = 0; index < length; index++)
{
if (newTitle[index] == '/' || newTitle[index] == ':')
newTitle[index] = '-';
}
file.Sync();
BEntry entry(&ref);
entry.Rename(newTitle.String(), false);
}
}
catch (status_t error)
{
query->fState = kError;
query->fResult = error;
// the cached file is messed up, remove it
BEntry entry(&ref);
BPath path;
entry.GetPath(&path);
printf("removing bad CD content file %s\n", path.Path());
entry.Remove();
}
return 0;
}
const char *
CDDBQuery::GetToken(const char *stream, BString &result)
{
@@ -283,42 +240,13 @@ cddb_sum(int n)
return ret;
}
struct ConvertedToc
{
int32 min;
int32 sec;
int32 frame;
};
int32
CDDBQuery::GetDiscID(const scsi_toc *toc)
{
int32 tmpDiscID;
int32 tmpDiscLength;
int32 tmpNumTracks;
BString tmpFrameOffsetString;
BString tmpDiscIDStr;
GetDiscID(toc, tmpDiscID, tmpNumTracks, tmpDiscLength, tmpFrameOffsetString,
tmpDiscIDStr);
return tmpDiscID;
}
// The SCSI TOC data has a 4-byte header and then each track record starts
TrackRecord *tocData = (TrackRecord*)&(toc->toc_data[4]);
void
CDDBQuery::GetDiscID(const scsi_toc *toc, int32 &id, int32 &numTracks,
int32 &length, BString &frameOffsetsString, BString &discIDString)
{
ConvertedToc tocData[100];
// figure out the disc ID
for (int index = 0; index < 100; index++)
{
tocData[index].min = toc->toc_data[9 + 8*index];
tocData[index].sec = toc->toc_data[10 + 8*index];
tocData[index].frame = toc->toc_data[11 + 8*index];
}
numTracks = toc->toc_data[3] - toc->toc_data[2] + 1;
int32 numTracks = toc->toc_data[3] - toc->toc_data[2] + 1;
int32 sum1 = 0;
int32 sum2 = 0;
@@ -330,39 +258,87 @@ CDDBQuery::GetDiscID(const scsi_toc *toc, int32 &id, int32 &numTracks,
sum2 += (tocData[index + 1].min * 60 + tocData[index + 1].sec)
- (tocData[index].min * 60 + tocData[index].sec);
}
id = ((sum1 % 0xff) << 24) + (sum2 << 8) + numTracks;
discIDString = "";
int32 discID = ((sum1 % 0xff) << 24) + (sum2 << 8) + numTracks;
sprintf(discIDString.LockBuffer(10), "%08lx", id);
discIDString.UnlockBuffer();
return discID;
}
// compute the total length of the CD.
length = tocData[numTracks].min * 60 + tocData[numTracks].sec;
BString
CDDBQuery::OffsetsToString(const scsi_toc *toc)
{
TrackRecord *tocData = (TrackRecord*)&(toc->toc_data[4]);
for (int index = 0; index < numTracks; index++)
frameOffsetsString << tocData[index].min * 4500 + tocData[index].sec * 75
int32 numTracks = toc->toc_data[3] - toc->toc_data[2] + 1;
BString string;
for (int index = 0; index < numTracks; index++)
{
string << tocData[index].min * 4500 + tocData[index].sec * 75
+ tocData[index].frame << ' ';
}
return string;
}
int32
CDDBQuery::GetTrackCount(const scsi_toc *toc)
{
return toc->toc_data[3] - toc->toc_data[2] + 1;
}
cdaudio_time
CDDBQuery::GetDiscTime(const scsi_toc *toc)
{
int16 trackcount = toc->toc_data[3] - toc->toc_data[2] + 1;
TrackRecord *desc = (TrackRecord*)&(toc->toc_data[4]);
cdaudio_time disc;
disc.minutes = desc[trackcount].min;
disc.seconds = desc[trackcount].sec;
return disc;
}
void
CDDBQuery::ReadFromServer(BDataIO *stream)
CDDBQuery::GetTrackTimes(const scsi_toc *toc, vector<cdaudio_time> &times)
{
// Format the query
TrackRecord *tocData = (TrackRecord*)&(toc->toc_data[4]);
int16 trackCount = toc->toc_data[3] - toc->toc_data[2] + 1;
for (int index = 0; index < trackCount; index++)
{
cdaudio_time cdtime;
cdtime.minutes = tocData[index].min;
cdtime.seconds = tocData[index].sec;
times.push_back(cdtime);
}
}
void
CDDBQuery::ReadFromServer(BString &data)
{
// This function queries the given CDDB server for the existence of the disc's data and
// saves the data to file once obtained.
// Query for the existence of the disc in the database
char idString[10];
sprintf(idString, "%08lx", fDiscID);
BString query;
query << "cddb query " << fDiscIDStr << ' ' << fTrackCount << ' '
query << "cddb query " << idString << ' ' << fTrackCount << ' '
<< fFrameOffsetString << ' ' << fDiscLength << '\n';
if (fLog)
printf(">%s", query.String());
// Send it off.
ThrowIfNotSize( fSocket.Send(query.String(), query.Length()) );
BString tmp;
ReadLine(tmp);
BString category;
BString queryDiscID(fDiscIDStr);
BString queryDiscID(idString);
if(tmp.FindFirst("200") != 0)
{
@@ -404,54 +380,22 @@ CDDBQuery::ReadFromServer(BDataIO *stream)
if (!category.Length())
category = "misc";
// Query for the disc's data - artist, album, tracks, etc.
query = "";
query << "cddb read " << category << ' ' << queryDiscID << '\n' ;
ThrowIfNotSize( fSocket.Send(query.String(), query.Length()) );
for (;;)
while(true)
{
BString tmp;
ReadLine(tmp);
tmp += '\n';
ThrowIfNotSize( stream->Write(tmp.String(), tmp.Length()) );
if (tmp == "." || tmp == ".\n")
break;
}
fState = kDone;
}
void
CDDBQuery::ParseResult(BDataIO *source)
{
fTitle = "";
fTrackNames.clear();
for (;;)
{
BString tmp;
ReadLine(source, tmp);
if (tmp == ".")
break;
if (tmp == "")
throw (status_t)B_ERROR;
if (tmp.FindFirst("DTITLE=") == 0)
fTitle = tmp.String() + sizeof("DTITLE");
else
if (tmp.FindFirst("TTITLE") == 0)
{
int32 afterIndex = tmp.FindFirst('=');
if (afterIndex > 0)
{
BString trackName(tmp.String() + afterIndex + 1);
fTrackNames.push_back(trackName);
}
}
data << tmp << '\n';
}
fState = kDone;
}
void
CDDBQuery::Connect()
{
@@ -497,35 +441,6 @@ CDDBQuery::ReadLine(BString &buffer)
printf("<%s\n", buffer.String());
}
void
CDDBQuery::ReadLine(BDataIO *stream, BString &buffer)
{
// this is super-lame, should use better buffering
// ToDo:
// redo using LockBuffer, growing the buffer by 2k and reading into it
buffer = "";
char ch;
for (;;)
{
ssize_t result = stream->Read(&ch, 1);
// check for read error
if (result < 0)
throw (status_t)result;
if (result == 0)
break;
if (ch >= ' ')
buffer += ch;
if (ch == '\n')
break;
}
}
void
CDDBQuery::IdentifySelf()
{
@@ -548,10 +463,14 @@ CDDBQuery::IdentifySelf()
}
bool
CDDBQuery::FindOrCreateContentFileForDisk(BFile *file, entry_ref *fileRef, int32 discID)
CDDBQuery::OpenContentFile(const int32 &discID)
{
// Makes sure that the lookup has a valid file to work with for the CD content.
// Returns true if there is an existing file, false if a lookup is required.
BFile file;
BString predicate;
predicate << "cddb:discID == " << discID;
predicate << "CD:key == " << discID;
entry_ref ref;
BVolumeRoster roster;
@@ -564,7 +483,7 @@ CDDBQuery::FindOrCreateContentFileForDisk(BFile *file, entry_ref *fileRef, int32
continue;
// make sure the volume we are looking at is indexed right
fs_create_index(volume.Device(), "cddb:discID", B_INT32_TYPE, 0);
fs_create_index(volume.Device(), "CD:key", B_INT32_TYPE, 0);
BQuery query;
query.SetVolume(&volume);
@@ -572,33 +491,210 @@ CDDBQuery::FindOrCreateContentFileForDisk(BFile *file, entry_ref *fileRef, int32
if (query.Fetch() != B_OK)
continue;
while (query.GetNextRef(&ref) == B_OK)
if(query.GetNextRef(&ref) == B_OK)
{
// we have one already, return
file->SetTo(&ref, O_RDONLY);
*fileRef = ref;
file.SetTo(&ref, B_READ_ONLY);
break;
}
}
if(file.InitCheck() == B_NO_INIT)
return false;
// Getting this far means that we have a file. Now we parse the data in it and assign it
// to the appropriate members of the object
attr_info info;
if(file.GetAttrInfo("CD:tracks",&info)==B_OK && info.size > 0)
{
char trackdata[info.size+2];
if(file.ReadAttr("CD:tracks",B_STRING_TYPE,0,trackdata,info.size)>0)
{
trackdata[info.size] = 0;
BString tmp = GetLineFromString(trackdata);
char *index;
fArtist = tmp;
fArtist.Truncate(fArtist.FindFirst(" - "));
fTitle = strchr(tmp.String(),'-') + 2;
index = strchr(trackdata,'\n') + 1;
while(*index)
{
tmp = GetLineFromString(index);
fTrackNames.push_back(tmp);
index = strchr(index,'\n') + 1;
}
return true;
}
}
BPath path;
ThrowOnError( find_directory(B_USER_DIRECTORY, &path, true) );
path.Append("cd");
ThrowOnError( create_directory(path.Path(), 0755) );
BDirectory dir(path.Path());
BString name("CDContent");
for (int32 index = 0; ;index++)
{
if (dir.CreateFile(name.String(), file, true) != B_FILE_EXISTS)
{
BEntry entry(&dir, name.String());
entry.GetRef(fileRef);
file->WriteAttr("cddb:discID", B_INT32_TYPE, 0, &discID, sizeof(int32));
break;
}
name = "CDContent";
name << index;
}
// If we got this far, it means the file is corrupted, so nuke it and get a new one
BEntry entry(&ref);
entry.Remove();
return false;
}
int32
CDDBQuery::QueryThread(void *owner)
{
CDDBQuery *query = (CDDBQuery *)owner;
try
{
signal(kTerminatingSignal, DoNothing);
if(!query->OpenContentFile(query->fDiscID))
{
// new content file, read it in from the server
Connector connection(query);
BString data;
query->ReadFromServer(data);
query->ParseData(data);
query->WriteFile();
}
query->fState = kDone;
query->fThread = -1;
query->fResult = B_OK;
}
catch (status_t error)
{
query->fState = kError;
query->fResult = error;
}
return 0;
}
void
CDDBQuery::WriteFile(void)
{
BPath path;
if(find_directory(B_USER_DIRECTORY, &path, true)!=B_OK)
return;
path.Append("cd");
create_directory(path.Path(), 0755);
BString filename(path.Path());
filename << "/" << fArtist << " - " << fTitle;
BFile file(filename.String(), B_READ_WRITE | B_CREATE_FILE | B_FAIL_IF_EXISTS);
if(file.InitCheck() != B_OK)
return;
BString entry;
char timestring[10];
sprintf(timestring,"%.2ld:%.2ld",fDiscLength / 60, fDiscLength % 60);
entry << fArtist << " - " << fTitle << "\t" << timestring << "\n";
file.Write(entry.String(),entry.Length());
BString tracksattr(fArtist);
tracksattr << " - " << fTitle << "\n";
for(int32 i=0; i<fTrackCount; i++)
{
entry = fTrackNames[i];
sprintf(timestring,"%.2ld:%.2ld",fTrackTimes[i].minutes, fTrackTimes[i].seconds);
entry << "\t" << timestring << "\n";
file.Write(entry.String(),entry.Length());
tracksattr << fTrackNames[i] << "\n";
}
file.WriteAttr("CD:key", B_INT32_TYPE, 0, &fDiscID, sizeof(int32));
file.WriteAttr("CD:tracks", B_STRING_TYPE, 0, tracksattr.String(), tracksattr.Length());
}
void
CDDBQuery::ParseData(const BString &data)
{
// TODO: This function is dog slow, but it works. Optimize.
// Ideally, the search should be done sequentially using GetLineFromString() and strchr().
// Order: genre(category), frame offsets, disc length, artist/album, track titles
fTitle = "";
fTrackNames.clear();
int32 pos;
pos = data.FindFirst("DTITLE=");
if(pos > 0)
{
fTitle = fArtist = GetLineFromString(data.String() + sizeof("DTITLE") + pos);
pos = fArtist.FindFirst(" / ");
if(pos > 0)
fArtist.Truncate(pos);
fTitle = fTitle.String() + pos + sizeof(" / ") - 1;
}
fCategory = GetLineFromString(data.String() + 4);
pos = fCategory.FindFirst(" ");
if(pos > 0)
fCategory.Truncate(pos);
pos = data.FindFirst("Disc length: ");
if(pos > 0)
{
BString discLengthString = GetLineFromString(data.String() + pos);
pos = discLengthString.FindFirst(" seconds");
if(pos > 0)
{
discLengthString.Truncate(pos);
discLengthString = discLengthString.String() + sizeof("Disc length:");
fDiscLength = atoi(discLengthString.String());
}
else
fDiscLength = -1;
}
pos = data.FindFirst("TTITLE0=");
if(pos > 0)
{
int32 trackCount=0;
BString searchString="TTITLE0=";
while(pos > 0)
{
BString trackName = data.String() + pos + searchString.Length();
trackName.Truncate(trackName.FindFirst("\n"));
fTrackNames.push_back(trackName);
trackCount++;
searchString = "TTITLE";
searchString << trackCount << "=";
pos = data.FindFirst(searchString.String(),pos);
}
fTrackCount = trackCount;
}
}
BString
CDDBQuery::GetLineFromString(const char *string)
{
if(!string)
return NULL;
BString out(string);
int32 linefeed = out.FindFirst("\n");
if(linefeed > 0)
out.Truncate(linefeed);
return out;
}
+54 -47
View File
@@ -5,37 +5,40 @@
#include <String.h>
#include <scsi.h>
#include <vector>
#include "CDAudioDevice.h"
// based on Jukebox by Chip Paul
class CDDBQuery
{
public:
CDDBQuery(const char *server, int32 port = 888, bool log = false);
void SetToSite(const char *server, int32 port);
void GetSites(bool (*)(const char *site, int port, const char *latitude,
const char *longitude, const char *description,
void *state), void *);
CDDBQuery(const char *server, int32 port = 888,
bool log = false);
void SetToSite(const char *server, int32 port);
void GetSites(bool (*)(const char *site, int port,
const char *latitude, const char *longitude,
const char *description, void *state), void *);
void SetToCD(const char *devicepath);
bool GetTitles(BString *title, vector<BString> *tracks,
bigtime_t timeout);
void SetToCD(const char *devicepath);
bool GetTitles(BString *title, vector<BString> *tracks,
bigtime_t timeout);
bool Ready() const { return fState == kDone; }
int32 CurrentDiscID() const { return fDiscID; }
bool Ready() const { return fState == kDone; }
int32 CurrentDiscID() const { return fDiscID; }
static int32 GetDiscID(const scsi_toc *);
private:
static void GetDiscID(const scsi_toc *, int32 &id, int32 &numTracks,
int32 &length, BString &tmpFrameOffsetString,
BString &discIDString);
void Connect();
void Disconnect();
bool IsConnected() const;
static int32 GetDiscID(const scsi_toc *);
static int32 GetTrackCount(const scsi_toc *);
static cdaudio_time GetDiscTime(const scsi_toc *);
static void GetTrackTimes(const scsi_toc *, vector<cdaudio_time> &times);
static BString OffsetsToString(const scsi_toc *);
static int32 LookupBinder(void *);
// TODO: make private again once finished with testing
//private:
void Connect();
void Disconnect();
bool IsConnected() const;
static int32 QueryThread(void *);
class Connector
{
@@ -70,39 +73,43 @@ private:
kError
};
bool FindOrCreateContentFileForDisk(BFile *file, entry_ref *ref,
int32 discID);
bool OpenContentFile(const int32 &discID);
void ReadFromServer(BDataIO *);
void ParseResult(BDataIO *);
void ReadFromServer(BString &data);
void ParseData(const BString &data);
void WriteFile(void);
BString GetLineFromString(const char *string);
void ReadLine(BString &);
void IdentifySelf();
void ReadLine(BString &);
static void ReadLine(BDataIO *, BString &);
void IdentifySelf();
const char * GetToken(const char *, BString &);
const char * GetToken(const char *, BString &);
bool fLog;
bool fLog;
// connection description
BString fServerName;
BNetEndpoint fSocket;
int32 fPort;
bool fConnected;
// state data
BString fServerName;
BNetEndpoint fSocket;
int32 fPort;
bool fConnected;
// disc identification
int32 fDiscID;
int32 fDiscLength;
int32 fTrackCount;
BString fFrameOffsetString;
BString fDiscIDStr;
thread_id fThread;
State fState;
status_t fResult;
thread_id fThread;
State fState;
BString fTitle;
vector<BString> fTrackNames;
status_t fResult;
// disc information
BString fArtist;
BString fTitle;
BString fCategory;
int32 fDiscID;
int32 fDiscLength;
vector<BString> fTrackNames;
int32 fTrackCount;
BString fFrameOffsetString;
vector<cdaudio_time> fTrackTimes;
};
#endif
+25 -15
View File
@@ -58,20 +58,28 @@ bool
PlayState::UpdateState(void)
{
CDState state = sCDDevice.GetState();
if(state == kStopped && fEngine->GetState() == kPlaying)
if(state == kStopped)
{
// this means we have come to the end of a song, but probably not
// the last song in the playlist
int16 next = sPlayList.GetNextTrack();
if(next > 0)
if(fEngine->GetState() == kPlaying)
{
sCDDevice.Play(next);
// this means we have come to the end of a song, but probably not
// the last song in the playlist
int16 next = sPlayList.GetNextTrack();
if(next > 0)
{
sCDDevice.Play(next);
return CurrentState(kPlaying);
}
}
else
{
sPlayList.SetTrackCount(sCDDevice.CountTracks());
sPlayList.SetStartingTrack(sCDDevice.GetTrack());
return CurrentState(kPlaying);
}
}
/* if(state == kPlaying && fEngine->GetState() == kStopped)
if(state == kPlaying && fEngine->GetState() == kStopped)
{
// This happens when the CD drive is started by R5's player
// or this app is started while the drive is playing. We should
@@ -81,7 +89,7 @@ PlayState::UpdateState(void)
sPlayList.SetStartingTrack(sCDDevice.GetTrack());
return CurrentState(kPlaying);
}
*/
return CurrentState(state);
}
@@ -120,33 +128,35 @@ TrackState::UpdateState()
// As a result, we want to make sure this hasn't happened. If it has, then we
// need to update the playlist's position.
int16 cdTrack;
int16 cdTrack, count;
if(sCDDevice.GetState() == kPlaying)
{
cdTrack = sCDDevice.GetTrack();
if(cdTrack != sPlayList.GetCurrentTrack())
sPlayList.SetCurrentTrack(cdTrack);
return CurrentState(cdTrack);
return CurrentState(cdTrack, trackCount);
}
// If we're not playing, just monitor the current track in the playlist
cdTrack = sPlayList.GetCurrentTrack();
return CurrentState(cdTrack);
count = sCDDevice.CountTracks();
return CurrentState(cdTrack,count);
}
int32
TrackState::GetNumTracks() const
{
return sCDDevice.CountTracks()+1;
return sCDDevice.CountTracks();
}
bool
TrackState::CurrentState(int32 track)
TrackState::CurrentState(int32 track, int32 count)
{
if (track != currentTrack)
if( (track != currentTrack) || (count != trackCount) )
{
currentTrack = track;
trackCount = count;
return true;
}
return false;
+2 -1
View File
@@ -72,8 +72,9 @@ public:
private:
bool UpdateState();
bool CurrentState(int32);
bool CurrentState(int32,int32);
int32 currentTrack;
int32 trackCount;
};
// this watcher sends notices to observers that are interested