// Copyright 2000, Be Incorporated. All Rights Reserved.
// This file may be used under the terms of the Be Sample Code License.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <OS.h>
#include <string.h>
#include <socket.h> 
#include <netdb.h> 
#include <UTF8.h>
#include <fs_info.h>
#include <VolumeRoster.h>
#include <Volume.h>
#include <Directory.h>
#include <Path.h>
#include <File.h>

#include "DirWalker.h"

typedef struct CDFS_TOC_Entry 
{
	ulong startaddress;
	ulong length;
	ulong realtracknumber;
	char  *name;
} t_CDFS_TOC_Entry;

typedef struct CDFS_TOC
{
	long numtracks;
	t_CDFS_TOC_Entry tocentry[100];
	ulong CDDBid;
	ulong CDPlayerkey;
} t_CDFS_TOC;

const char cddbfiles[]="/boot/home/config/settings/cdda";
const char cddbfilestodo[]="/boot/home/config/settings/cdda/TODO";

/*
 if you change this to use a different service, please make sure
 sure that you comply with whatever licensing restrictions the
 owner of that service has specified.
*/
const char *cddbhostname="freedb.freedb.org";

int get_cddb_data(t_CDFS_TOC *cd);
char *GetLineFromString(char *string, char *line);
int send_cddb_command(char *command, char *answer);
int make_cddb_file(char *response,long cddbid);
void do_namechange(t_CDFS_TOC *toc);
void publish();

//#define debug(x)
#define debug(x) printf x


int main()
{
	printf("CDDB lookup daemon for cdda-fs\n");
	printf("Copyright ©2000 Be, Inc.\n");

	// need a BApplication to publish attributes for our mime-type
	BApplication *app=new BApplication("application/x-vnd.Be-cddblinkd");
	// not really related to CD lookup, but included for convenience
	publish();
	// don't need the BApplication anymore, so get it rid of it to
	// prevent cddblinkd from hanging on shutdown
	app->PostMessage(B_QUIT_REQUESTED);
	app->Run();
	delete app;

	printf("Running...\n");

	// create the folder that are needed, since the filesystem add-on itself might not create them
	mkdir(cddbfiles,S_IRWXU|S_IRWXG|S_IRWXO);
	mkdir(cddbfilestodo,S_IRWXU|S_IRWXG|S_IRWXO);

	// ugly polling mechanism
	// converting this to a nice live query is left as an exercise for the reader
	while(true)
	{
		entry_ref requestdir;
		get_ref_for_path(cddbfilestodo,&requestdir);
		DirWalker walker(requestdir);
		
		entry_ref request;
		while(walker.NextRef(&request)==B_OK)
		{
			t_CDFS_TOC toc;
			BFile file(&request,B_READ_ONLY);
			if(file.InitCheck()==B_OK)
			{
				if(sizeof(t_CDFS_TOC)==file.Read(&toc,sizeof(t_CDFS_TOC)))
				{
					if(get_cddb_data(&toc)==B_OK)
						do_namechange(&toc);
				}
			}
			BEntry(&request).Remove();
		}
		snooze(1000000);
	}
}

int get_cddb_data(t_CDFS_TOC *toc)
{
	char command[4096];
	char answer[4096];
	char cmdword[20];
	ulong totalseconds;
	int status=B_ERROR;

	sprintf(command,"cddb query %08x %d ",toc->CDDBid,toc->numtracks);
	totalseconds=0;
	for(int i=0;i<toc->numtracks;i++)
	{
		totalseconds+=(toc->tocentry[i].length);
		sprintf(cmdword,"%d ",toc->tocentry[i].startaddress);
		strcat(command,cmdword);
	}
	sprintf(cmdword,"%d",totalseconds/75);
	strcat(command,cmdword);

	debug(("Contacting %s\n",cddbhostname));
	if(send_cddb_command(command, answer)==0)
	{
		char *lp=answer;
		char line[1024];
		char arg[1024];
		char arg2[1024];

		lp=GetLineFromString(lp,line);
		sscanf(line,"%d %1000s",&status,arg);
		debug(("return code: %d\n",status));
		if(status==200)
		{
			// exact match
			sprintf(command,"cddb read %s %08x",arg,toc->CDDBid);
			if(send_cddb_command(command,answer)==0)
				status=make_cddb_file(answer,toc->CDDBid);
		}
		else if(status==211)
		{
			// inexact match, get first one
			lp=GetLineFromString(lp,line);
			sscanf(line,"%s %s",arg,arg2);
			sprintf(command,"cddb read %s %s",arg,arg2);
			if(send_cddb_command(command,answer)==0)
				status=make_cddb_file(answer,toc->CDDBid);
		}
		else
			status=B_ERROR;
	}
	return status;
}

