Main Page | Data Structures | File List | Data Fields | Globals | Related Pages

playlist.c

Go to the documentation of this file.
00001 /*
00002  *  playlist.c
00003  *  mod_musicindex
00004  *
00005  *  $Id: playlist.c,v 1.60 2003/10/28 20:50:12 boudinr Exp $
00006  *
00007  *  Created by Thibaut VARENE on Thu Mar 20 2003.
00008  *  Copyright (c) 2003 Regis BOUDIN
00009  *  Copyright (c) 2003 Thibaut VARENE
00010  *   
00011  *  This program is free software; you can redistribute it and/or modify
00012  *  it under the terms of the GNU Lesser General Public License as published by
00013  *  the Free Software Foundation; either version 2.1, or (at your option)
00014  *  any later version.
00015  *
00016  */
00017 
00035 #include "playlist.h"
00036 #include "html.h"
00037 
00046 mu_ent *new_ent(apr_pool_t *pool, mu_ent *head)
00047 {
00048         mu_ent *p = (mu_ent *) ap_pcalloc(pool, sizeof(mu_ent));
00049         memset(p, 0, sizeof(mu_ent));   
00050         p->next = head;
00051         return p;
00052 }
00053 
00057 #undef DETECT_BY_CONTENT_NOT_BORKEN
00058 #ifdef DETECT_BY_CONTENT_NOT_BORKEN
00059 /* The following pieces of code are taken from xmms, in:
00060  *      Input/mpg123/common.c
00061  *      Input/mpg123/mpg123.c
00062  */
00063 static unsigned long mpg123_convert_to_header(unsigned char * buf)
00064 {
00065         return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
00066 }
00067 
00077 static short mpg123_head_check(unsigned long head)
00078 {
00079         if ((head & 0xffe00000) != 0xffe00000)
00080                 return FALSE;
00081         if (!((head >> 17) & 3))
00082                 return FALSE;
00083         if (((head >> 12) & 0xf) == 0xf)
00084                 return FALSE;
00085         if (!((head >> 12) & 0xf))
00086                 return FALSE;
00087         if (((head >> 10) & 0x3) == 0x3)
00088                 return FALSE;
00089         if (((head >> 19) & 1) == 1 && ((head >> 17) & 3) == 3 && ((head >> 16) & 1) == 1)
00090                 return FALSE;
00091         if ((head & 0xffff0000) == 0xfffe0000)
00092                 return FALSE;
00093 
00094         return TRUE;
00095 }
00096 #endif
00097 
00101 const char *mpg123_id3_genres[GENRE_MAX] = {
00102         "Blues", "Classic Rock", "Country", "Dance",
00103         "Disco", "Funk", "Grunge", "Hip-Hop",
00104         "Jazz", "Metal", "New Age", "Oldies",
00105         "Other", "Pop", "R&B", "Rap", "Reggae",
00106         "Rock", "Techno", "Industrial", "Alternative",
00107         "Ska", "Death Metal", "Pranks", "Soundtrack",
00108         "Euro-Techno", "Ambient", "Trip-Hop", "Vocal",
00109         "Jazz+Funk", "Fusion", "Trance", "Classical",
00110         "Instrumental", "Acid", "House", "Game",
00111         "Sound Clip", "Gospel", "Noise", "Alt",
00112         "Bass", "Soul", "Punk", "Space",
00113         "Meditative", "Instrumental Pop",
00114         "Instrumental Rock", "Ethnic", "Gothic",
00115         "Darkwave", "Techno-Industrial", "Electronic",
00116         "Pop-Folk", "Eurodance", "Dream",
00117         "Southern Rock", "Comedy", "Cult",
00118         "Gangsta Rap", "Top 40", "Christian Rap",
00119         "Pop/Funk", "Jungle", "Native American",
00120         "Cabaret", "New Wave", "Psychedelic", "Rave",
00121         "Showtunes", "Trailer", "Lo-Fi", "Tribal",
00122         "Acid Punk", "Acid Jazz", "Polka", "Retro",
00123         "Musical", "Rock & Roll", "Hard Rock", "Folk",
00124         "Folk/Rock", "National Folk", "Swing",
00125         "Fast-Fusion", "Bebob", "Latin", "Revival",
00126         "Celtic", "Bluegrass", "Avantgarde",
00127         "Gothic Rock", "Progressive Rock",
00128         "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
00129         "Big Band", "Chorus", "Easy Listening",
00130         "Acoustic", "Humour", "Speech", "Chanson",
00131         "Opera", "Chamber Music", "Sonata", "Symphony",
00132         "Booty Bass", "Primus", "Porn Groove",
00133         "Satire", "Slow Jam", "Club", "Tango",
00134         "Samba", "Folklore", "Ballad", "Power Ballad",
00135         "Rhythmic Soul", "Freestyle", "Duet",
00136         "Punk Rock", "Drum Solo", "A Cappella",
00137         "Euro-House", "Dance Hall", "Goa",
00138         "Drum & Bass", "Club-House", "Hardcore",
00139         "Terror", "Indie", "BritPop", "Negerpunk",
00140         "Polsk Punk", "Beat", "Christian Gangsta Rap",
00141         "Heavy Metal", "Black Metal", "Crossover",
00142         "Contemporary Christian", "Christian Rock",
00143         "Merengue", "Salsa", "Thrash Metal",
00144         "Anime", "JPop", "Synthpop"
00145 };
00146 
00154 static short mpg123_mp3_ext_check(char *filename)
00155 {
00156         char *ext = strrchr(filename, '.');
00157         if (ext && (!strncasecmp(ext, ".mp2", 4) || !strncasecmp(ext, ".mp3", 4)))
00158                 return FALSE;
00159         return TRUE;
00160 }
00161 
00176 static id3_utf8_t *utf8_id3tag_findframe(struct id3_tag *tag, char const *frameid,
00177         unsigned short index)
00178 {
00179         const struct id3_frame  *frame = NULL;
00180         const union id3_field   *field = NULL;
00181         const id3_ucs4_t        *ucs4 = NULL;
00182 
00183         if ((frame = id3_tag_findframe (tag, frameid, index))) {
00184                 field = &frame->fields[1];
00185                 if (id3_field_getnstrings(field)
00186                         && (ucs4 = id3_field_getstrings(field, index))) {
00187                         return(id3_ucs4_utf8duplicate(ucs4));
00188                         /* id3_ucs4_utf8duplicate() allocates memory.
00189                            Don't forget to free() after the function call. */
00190                 }
00191         }
00192         return NULL;
00193 }
00194 
00211 mu_ent *make_ogg_entry(apr_pool_t *pool, mu_ent *head,
00212         FILE *in, mu_config * conf, mu_ent_names *names, request_rec * r)
00213 {
00214         mu_ent                  *p = head;
00215         OggVorbis_File          vf;
00216         vorbis_comment          *comment = NULL;
00217         char                    *t;
00218                 
00219         if (!(ov_open(in, &vf, NULL, 0))) {
00220                 p = new_ent(pool, head);
00221                 p->filetype = FT_OGG;
00222         
00223                 if ((comment = ov_comment(&vf, -1))) {
00224                         if ((t = vorbis_comment_query(comment, "album", 0)))
00225                                 p->album = ap_pstrdup(pool, t);
00226                         if ((t = vorbis_comment_query(comment, "artist", 0)))
00227                                 p->artist = ap_pstrdup(pool, t);
00228                         if ((t = vorbis_comment_query(comment, "title", 0)))
00229                                 p->title = ap_pstrdup(pool, t);
00230                         if ((t = vorbis_comment_query(comment, "tracknumber", 0)))
00231                                 p->track = atoi(t);
00232                         if ((t = vorbis_comment_query(comment, "date", 0)))
00233                                 p->date = atoi(t);
00234                         if ((t = vorbis_comment_query(comment, "tpos", 0)))     /* XXX does it exist? */
00235                                 p->posn = atoi(t);
00236                         if ((t = vorbis_comment_query(comment, "genre", 0)))
00237                                 p->genre = ap_pstrdup(pool, t);
00238                 }
00239 
00240                 if (conf->options & (MI_QUICKPL)) {
00241                         p->bitrate = p->length = 0;
00242                 }
00243                 else {
00244                         p->bitrate = (long)ov_bitrate(&vf, -1);
00245                         p->length = (long)ov_time_total(&vf, -1);
00246                 }
00247 
00248                 ov_clear(&vf);
00249 
00250         }
00251 
00252         return p;
00253 }
00254 
00273 mu_ent *make_mp3_entry(apr_pool_t *pool, mu_ent *head,
00274         FILE *in, mu_config * conf, mu_ent_names *names, request_rec * r) 
00275 {
00276         mu_ent                  *p = head;
00277 
00278         struct id3_file         *id3struct = NULL;
00279         struct id3_tag          *tag = NULL;
00280         id3_utf8_t              *utf8 = NULL;
00281 
00282         struct stat             filestat;
00283         
00284         struct mad_stream       madstream;
00285         struct mad_frame        madframe;
00286         unsigned char           madinput_buffer[INPUT_BUFFER_SIZE];
00287         unsigned short          mp3gid = 0, isd = 0;
00288 
00289 #ifdef DETECT_BY_CONTENT_NOT_BORKEN
00290         unsigned char           tmp[4];
00291 
00292         if (fread(tmp, 1, 4, in) != 4)
00293                 goto exit;
00294 
00295         if (mpg123_head_check(mpg123_convert_to_header(tmp)))
00296 #else
00297         if (mpg123_mp3_ext_check(names->filename))
00298 #endif
00299                 goto exit;
00300         
00301         mad_stream_init(&madstream);
00302         mad_frame_init(&madframe);
00303         
00304         fstat(fileno(in), &filestat);
00305         
00306         p = new_ent(pool, head);
00307         p->filetype = FT_MP3;
00308         p->size = filestat.st_size;
00309                                 
00310         if (conf->options & (MI_QUICKPL)) {
00311                 p->bitrate = p->length = 0;
00312         }
00313         else {
00314                 /* First of all we try to find out the bitrate and length of
00315                  * the file */
00316                 do {
00317                         if (madstream.buffer == NULL || madstream.error == MAD_ERROR_BUFLEN) {
00318                                 size_t          madread_size, remaining;
00319                                 unsigned char   *madread_start;
00320                                 
00321                                 if (madstream.next_frame != NULL) {
00322                                         remaining = madstream.bufend-madstream.next_frame;
00323                                         memmove(madinput_buffer, madstream.next_frame, remaining);
00324                                         madread_start=madinput_buffer + remaining;
00325                                         madread_size = INPUT_BUFFER_SIZE - remaining;
00326                                 }
00327                                 else {
00328                                         madread_size = INPUT_BUFFER_SIZE;
00329                                         madread_start = madinput_buffer;
00330                                         remaining = 0;
00331                                 }
00332                                 
00333                                 madread_size = fread(madread_start, 1, madread_size, in);
00334                                 if (madread_size <= 0) {
00335                                         ap_log_printf(r->server, "[musicindex] DBG: maderror madread_size <= 0 on %s", names->filename);
00336                                         goto exit;
00337                                         /* TODO traitement eventuel erreur lecture */
00338                                 }
00339         
00340                                 mad_stream_buffer(&madstream, madinput_buffer, madread_size + remaining);
00341                                 madstream.error = 0;
00342                         }
00343         
00344         
00345                                 
00346                         if (mad_frame_decode(&madframe,&madstream)) {
00347                                 if (MAD_RECOVERABLE(madstream.error)) {
00348                                         /* TODO traitement eventuel erreur recoverable */
00349                                         continue;
00350                                 }
00351                                 else {
00352                                         if (madstream.error == MAD_ERROR_BUFLEN)
00353                                                 continue;
00354                                         else {
00355                                                 ap_log_printf(r->server, "[musicindex] DBG: maderror madstream.error unrecoverable on %s", names->filename);
00356                                                 /* TODO traitement eventuel erreur unrecoverable */
00357                                                 goto exit;
00358                                         }
00359                                 }
00360                         }
00361                 
00362                         p->bitrate = madframe.header.bitrate * 1.024;
00363                         p->length = filestat.st_size / (madframe.header.bitrate / 8);
00364                         break;
00365                 } while (1);
00366         }
00367         
00368         /* Then we check for an ID3 tag and read it if any */
00369         if ((id3struct = id3_file_open(names->filename, ID3_FILE_MODE_READONLY))) {
00370                 if ((tag = id3_file_tag(id3struct)) && (tag->frames)) {
00371                         if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_TITLE, 0))) {
00372                                 p->title = ap_pstrdup(pool, (char *)utf8);
00373                                 free(utf8);
00374                         }
00375 
00376                         if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_ARTIST, 0))) {
00377                                 p->artist = ap_pstrdup(pool, (char *)utf8);
00378                                 free(utf8);
00379                         }
00380                         
00381                         if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_ALBUM, 0))) {
00382                                 p->album = ap_pstrdup(pool, (char *)utf8);
00383                                 free(utf8);
00384                         }
00385 
00386                         if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_YEAR, 0))) {
00387                                 p->date = atoi((char *)utf8);
00388                                 free(utf8);
00389                         }
00390 
00391                         if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_TRACK, 0))) {
00392                                 p->track = atoi((char *)utf8);
00393                                 free(utf8);
00394                         }
00395                         /* TPOS is Part Of a Set (aka disc number) */
00396                         if ((utf8 = utf8_id3tag_findframe(tag, "TPOS", 0))) {
00397                                 p->posn = atoi((char *)utf8);
00398                                 free(utf8);
00399                         }
00400                         /* Some MP3s (especially VBRs) are nice enough to tag their length */
00401                         if ((utf8 = utf8_id3tag_findframe(tag, "TLEN", 0))) {
00402                                 if ((atoi((char *)utf8) / 1000) > 0)
00403                                         p->length = atoi((char *)utf8) / 1000;
00404                                 free(utf8);
00405                         }
00406                         if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_GENRE, 0))) {
00407                                 if (*utf8 == '(') {     /* some encoders encaps id nb in brackets */
00408                                         (*utf8)++;
00409                                         mp3gid = atoi((char *)utf8);
00410                                         (*utf8)--;      /* MOUAHAHAHA MIAM MIAM */
00411                                         isd = 1;
00412                                 }
00413                                 else if ((isd = isdigit(*utf8)))
00414                                         mp3gid = atoi((char *)utf8);
00415                                 
00416                                 if (isd && (mp3gid < GENRE_MAX))
00417                                         p->genre = ap_pstrdup(pool, mpg123_id3_genres[mp3gid]);
00418                                 else
00419                                         p->genre = ap_pstrdup(pool, (char *)utf8);
00420 
00421                                 free(utf8);
00422                         }
00423 
00424                 }
00425                 id3_file_close (id3struct);
00426         }
00427 
00428 exit:
00429         return p;
00430 }
00431 
00450 mu_ent *make_music_entry(apr_pool_t *pool, request_rec * r,
00451         mu_ent *head, mu_config * conf, mu_ent_names *names)
00452 {
00453         DIR                     *dir;
00454         struct DIR_TYPE         *dstruct;
00455         mu_ent                  *p = head;
00456         FILE                    *in = NULL;
00457         unsigned short          i, checkresult = 0;
00458         char                    *t, *fn, *uri;
00459 #ifndef NO_CACHE
00460         mu_cache_data           *cachedata = NULL;
00461 #endif
00462         
00463         if (!names) {
00464                 names = ap_palloc(r->pool, sizeof(mu_ent_names));
00465                 names->filename = ap_palloc(r->pool, MAX_STRING);
00466                 names->uri = ap_palloc(r->pool, MAX_STRING);
00467                 strcpy(names->filename, r->filename);
00468                 strcpy(names->uri, r->parsed_uri.path);
00469         }
00470 
00471         t = names->uri + strlen(names->uri)-1;
00472         while (*t != '/')
00473                 t--;
00474         t++;
00475         if (*t == '.')
00476                 return p;
00477 
00478         if ((in = fopen(names->filename, "r"))) {
00479 #ifndef NO_CACHE
00480                 cachedata = ap_palloc(r->pool, sizeof(mu_cache_data));
00481                 cachedata->name = names->filename;
00482 #endif
00483                 if ((ap_is_directory(names->filename)) ) {
00484                         fn = names->filename + strlen(names->filename);
00485                         *fn++ = '/';
00486                         *fn = '\0';
00487                 
00488                         uri = names->uri + strlen(names->uri) - 1;
00489                         if (*uri++ != '/')
00490                                 *uri++ = '/';
00491                         *uri = '\0';
00492                                 
00493                         if (conf->options & MI_RECURSIVE) {
00494                                 conf->options &= (conf->play_recursive);
00495 
00496                                 if (!(dir = opendir(names->filename)))
00497                                         return head;
00498 #ifndef NO_CACHE
00499                                 if ((conf->cache_path) && (cache_check_dir(r, conf, cachedata)))
00500                                         ap_log_error_old("[musicindex] DBG: cache_check_dir failed", r->server);        /* XXX debug */
00501 #endif
00502                                 while ((dstruct = readdir(dir))) {
00503                                         strcpy(fn, dstruct->d_name);
00504                                         strcpy(uri, dstruct->d_name);
00505                                         p = make_music_entry(pool, r, p, conf, names);  
00506                                 }
00507                                 closedir(dir);
00508                                 fclose(in);
00509                                 return p;
00510                         }
00511                         else if (!(conf->options & MI_PLAYLIST)) {
00512                                 p = new_ent(pool, head);
00513                                 p->filetype = FT_DIR;
00514                         }
00515                         fclose(in);
00516                 }
00517 #ifndef NO_CACHE
00518                 /* from left to right: "is cache enabled?" && "is cache file ok to be read?" && "has cache file correctly been read?" */
00519                 else if ((conf->cache_path) && !(checkresult = cache_check_file(r, conf, cachedata))
00520                         && ((p = cache_read_file(r, head, conf, cachedata)) != head))
00521                                 fclose(in);
00522 #endif
00523                 else if ((p = make_ogg_entry(pool, head, in, conf, names, r)) != head);
00524                 /* ATTENTION! ov_open() takes complete possession of the file,
00525                    and ov_clear() closes it if ov_open() was successful.
00526                    We should NOT use fclose() in that case. */
00527                 else if ((p = make_mp3_entry(pool, head, in, conf, names, r)) != head)
00528                         fclose(in);
00529                 else
00530                         fclose(in);
00531         }
00532 
00533         if (p == head)
00534                 return p;
00535 
00536         p->uri = ap_pstrdup(pool, names->uri);
00537         p->file = p->uri + strlen(r->parsed_uri.path);
00538         
00539         if (p->filetype == FT_DIR)
00540                 return p;
00541 
00542         if (!(p->title)) {
00543 #ifdef NO_TITLE_STRIP
00544                 p->title = p->file;
00545 #else
00546                 /* Copy the name removing file extension and changing '_' to ' ' */
00547                 p->title = ap_pstrndup(pool, p->file, strlen(p->file) - 4);
00548                 for (i=0; p->title[i] != '\0'; i++)
00549                         if (p->title[i] == '_')
00550                                 p->title[i] = ' ';
00551 #endif  /* NO_TITLE_STRIP */
00552                 /* We remove the trailing path if any. basename() ? */
00553                 if (strrchr(p->title, '/')) {
00554                         p->title = strrchr(p->title, '/');
00555                         (p->title)++;
00556                 }
00557 
00558         }
00559         
00560 #ifndef NO_CACHE
00561         /* We put that here so that we do not create cache files for unhandled file types,
00562          * and p->title is formated as we desire */
00563         if (checkresult == CA_CREATE)
00564                 cache_write_file(r, p, conf, cachedata);
00565 #endif
00566         
00567         if (!(conf->options & MI_SEARCH))
00568                 return p;
00569 
00570         /* Here we go if we have an ongoing search request.
00571          * We will return only the entries matching the search criteria */
00572         if (p->file && (ap_strcasestr(p->file, conf->search)))
00573                 return p;
00574         if (p->artist && (ap_strcasestr(p->artist, conf->search)))
00575                 return p;
00576         if (p->album && (ap_strcasestr(p->album, conf->search)))
00577                 return p;
00578         if (p->title && (ap_strcasestr(p->title, conf->search)))
00579                 return p;
00580 
00581         return head;
00582 }
00583 
00592 short musicindex_directory(request_rec * r, mu_config * conf)
00593 {
00594         DIR             *dir;
00595         mu_ent          *head = NULL;
00596 
00597         if (!(dir = ap_popendir(r->pool, r->filename))) {
00598                 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
00599                         "Can't open directory for music index: %s",
00600                         r->filename);
00601                 return HTTP_FORBIDDEN;
00602         }
00603 
00604         r->content_type = "text/html; charset=UTF-8";
00605         ap_send_http_header(r);
00606 
00607         if (r->header_only) {
00608                 ap_pclosedir(r->pool, dir);
00609                 return 0;
00610         }
00611         ap_hard_timeout("send music list", r);
00612 
00613         head = make_music_entry(r->pool, r, NULL, conf, NULL);
00614 
00615         head = quicksort(head, NULL, conf);
00616 
00617         send_head(r, conf);
00618 
00619         if (!(conf->options & MI_SEARCH))
00620                 send_directories(r, head, conf);
00621         
00622         send_tracks(r, head, conf);
00623         
00624         send_foot(r, conf);
00625 
00626         ap_pclosedir(r->pool, dir);
00627 
00628         ap_kill_timeout(r);
00629         return 0;
00630 }
00631 
00643 short playlist_directory(request_rec * r, mu_config * conf)
00644 {
00645         DIR             *dir;
00646         mu_ent          *head = NULL;
00647 
00648         if (!(dir = ap_popendir(r->pool, r->filename)))
00649                 return HTTP_FORBIDDEN;
00650 
00651         r->content_type = "audio/mpegurl";
00652 
00653         ap_send_http_header(r);
00654 
00655         conf->options |= MI_PLAYLIST;
00656 
00657         if (r->header_only) {
00658                 ap_pclosedir(r->pool, dir);
00659                 return 0;
00660         }
00661         ap_hard_timeout("send playlist", r);
00662 
00663         head = make_music_entry(r->pool, r, head, conf, NULL);
00664 
00665         head = quicksort(head, NULL, conf);
00666 
00667         send_playlist(r, head, conf);
00668 
00669         ap_pclosedir(r->pool, dir);
00670 
00671         ap_kill_timeout(r);
00672         return 0;
00673 }
00674 
00691 short playlist_selected(request_rec * r, mu_config * conf)
00692 {
00693         const char              *args = r->args;
00694         char                    *p;
00695         unsigned short          i;
00696 
00697         char                    *fn, *uri;
00698         mu_ent_names            *names;
00699         mu_ent                  *head = NULL;
00700 
00701         r->content_type = "audio/mpegurl";
00702 
00703         ap_send_http_header(r);
00704 
00705         if (r->header_only)
00706                 return 0;
00707         ap_hard_timeout("send playlist", r);
00708 
00709         if ((args == NULL) || (*args == '\0')) {
00710                 ap_kill_timeout(r);
00711                 return 0;
00712         }
00713 
00714         names = ap_palloc(r->pool, sizeof(mu_ent_names));
00715         names->filename = ap_palloc(r->pool, MAX_STRING);
00716         names->uri = ap_palloc(r->pool, MAX_STRING);
00717         strcpy(names->filename, r->filename);
00718         strcpy(names->uri, r->parsed_uri.path);
00719 
00720         fn = names->filename + strlen(names->filename);
00721         *fn++ = '/';
00722         *fn = '\0';
00723 
00724         uri = names->uri + strlen(names->uri) - 1;
00725         if (*uri++ != '/')
00726                 *uri++ = '/';
00727         *uri = '\0';
00728 
00729         while (args[0]){
00730                 p = ap_getword(r->pool, &args, '&');
00731                 if (!strncmp(p, "file=", 5))
00732                 {
00733                         p += 5;
00734                         strcpy(fn, p);
00735                         for (i=0;fn[i]; i++) {
00736                                 if (fn[i] == '+')
00737                                         fn[i] = ' ';
00738                         }
00739                         ap_unescape_url(fn);
00740                         strcpy(uri, fn);
00741                         head = make_music_entry(r->pool, r, head, conf, names);
00742                 }
00743         }
00744 
00745         head = quicksort(head, NULL, conf);
00746         send_playlist(r, head, conf);
00747 
00748         ap_kill_timeout(r);
00749         return 0;
00750 }

Generated on Thu Oct 30 13:50:29 2003 for mod_musicindex by doxygen 1.3.4