ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / cdda-cddb.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
2
3 /*
4
5   cdda-cddb.c
6
7   Based on code from libcdaudio 0.5.0 (Copyright (C)1998 Tony Arcieri)
8
9   All changes copyright (c) 1998 by Mike Oliphant - oliphant@ling.ed.ac.uk
10
11     http://www.ling.ed.ac.uk/~oliphant/grip
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
21
22 */
23
24 #include "cdda-cddb.h"
25
26 #include <config.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/ioctl.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/stat.h>
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37 #include <string.h>
38 #include <pwd.h>
39 #include <errno.h>
40 #include <unistd.h>
41
42 #include <glib/gmessages.h>
43 #include <glib/gstrfuncs.h>
44 #include <glib/gutils.h>
45
46 #define size16 short
47 #define size32 int
48 #include <cdda_interface.h>
49
50 #include "cdda-cdrom-extensions.h"
51
52 /* This is here to work around a broken header file.
53  * cdda_interface.h has a statically defined array of
54  * chars that is unused. This will break our build
55  * due to our strict error checking.
56  */
57 char **broken_header_fix2 = strerror_tr;
58
59 static int      CDDBSum(int val);
60 static int      CDDBConnect(CDDBServer *server);
61 static void     CDDBDisconnect(int sock);
62 static void     CDDBSkipHTTP(int sock);
63 static int      CDDBReadLine(int sock,char *inbuffer,int len);
64 static void     CDDBMakeHello(CDDBHello *hello,char *hellobuf);
65 static void     CDDBMakeRequest(CDDBServer *server, CDDBHello *hello, char *cmd,char *outbuf,int outlen);
66 static void     CDDBProcessLine(char *inbuffer,DiscData *data, int numtracks);
67 #if 0
68 static void CDDBWriteLine(char *header,int num,char *data,FILE *outfile);
69 #endif
70
71 static char *cddb_genres[] = {"unknown","blues","classical","country",
72                               "data","folk","jazz","misc","newage",
73                               "reggae","rock","soundtrack"};
74
75 #ifdef ENABLE_IPV6
76 /*Check whether the node is IPv6 enabled.*/
77 static gboolean
78 have_ipv6 (void)
79 {
80         int s;
81
82         s = socket (AF_INET6, SOCK_STREAM, 0);
83         if (s != -1) {
84                 close (s);
85                 return TRUE;
86         }
87
88         return FALSE;
89 }
90 #endif
91
92 /* CDDB sum function */
93 static int 
94 CDDBSum(int val)
95 {
96         char *bufptr, buf[16];
97         int ret = 0;
98    
99         g_snprintf(buf,16,"%lu",(unsigned long int)val);
100
101         for(bufptr = buf; *bufptr != '\0'; bufptr++) {
102                 ret += (*bufptr - '0');
103         }   
104         return ret;
105 }
106
107 /* Produce CDDB ID for CD currently in CD-ROM */
108 unsigned int 
109 CDDBDiscid (cdrom_drive *drive)
110 {
111         int index, tracksum = 0, discid, retval;
112         disc_info disc;
113
114         retval = CDStat (drive->ioctl_fd, &disc, TRUE);
115         
116         for(index = 0; index < disc.disc_totaltracks; index++) {
117         tracksum += CDDBSum(disc.track[index].track_pos.minutes * 60 +
118                          disc.track[index].track_pos.seconds);
119         }
120         
121         discid = (disc.disc_length.minutes * 60 + disc.disc_length.seconds) -
122                          (disc.track[0].track_pos.minutes * 60 + disc.track[0].track_pos.seconds);
123         
124         return (tracksum % 0xFF) << 24 | discid << 8 | disc.disc_totaltracks;
125 }
126
127 /* Convert numerical genre to text */
128 char *CDDBGenre(int genre)
129 {
130         if(genre>11) {
131                 return("unknown");
132         }
133
134         return cddb_genres[genre];
135 }
136
137 /* Convert genre from text form into an integer value */
138 int CDDBGenreValue(char *genre)
139 {
140         int pos;
141
142         for (pos = 0; pos < 12; pos++) {
143         if (!strcmp (genre,cddb_genres[pos])) {
144                 return pos;
145         }
146     }
147         return 0;
148 }
149
150 /* Connect to a CDDB server */
151 static int 
152 CDDBConnect (CDDBServer *server)
153 {
154         int sock = -1;
155 #ifdef ENABLE_IPV6
156         struct sockaddr_in6 sin6;
157         struct addrinfo hints, *result, *res;  /*info abt the IP of node*/
158 #endif
159         struct sockaddr_in sin;
160         struct hostent *host;
161         char *sname;
162   
163 #ifdef ENABLE_IPV6
164         if (have_ipv6 ()) {
165                 result = NULL;
166
167                 memset (&sin6, 0 , sizeof (sin6));      
168                 sin6.sin6_family = AF_INET6;
169
170                 if (server->use_proxy) {
171                         sin6.sin6_port = htons (server->proxy->port);
172                 } else {
173                         sin6.sin6_port = htons (server->port);
174                 }
175         }
176 #endif
177
178         memset (&sin, 0, sizeof (sin));
179         sin.sin_family = AF_INET;
180
181         if (server->use_proxy)
182                 sin.sin_port=htons(server->proxy->port);
183         else
184                 sin.sin_port = htons(server->port);
185   
186         if (server->use_proxy)
187                 sname=server->proxy->name;
188         else
189                 sname=server->name;
190   
191 #ifdef ENABLE_IPV6
192         if (have_ipv6 ()) {
193                 memset (&hints, 0, sizeof (hints));
194                 hints.ai_socktype = SOCK_STREAM;
195
196                 if ((getaddrinfo (sname, NULL, &hints, &result)) != 0) {
197                         return -1;
198                 }
199       
200                 for (res = result; res; res = res->ai_next) {
201
202                         if (res->ai_family != AF_INET && res->ai_family != AF_INET6) {
203                                 continue;
204                         }
205
206                         sock = socket (res->ai_family, SOCK_STREAM, 0);
207                         if (sock < 0) {
208                                 continue;
209                         }
210
211                         if (res->ai_family == AF_INET) {
212                                 memcpy (&sin.sin_addr, &((struct sockaddr_in *)res->ai_addr)->sin_addr, sizeof (struct in_addr));
213
214                                 if (connect (sock, (struct sockaddr *)&sin, sizeof (sin)) != -1) {
215                                         break;
216                                 }
217                         }
218
219                         if (res->ai_family == AF_INET6) {
220                                 memcpy (&sin6.sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof (struct in6_addr));
221         
222                                 if (connect (sock, (struct sockaddr *)&sin6, sizeof (sin6)) != -1) {
223                                         break;
224                                 }
225                         }
226
227                         close (sock);
228                 }
229
230                 freeaddrinfo (result);
231
232                 if (!res) {
233                         /* No valid address found. */
234                         return -1;
235                 }
236         } else
237 #endif  /*IPv4*/
238         {
239                 sin.sin_addr.s_addr = inet_addr (sname);
240 #ifdef SOLARIS
241                 if (sin.sin_addr.s_addr == (unsigned long)-1)
242 #else
243                 if (sin.sin_addr.s_addr == INADDR_NONE)
244 #endif
245                 {
246                         host = gethostbyname (sname);
247                         if (host == NULL) {
248                                 return -1;
249                         }
250
251                         bcopy (host->h_addr, &sin.sin_addr, host->h_length);
252                 }
253
254                 sock = socket (AF_INET, SOCK_STREAM, 0);
255                 if (sock < 0) {
256                         return -1;
257                 }
258   
259                 if (connect (sock, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
260                         return -1;
261                 }
262
263         }
264
265         return sock;
266 }
267
268
269 /* Disconnect from CDDB server */
270
271 static void CDDBDisconnect(int sock)
272 {
273   shutdown(sock,2);
274   close(sock);
275 }
276
277 /* Skip http header */
278
279 static void CDDBSkipHTTP(int sock)
280 {
281         char inchar;
282         int len;
283
284         do {
285                 len=0;
286                 do {
287                         read(sock,&inchar,1);
288                         len++;
289                         //g_message ("%c",inchar);
290                 }
291                 while(inchar!='\n');
292         }       
293         while(len>2);
294 }
295
296 /* Read a single line from the CDDB server*/
297
298 static int CDDBReadLine(int sock,char *inbuffer,int len)
299 {
300   int index;
301   char inchar;
302   char *pos;
303   
304   pos=inbuffer;
305
306   for(index = 0; index < len; index++) {
307     read(sock, &inchar, 1);
308
309     if(inchar == '\n') {
310         inbuffer[index] = '\0';
311         //g_message ("[%s]\n",pos);
312         pos=inbuffer+index;
313
314                 if(inbuffer[0] == '.')
315                         return 1;
316
317                 return 0;
318         }
319
320         inbuffer[index] = inchar;
321         }
322
323   return index;
324 }
325
326 /* Make a 'hello' string from a cddb_hello structure */
327
328 static void CDDBMakeHello(CDDBHello *hello,char *hellobuf)
329 {
330   g_snprintf(hellobuf,256,"&hello=private+free.the.cddb+%s+%s",
331            hello->hello_program,hello->hello_version);
332 }
333
334 /* Make a CDDB http request string */
335
336 static void CDDBMakeRequest(CDDBServer *server,
337                             CDDBHello *hello,
338                             char *cmd,char *outbuf,int outlen)
339 {
340   char hellobuf[256];  
341
342   CDDBMakeHello(hello,hellobuf);
343
344   if(server->use_proxy)
345     g_snprintf(outbuf,outlen,
346              "GET http://%s/%s?cmd=%s%s&proto=%s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s/%s\r\nAccept: text/plain\n\n",
347              server->name,server->cgi_prog,cmd,hellobuf,
348              //CDDA_CDDB_LEVEL, server->name, Program, Version);
349              CDDA_CDDB_LEVEL, server->name, "Loser", "1.0");
350   else
351     g_snprintf(outbuf,outlen,"GET /%s?cmd=%s%s&proto=%s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s/%s\r\nAccept: text/plain\n\n",
352              server->cgi_prog,cmd,hellobuf,CDDA_CDDB_LEVEL,server->name,
353              //Program,Version);
354              "Loser", "1.0");
355 }
356
357 /* Query the CDDB for the CD currently in the CD-ROM */
358 gboolean 
359 CDDBDoQuery (cdrom_drive *cd_desc, CDDBServer *server, CDDBHello *hello, CDDBQuery *query)
360 {
361         int socket, index;
362         disc_info disc;
363         char *offset_buffer, *query_buffer, *http_buffer, inbuffer[256];
364         int tot_len,len;
365
366         socket = CDDBConnect (server);
367         if (socket==-1) {
368                 //g_message ("CDDBConnect failure");
369                 return FALSE;
370         }
371
372         query->query_matches = 0;
373
374         CDStat (cd_desc->ioctl_fd, &disc, TRUE);
375         
376         // Figure out a good buffer size -- 7 chars per track, plus 256 for the rest of the query
377         tot_len = (disc.disc_totaltracks * 7) + 256;
378
379         offset_buffer = malloc(tot_len);
380         len = 0;
381
382         len += g_snprintf (offset_buffer + len, tot_len - len, "%d", disc.disc_totaltracks);
383
384         for (index = 0; index < disc.disc_totaltracks; index++) {
385                 len += g_snprintf (offset_buffer + len, tot_len - len, "+%d", disc.track[index].track_start);
386     }
387
388         query_buffer = malloc(tot_len);
389
390         g_snprintf (query_buffer, tot_len, "cddb+query+%08x+%s+%d",
391                                 CDDBDiscid(cd_desc),
392                                 offset_buffer,
393                                 disc.disc_length.minutes * 60 +
394                                 disc.disc_length.seconds);
395
396         http_buffer = malloc(tot_len);
397
398         CDDBMakeRequest (server, hello, query_buffer, http_buffer, tot_len);
399
400         //g_message ("Query is [%s]\n",http_buffer);
401
402         write (socket, http_buffer, strlen (http_buffer));
403
404         free (offset_buffer);
405         free (query_buffer);
406         free (http_buffer);
407    
408         CDDBSkipHTTP (socket);
409
410         *inbuffer='\0';
411
412         CDDBReadLine(socket,inbuffer,256);
413
414         /* Skip the keep-alive */
415         if((strlen(inbuffer)<5)||!strncmp(inbuffer,"Keep",4)) {
416                 //g_message("Skipping keepalive\n");
417                 CDDBReadLine (socket,inbuffer,256);
418         }
419
420         //g_message ("Reply is [%s]\n",inbuffer);
421
422         switch(strtol(strtok(inbuffer," "),NULL,10)) {
423     /* 200 - exact match */
424   case 200:
425     query->query_match=MATCH_EXACT;
426     query->query_matches=1;
427
428     query->query_list[0].list_genre=
429       CDDBGenreValue(ChopWhite(strtok(NULL," ")));
430
431     sscanf(ChopWhite(strtok(NULL," ")),"%xd",
432            &query->query_list[0].list_id);
433
434     CDDBParseTitle(ChopWhite(strtok(NULL,"")),query->query_list[0].list_title,
435                    query->query_list[0].list_artist,"/");
436
437     break;
438     /* 211 - inexact match */
439   case 211:
440     query->query_match=MATCH_INEXACT;
441     query->query_matches=0;
442
443     while(!CDDBReadLine(socket,inbuffer,256)) {
444       query->query_list[query->query_matches].list_genre=
445         CDDBGenreValue(ChopWhite(strtok(inbuffer," ")));
446       
447       sscanf(ChopWhite(strtok(NULL," ")),"%xd",
448              &query->query_list[query->query_matches].list_id);
449       
450       CDDBParseTitle(ChopWhite(strtok(NULL,"")),
451                      query->query_list[query->query_matches].list_title,
452                      query->query_list[query->query_matches].list_artist,"/");
453
454       query->query_matches++;
455     }
456       
457     break;
458     
459     /* No match */
460         default:
461         query->query_match=MATCH_NOMATCH;
462
463     CDDBDisconnect(socket);
464
465     return FALSE;
466   }
467
468   CDDBDisconnect(socket);
469   
470   return TRUE;
471 }
472
473 /* Get rid of whitespace at the beginning or end of a string */
474
475 char *ChopWhite(char *buf)
476 {
477   int pos;
478
479   for(pos=strlen(buf)-1;(pos>=0)&&g_ascii_isspace(buf[pos]);pos--);
480
481   buf[pos+1]='\0';
482
483   for(;g_ascii_isspace(*buf);buf++);
484
485   return buf;
486 }
487
488 /* Split string into title/artist */
489
490 void CDDBParseTitle(char *buf,char *title,char *artist,char *sep)
491 {
492   char *tmp;
493
494   tmp=strtok(buf,sep);
495
496   if(!tmp) return;
497
498   strncpy(artist,ChopWhite(tmp),64);
499
500   tmp=strtok(NULL,"");
501
502   if(tmp)
503     strncpy(title,ChopWhite(tmp),64);
504   else strcpy(title,artist);
505 }
506
507 /* Process a line of input data */
508
509 static void CDDBProcessLine(char *inbuffer,DiscData *data,
510                             int numtracks)
511 {
512   int track;
513   int len = 0;
514   char *st;
515   
516   if(!g_ascii_strncasecmp(inbuffer,"DTITLE",6)) {
517     len = strlen(data->data_title);
518
519     strncpy(data->data_title+len,ChopWhite(inbuffer+7),256-len);
520   }
521   else if(!g_ascii_strncasecmp(inbuffer,"DYEAR",5)) {
522     strtok(inbuffer,"=");
523     
524     st = strtok(NULL, "");
525     if(st == NULL)
526         return;
527
528     data->data_year=atoi(ChopWhite(st));
529   }
530   else if(!g_ascii_strncasecmp(inbuffer,"DGENRE",6)) {
531     strtok(inbuffer,"=");
532     
533     st = strtok(NULL, "");
534     if(st == NULL)
535         return;
536     
537     data->data_genre=CDDBGenreValue(ChopWhite(st));
538   }
539   else if(!g_ascii_strncasecmp(inbuffer,"TTITLE",6)) {
540     track=atoi(strtok(inbuffer+6,"="));
541     
542     if(track<numtracks)
543       len=strlen(data->data_track[track].track_name);
544
545     strncpy(data->data_track[track].track_name+len,
546             ChopWhite(strtok(NULL,"")),256-len);
547   }
548   else if(!g_ascii_strncasecmp(inbuffer,"TARTIST",7)) {
549     data->data_multi_artist=TRUE;
550
551     track=atoi(strtok(inbuffer+7,"="));
552     
553     if(track<numtracks)
554       len=strlen(data->data_track[track].track_artist);
555
556     st = strtok(NULL, "");
557     if(st == NULL)
558         return;    
559     
560     strncpy(data->data_track[track].track_artist+len,
561             ChopWhite(st),256-len);
562   }
563   else if(!g_ascii_strncasecmp(inbuffer,"EXTD",4)) {
564     len=strlen(data->data_extended);
565
566     strncpy(data->data_extended+len,ChopWhite(inbuffer+5),4096-len);
567   }
568   else if(!g_ascii_strncasecmp(inbuffer,"EXTT",4)) {
569     track=atoi(strtok(inbuffer+4,"="));
570     
571     if(track<numtracks)
572       len=strlen(data->data_track[track].track_extended);
573
574     st = strtok(NULL, "");
575     if(st == NULL)
576         return;
577     
578     strncpy(data->data_track[track].track_extended+len,
579             ChopWhite(st),4096-len);
580   }
581   else if(!g_ascii_strncasecmp(inbuffer,"PLAYORDER",5)) {
582     len=strlen(data->data_playlist);
583
584     strncpy(data->data_playlist+len,ChopWhite(inbuffer+10),256-len);
585   }
586 }
587
588
589 /* Read the actual CDDB entry */
590 gboolean CDDBRead(cdrom_drive *cd_desc, CDDBServer *server,
591                   CDDBHello *hello,CDDBEntry *entry,
592                   DiscData *data)
593 {
594   int socket;
595   int index;
596   char outbuffer[256], inbuffer[512],cmdbuffer[256];
597   disc_info disc;
598   
599   socket=CDDBConnect(server);
600   if(socket==-1) return FALSE;
601   
602   //CDStat(cd_desc,&disc,TRUE);
603   
604   data->data_genre=entry->entry_genre;
605   data->data_id=CDDBDiscid(cd_desc);
606   *(data->data_extended)='\0';
607   *(data->data_title)='\0';
608   *(data->data_artist)='\0';
609   *(data->data_playlist)='\0';
610   data->data_multi_artist=FALSE;
611   data->data_year=0;
612
613   for(index=0;index<MAX_TRACKS;index++) {
614     *(data->data_track[index].track_name)='\0';
615     *(data->data_track[index].track_artist)='\0';
616     *(data->data_track[index].track_extended)='\0';
617   }
618
619   g_snprintf(cmdbuffer,256,"cddb+read+%s+%08x",CDDBGenre(entry->entry_genre),
620            entry->entry_id);
621   
622   CDDBMakeRequest(server,hello,cmdbuffer,outbuffer,256);
623
624   write(socket,outbuffer,strlen(outbuffer));
625    
626   CDDBSkipHTTP(socket);
627
628   CDDBReadLine(socket,inbuffer,256);
629
630   /* Skip the keep-alive */
631   if((strlen(inbuffer)<5)||!strncmp(inbuffer,"Keep",4)) {
632                 //g_message ("Skipping keepalive\n");
633                 CDDBReadLine(socket,inbuffer,256);
634   }
635
636   while(!CDDBReadLine(socket,inbuffer,512))
637     CDDBProcessLine(inbuffer,data,disc.disc_totaltracks);
638
639   /* Both disc title and artist have been stuffed in the title field, so the
640      need to be separated */
641
642   CDDBParseTitle(data->data_title,data->data_title,data->data_artist,"/");
643
644   CDDBDisconnect(socket);
645    
646   return 0;
647 }
648
649 /* See if a disc is in the local database */
650
651 gboolean CDDBStatDiscData(cdrom_drive *cd_desc)
652 {
653   int index,id;
654   //disc_info disc;
655   struct stat st;
656   char root_dir[256], file[256];
657   
658   //CDStat(cd_desc,&disc,TRUE);
659
660   id=CDDBDiscid(cd_desc);
661   
662   g_snprintf(root_dir,256,"%s/.cddb",getenv("HOME"));
663   
664   if(stat(root_dir, &st) < 0)
665     return FALSE;
666   else {
667     if(!S_ISDIR(st.st_mode))
668       return FALSE;
669   }
670   
671   g_snprintf(file,256,"%s/%08x",root_dir,id);
672   if(stat(file,&st)==0) return TRUE;
673
674   for(index=0;index<12;index++) {
675     g_snprintf(file,256,"%s/%s/%08x",root_dir,CDDBGenre(index),id);
676
677     if(stat(file,&st) == 0)
678       return TRUE;
679   }
680    
681   return FALSE;
682 }
683
684 /* Read from the local database */
685 int 
686 CDDBReadDiscData(cdrom_drive *cd_desc,DiscData *ddata)
687 {
688         FILE *cddb_data = NULL;
689         int index,genre;
690         char root_dir[256], file[256], inbuf[512];
691         disc_info disc;
692         struct stat st;
693   
694         g_snprintf(root_dir,256,"%s/.cddb",getenv("HOME"));
695   
696         if(stat(root_dir, &st) < 0) {
697         return -1;
698         } else {
699         if(!S_ISDIR(st.st_mode)) {
700                         errno = ENOTDIR;
701                         return -1;
702                 }
703         }
704   
705         CDStat (cd_desc->ioctl_fd, &disc, TRUE);
706
707   ddata->data_id=CDDBDiscid(cd_desc);
708   *(ddata->data_extended)='\0';
709   *(ddata->data_title)='\0';
710   *(ddata->data_artist)='\0';
711   *(ddata->data_playlist)='\0';
712   ddata->data_multi_artist=FALSE;
713   ddata->data_year=0;
714
715   for(index=0;index<MAX_TRACKS;index++) {
716     *(ddata->data_track[index].track_name)='\0';
717     *(ddata->data_track[index].track_artist)='\0';
718     *(ddata->data_track[index].track_extended)='\0';
719   }
720
721   g_snprintf(file,256,"%s/%08x",root_dir,ddata->data_id);
722   if(stat(file,&st)==0) {
723     cddb_data=fopen(file, "r");
724   }
725   else {
726     for(genre=0;genre<12;genre++) {
727       g_snprintf(file,256,"%s/%s/%08x",root_dir,CDDBGenre(genre),
728                ddata->data_id);
729       
730       if(stat(file,&st)==0) {
731         cddb_data=fopen(file, "r");
732         
733         ddata->data_genre=genre;
734         break;
735       }
736     }
737
738     if(genre==12) return -1;
739   }
740
741   while(fgets(inbuf,512,cddb_data))
742     CDDBProcessLine(inbuf,ddata,disc.disc_totaltracks);
743
744   /* Both disc title and artist have been stuffed in the title field, so the
745      need to be separated */
746
747   CDDBParseTitle(ddata->data_title,ddata->data_title,ddata->data_artist,"/");
748
749   fclose(cddb_data);
750   
751   return 0;
752 }
753
754 #if 0
755 static void CDDBWriteLine(char *header,int num,char *data,FILE *outfile)
756 {
757   int len;
758   char *offset;
759
760   len=strlen(data);
761   offset=data;
762
763   for(;;) {
764     if(len>80) {
765       if(num==-1)
766         fprintf(outfile,"%s=%.70s\n",header,offset);
767       else fprintf(outfile,"%s%d=%.70s\n",header,num,offset);
768
769       offset+=70;
770       len-=70;
771     }
772     else {
773       if(num==-1) fprintf(outfile,"%s=%s\n",header,offset);
774       else fprintf(outfile,"%s%d=%s\n",header,num,offset);
775       break;
776     }
777   }
778 }
779 #endif
780
781 /* Write to the local cache */
782 int CDDBWriteDiscData(cdrom_drive *drive, DiscData *ddata,FILE *outfile,
783                       gboolean gripext)
784 {
785 #if 0
786   FILE *cddb_data;
787   int track;
788   char root_dir[256],file[256];
789   struct stat st;
790   disc_info disc;
791   
792   //CDStat(cd_desc,&disc,TRUE);
793
794   if(!outfile) {
795     g_snprintf(root_dir,256,"%s/.cddb",getenv("HOME"));
796     g_snprintf(file,256,"%s/%08x",root_dir,ddata->data_id);
797   
798     if(stat(root_dir,&st)<0) {
799       if(errno != ENOENT) {
800         //g_message("Stat error %d on %s\n",errno,root_dir);
801         return -1;
802       }
803       else {
804         //g_message("Creating directory %s\n",root_dir);
805         mkdir(root_dir, 0755);
806       }
807     } else {
808       if(!S_ISDIR(st.st_mode)) {
809         //g_message("Error: %s exists, but is a file\n",root_dir);
810         errno=ENOTDIR;
811         return -1;
812       }   
813     }
814       
815     if((cddb_data=fopen(file,"w"))==NULL) {
816       //g_message("Error: Unable to open %s for writing\n",file);
817       return -1;
818     }
819   }
820   else cddb_data=outfile;
821
822   //fprintf(cddb_data,"# xmcd CD database file generated by %s %s\n", Program,Version);
823   fprintf(cddb_data,"# xmcd CD database file generated by Loser 1.0\n");
824   fputs("# \n",cddb_data);
825   fputs("# Track frame offsets:\n",cddb_data);
826
827   for (track = 0; track < disc.disc_totaltracks; track++)
828     fprintf(cddb_data, "#       %d\n",disc.track[track].track_start);
829
830   fputs("# \n",cddb_data);
831   fprintf(cddb_data,"# Disc length: %d seconds\n",disc.disc_length.minutes *
832           60 + disc.disc_length.seconds);
833   fputs("# \n",cddb_data);
834   fprintf(cddb_data,"# Revision: %s\n",CDDA_CDDB_LEVEL);
835   //fprintf(cddb_data,"# Submitted via: %s %s\n",Program,Version);
836   fprintf(cddb_data,"# Submitted via: Loser 1.0\n");
837   fputs("# \n",cddb_data);
838   fprintf(cddb_data,"DISCID=%08x\n",ddata->data_id);
839
840   fprintf(cddb_data,"DTITLE=%s / %s\n",
841           ddata->data_artist,ddata->data_title);
842
843   if(gripext&&ddata->data_year)
844     fprintf(cddb_data,"DYEAR=%d\n",ddata->data_year);
845
846   if(gripext)
847     fprintf(cddb_data,"DGENRE=%s\n",CDDBGenre(ddata->data_genre));
848
849   for(track=0;track<disc.disc_totaltracks;track++) {
850     if(gripext||!*(ddata->data_track[track].track_artist)) {
851       fprintf(cddb_data,"TTITLE%d=%s\n",track,
852               ddata->data_track[track].track_name);
853     }
854     else {
855       fprintf(cddb_data,"TTITLE%d=%s / %s\n",track,
856               ddata->data_track[track].track_name,
857               ddata->data_track[track].track_artist);
858     }
859
860     if(gripext&&*(ddata->data_track[track].track_artist))
861       fprintf(cddb_data,"TARTIST%d=%s\n",track,
862               ddata->data_track[track].track_artist);
863     
864   }
865
866   CDDBWriteLine("EXTD",-1,ddata->data_extended,cddb_data);
867    
868   for(track=0;track<disc.disc_totaltracks;track++)
869     CDDBWriteLine("EXTT",track,
870                   ddata->data_track[track].track_extended,cddb_data);
871   
872   if(outfile)
873     fprintf(cddb_data,"PLAYORDER=\n");
874   else {
875     fprintf(cddb_data,"PLAYORDER=%s\n",ddata->data_playlist);
876     fclose(cddb_data);
877   }
878 #endif
879   return 0;
880 }
881
882
883 gboolean
884 CDDBLookupDisc (CDDBServer *server, cdrom_drive *drive, DiscData *disc_data)
885 {
886         CDDBHello hello;
887         CDDBQuery query;
888         CDDBEntry entry;
889         gboolean success = FALSE;
890
891         if(server->use_proxy) {
892         //g_message ("Querying %s (through %s:%d) for disc %02x.\n", server->name,
893                 //server->proxy->name, server->proxy->port, CDDBDiscid (drive));
894         } else {
895                 //g_message ("Querying %s for disc %02x.\n",server->name, CDDBDiscid (drive));
896         }
897         
898         strncpy (hello.hello_program, "Loser", 256);
899         strncpy (hello.hello_version, "1.0", 256);
900         
901         if (!CDDBDoQuery (drive, server, &hello, &query)) {
902                 g_message ("Query failed");
903         } else {
904                 switch(query.query_match) {
905                         case MATCH_INEXACT:
906                         case MATCH_EXACT:
907                                 //g_message ("Match for \"%s / %s\"\nDownloading data...\n",
908                                 //                      query.query_list[0].list_artist,
909                                 //                      query.query_list[0].list_title);
910                                 entry.entry_genre = query.query_list[0].list_genre;
911                         entry.entry_id = query.query_list[0].list_id;
912                         CDDBRead (drive, server, &hello, &entry, disc_data);
913                 
914                         //g_message ("Done\n");
915                         success = TRUE;
916                 
917                         //if (CDDBWriteDiscData (drive, disc_data, NULL, TRUE) < 0) {
918                                 //      printf ("Error saving disc data\n");
919                                 //}             
920                                 break;
921                         
922                 case MATCH_NOMATCH:
923                         g_message ("No match\n");
924                         break;
925                 }
926         }
927         return success;
928 }
929
930 /* Update a CD status structure... because operating system interfaces vary
931    so does this function. */
932
933 int 
934 CDStat (int cd_desc, disc_info *disc, gboolean read_toc)
935 {
936         struct cdrom_tochdr cdth;
937         struct cdrom_tocentry cdte;  
938         int readtracks,frame[MAX_TRACKS],pos;
939         int retcode;
940
941         retcode = ioctl(cd_desc, CDROM_DRIVE_STATUS, CDSL_CURRENT);
942         //g_message("Drive status is %d\n", retcode);
943         if (retcode < 0) {
944       //g_message("Drive doesn't support drive status check (assume CDS_NO_INFO)\n");
945         } else if (retcode != CDS_DISC_OK && retcode != CDS_NO_INFO) {
946                 return -1;
947         }
948
949         disc->disc_present = 1;
950
951         if (read_toc) {
952                 //g_message ("Reading TOC");
953                 /* Read the Table Of Contents header */
954                 if(ioctl(cd_desc, CDROMREADTOCHDR, &cdth) < 0) {
955                         printf("Error: Failed to read disc contents\n");
956                         return -1;
957                 }
958                 disc->disc_totaltracks = cdth.cdth_trk1;
959     
960                 /* Read the table of contents */
961                 for(readtracks = 0; readtracks <= disc->disc_totaltracks; readtracks++) {
962                         if(readtracks == disc->disc_totaltracks)        
963                                 cdte.cdte_track = CDROM_LEADOUT;
964                         else
965                                 cdte.cdte_track = readtracks + 1;
966       
967                         cdte.cdte_format = CDROM_MSF;
968                         if(ioctl(cd_desc, CDROMREADTOCENTRY, &cdte) < 0) {
969                                 printf("Error: Failed to read disc contents\n");
970                                 return -1;
971         }
972       
973       disc->track[readtracks].track_pos.minutes = cdte.cdte_addr.msf.minute;
974       disc->track[readtracks].track_pos.seconds = cdte.cdte_addr.msf.second;
975       frame[readtracks] = cdte.cdte_addr.msf.frame;
976     }
977     
978     for(readtracks = 0; readtracks <= disc->disc_totaltracks; readtracks++) {
979       disc->track[readtracks].track_start=
980         (disc->track[readtracks].track_pos.minutes * 60 +
981          disc->track[readtracks].track_pos.seconds) * 75 + frame[readtracks];
982       
983       if(readtracks > 0) {
984         pos = (disc->track[readtracks].track_pos.minutes * 60 +
985                disc->track[readtracks].track_pos.seconds) -
986           (disc->track[readtracks - 1].track_pos.minutes * 60 +
987            disc->track[readtracks -1].track_pos.seconds);
988         disc->track[readtracks - 1].track_length.minutes = pos / 60;
989         disc->track[readtracks - 1].track_length.seconds = pos % 60;
990       }
991     }
992     
993     disc->disc_length.minutes=
994       disc->track[disc->disc_totaltracks].track_pos.minutes;
995     
996     disc->disc_length.seconds=
997       disc->track[disc->disc_totaltracks].track_pos.seconds;
998   }
999    
1000   disc->disc_track = 0;
1001
1002   while(disc->disc_track < disc->disc_totaltracks &&
1003         disc->disc_frame >= disc->track[disc->disc_track].track_start)
1004     disc->disc_track++;
1005
1006   pos=(disc->disc_frame - disc->track[disc->disc_track - 1].track_start) / 75;
1007
1008   disc->track_time.minutes = pos / 60;
1009   disc->track_time.seconds = pos % 60;
1010
1011   return 0;
1012 }