char *GetLineFromString(char *string, char *line)
{
    strcpy(line,"");
    if(sscanf(string,"%[^\n]",line)==1)
    {
        return string+strlen(line)+1;
	}
    else
    {
    	debug(("Error reading line\n"));
		return line;
	}
}

int send_cddb_command(char *command, char *answer)
{
	strcat(command,"&hello=");
	char *user=getenv("USER");
	if(!user || strlen(user)==0)
		user="unknown";
	strcat(command,user);
	strcat(command," ");

	char localhost[MAXHOSTNAMELEN+1];
	gethostname(localhost,MAXHOSTNAMELEN);
	if(!localhost || strlen(localhost)==0)
		strcpy(localhost,"unknown");
	strcat(command,localhost);
	
	strcat(command," cdda-fs 1.0&proto=1");
	char *s=command;
	while(*s)
	{
		if(*s==' ')
			*s='+';
		s++;
	}
	char GET[2048];
	sprintf(GET,"GET /~cddb/cddb.cgi?cmd=%s\n\n",command);

	struct in_addr **pptr;
	struct hostent *hp; 
	struct sockaddr_in dest;

	hp = gethostbyname(cddbhostname);

	if(hp==NULL)
	{
		debug(("couldn't find host\n"));
		return -1;
	}

	pptr = (struct in_addr**)hp->h_addr_list;
	
	// Server side socket 
	memset(&dest,0,sizeof(dest)); 
	dest.sin_family=AF_INET; 
	memcpy(&dest.sin_addr, *pptr, sizeof(struct in_addr)); 
	dest.sin_port=htons(80); 
	
	int sd = socket(AF_INET, SOCK_STREAM, 0);
	if(sd<0)
	{
		debug(("couldn't create socket\n"));
		return -1;
	}

	if(connect(sd, (sockaddr*)&dest, sizeof(dest))>=0)
	{
		debug(("connected\n"));
		send(sd, GET, strlen(GET), 0); 
		int32 bytes=recv(sd, answer, 4096, 0);
		if(bytes)
			answer[bytes]='\0';
		else
			sprintf(answer,"no answer");
		debug(("response: %s\n",answer));
		closesocket(sd);
		return 0;
	}
	else
	{
		debug(("couldn't connect\n"));
		closesocket(sd);
		return -1;
	}
}

int make_cddb_file(char *response,long cddbid)
{
	char *lp=response;
	char line[1024];
	char name[1000];
	char value[1000];
	int status=B_ERROR;

	lp=GetLineFromString(lp,line);
	int code;
	if(sscanf(line,"%d",&code)==1)
	{
		if(code==210)
		{
			char cddbfile[100];
			sprintf(cddbfile,"%s/%08x",cddbfiles,cddbid);
			FILE *file=fopen(cddbfile,"w");
			if(file)
			{
				while(strlen(lp))
				{
					lp=GetLineFromString(lp,line);
					debug(("line: %s\n",line));
					if(sscanf(line,"%[^=]=%[^\r\n]",name,value)==2)
					{
						if((strcmp("DTITLE",name)==0)||(strncmp("TTITLE",name,6)==0))
						{
							char utfbuffer[1024];
							int32 srclen=strlen(value);
							int32 dstlen=1024;
							int32 state;
							convert_to_utf8(B_ISO1_CONVERSION,value,&srclen,utfbuffer,&dstlen,&state);
							utfbuffer[dstlen]='\0';
							fprintf(file,"%s\n",utfbuffer);
							status=B_OK;
						}
					}
					else
						debug(("error scanning data\n"));
				}
				fclose(file);
			}
		}
	}
	return status;
}


char *CleanString(char *ss);
char *CleanString(char *ss)
{
	char *s=ss;

	int32 len=strlen(s);
	if((len>0)&&(s[len-1]=='\n'))
		s[len-1]='\0';

	while(s&&(*s!='\0'))
	{
		if(*s=='/')
			*s='-';
		s++;
	}
	return ss;
}


void do_namechange(t_CDFS_TOC *toc)
{
	BVolumeRoster vroster;
	BVolume volume;
	
	debug(("NAMECHANGE\n"));
	while(vroster.GetNextVolume(&volume)==B_OK)
	{
		char name[B_FILE_NAME_LENGTH];
		volume.GetName(name);
		fs_info info;
		fs_stat_dev(volume.Device(),&info);
		debug(("%s: %s\n",info.fsh_name,name));
		if(strcmp(info.fsh_name,"cdda")==0)
		{
			debug(("CDDA device found\n"));
			BDirectory dir;
			volume.GetRootDirectory(&dir);
			if(dir.InitCheck()==B_OK)
			{
				ulong id;
				if(sizeof(int32)==dir.ReadAttr("CD:cddbid",B_INT32_TYPE,0,&id,sizeof(int32)))
				{
					if(id==toc->CDDBid)
					{
						debug(("found cddb id\n"));
						dir.SetTo(&dir,"cdda");
						BEntry entry;
						dir.GetEntry(&entry);
						BPath path;
						entry.GetPath(&path);
						chdir(path.Path());

						FILE *file;
						char filename[B_FILE_NAME_LENGTH];
						sprintf(filename,"%s/%08x",cddbfiles,id);
						file=fopen(filename,"r");
						if(file)
						{
							char newname[B_FILE_NAME_LENGTH];
							fgets(newname,B_FILE_NAME_LENGTH,file);
							CleanString(newname);
							if(B_OK!=volume.SetName(newname))
								debug(("relabel to '%s' failed!\n",newname));
							else
								debug(("relabeled CD to '%s'\n",newname));
						
							char oldname[B_FILE_NAME_LENGTH];
							for(int i=1;i<=toc->numtracks;i++)
							{
								sprintf(oldname,"Track %02d",i);
								fgets(newname,B_FILE_NAME_LENGTH,file);
								CleanString(newname);
								int rename_status=rename(oldname,newname);
								debug(("renamed '%s' to '%s', status %08x (%s)\n",
									oldname,newname,rename_status,strerror(rename_status)));
							}
						}
						else
							debug(("couldn't open %s\n",filename));
						chdir("/");
					}
					else
						debug(("wrong cddb id\n"));
				}
				else
					debug(("no attribute\n"));
			}
			else
				debug(("can't access mountpoint\n"));
		}
	}
}


static bool HasAttribute(BMessage *attribs, char *attribute)
{
	for(int i=0;;i++)
	{
		const char *s;
		if(attribs->FindString("attr:name",i,&s)!=B_OK)
			return false;
		if(strcmp(s,attribute)==0)
			return true;
	}
}


void PublishAttributes(BMimeType *mime);
void PublishAttributes(BMimeType *mime)
{
	BMessage msg;
	mime->GetAttrInfo(&msg);
	
	if(!HasAttribute(&msg,"Audio:Length"))
	{
		msg.AddString("attr:name", "Audio:Length");
		msg.AddString("attr:public_name", "Playing time");
		msg.AddInt32("attr:type", B_STRING_TYPE);
		msg.AddBool("attr:viewable", true);
		msg.AddBool("attr:editable", false);
		msg.AddInt32("attr:width", 60);
		msg.AddInt32("attr:alignment", B_ALIGN_RIGHT);
	}
	
	if(!HasAttribute(&msg,"Audio:Track"))
	{
		msg.AddString("attr:name", "Audio:Track");
		msg.AddString("attr:public_name", "Track");
		msg.AddInt32("attr:type", B_INT32_TYPE);
		msg.AddBool("attr:viewable", true);
		msg.AddBool("attr:editable", false);
		msg.AddInt32("attr:width", 30);
		msg.AddInt32("attr:alignment", B_ALIGN_RIGHT);
	}
	mime->SetAttrInfo(&msg);
}


// publish the attributes for the audiofiles in the cdda-fs filesystem,
// so you can view them in the Tracker
void publish()
{
	BMimeType mime("audio/x-cdda");
	mime.SetLongDescription("CDDA Filesystem CD audio track file");
	mime.SetShortDescription("CDDA file");
	mime.Install();
	PublishAttributes(&mime);
	
	BMimeType mime2("audio/x-wav");
	PublishAttributes(&mime2);
} 

