From 2bb3bbe7d54adca7ba54d7bced5ee36790aacfa5 Mon Sep 17 00:00:00 2001 From: Ron Pedde Date: Tue, 17 May 2005 04:26:20 +0000 Subject: [PATCH] Update to use rxml rather than ezxml --- src/scan-xml.c | 576 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 398 insertions(+), 178 deletions(-) diff --git a/src/scan-xml.c b/src/scan-xml.c index 416e1704..66837d3e 100644 --- a/src/scan-xml.c +++ b/src/scan-xml.c @@ -31,19 +31,84 @@ #include "db-generic.h" #include "err.h" -#include "ezxml.h" #include "mp3-scanner.h" +#include "rxml.h" /* Forwards */ int scan_xml_playlist(char *filename); - +void scan_xml_handler(int action,void* puser,char* info); +int scan_xml_preamble_section(int action,char *info); +int scan_xml_tracks_section(int action,char *info); +int scan_xml_playlists_section(int action,char *info); /* Globals */ static char *scan_xml_itunes_version = NULL; static char *scan_xml_itunes_base_path = NULL; +static char *scan_xml_itunes_decoded_base_path = NULL; static char *scan_xml_real_base_path = NULL; #define MAYBECOPY(a) if(!mp3.a) mp3.a = pmp3->a +#define MAYBEFREE(a) if((a)) free((a)) + +static char *scan_xml_track_tags[] = { + "Name", + "Artist", + "Album", + "Genre", + "Total Time", + "Track Number", + "Track Count", + "Year", + "Bit Rate", + "Sample Rate", + "Play Count", + "Rating", + "Disabled", + "Disc Number", + "Disc Count", + "Compilation", + "Location", + NULL +}; + +#define SCAN_XML_T_UNKNOWN -1 +#define SCAN_XML_T_NAME 0 +#define SCAN_XML_T_ARTIST 1 +#define SCAN_XML_T_ALBUM 2 +#define SCAN_XML_T_GENRE 3 +#define SCAN_XML_T_TOTALTIME 4 +#define SCAN_XML_T_TRACKNUMBER 5 +#define SCAN_XML_T_TRACKCOUNT 6 +#define SCAN_XML_T_YEAR 7 +#define SCAN_XML_T_BITRATE 8 +#define SCAN_XML_T_SAMPLERATE 9 +#define SCAN_XML_T_PLAYCOUNT 10 +#define SCAN_XML_T_RATING 11 +#define SCAN_XML_T_DISABLED 12 +#define SCAN_XML_T_DISCNO 13 +#define SCAN_XML_T_DISCCOUNT 14 +#define SCAN_XML_T_COMPILATION 15 +#define SCAN_XML_T_LOCATION 16 + +/** + * get the tag index of a particular tag + * + * @param tag tag to determine tag index for + */ +int scan_xml_get_tagindex(char *tag) { + char **ptag = scan_xml_track_tags; + int index=0; + + while(*ptag && (strcasecmp(tag,*ptag) != 0)) { + ptag++; + index++; + } + + if(*ptag) + return index; + + return SCAN_XML_T_UNKNOWN; +} /** * urldecode a string, returning a string pointer which must @@ -104,161 +169,19 @@ char *scan_xml_urldecode(char *string, int space_as_plus) { return pnew; } -/** - * give an ezxml_t node for the Tracks dictionary, walk though - * and update metainfo - * - * \param tracksdict ezxml node for the tracks dictionary - */ -int scan_xml_process_tracks(ezxml_t tracksdict) { - char *base_path; - char *song_path=NULL; - char physical_path[PATH_MAX]; - char real_path[PATH_MAX]; - ezxml_t track,songdict,songkey,value; - MP3FILE mp3; - MP3FILE *pmp3; - - /* urldecode the base_path */ - base_path=scan_xml_urldecode(scan_xml_itunes_base_path,0); - - for(track=ezxml_child(tracksdict,"key"); track; track=track->next) { - memset((void*)&mp3,0x00,sizeof(mp3)); - if(song_path) - free(song_path); - song_path = NULL; - - songdict=track->ordered; - DPRINTF(E_DBG,L_SCAN,"Found track %s\n",track->txt); - - for(songkey=ezxml_child(songdict,"key"); songkey; songkey=songkey->next) { - /* walking through the song elements, hanging the data on the mp3 file */ - value=songkey->ordered; - if(!strcasecmp(songkey->txt,"Name")) { - mp3.title = value->txt; - } else if(!strcasecmp(songkey->txt,"Artist")) { - mp3.artist = value->txt; - } else if(!strcasecmp(songkey->txt,"Album")) { - mp3.album = value->txt; - } else if(!strcasecmp(songkey->txt,"Genre")) { - mp3.genre = value->txt; - } else if(!strcasecmp(songkey->txt,"Total Time")) { - mp3.song_length = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Track Number")) { - mp3.track = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Track Count")) { - mp3.total_tracks = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Year")) { - mp3.year = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Bit Rate")) { - mp3.bitrate = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Sample Rate")) { - mp3.samplerate = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Play Count")) { - mp3.play_count = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Rating")) { - mp3.rating = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Disabled")) { - mp3.disabled=1; - } else if(!strcasecmp(songkey->txt,"Disc Number")) { - mp3.disc = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Disc Count")) { - mp3.total_discs = atoi(value->txt); - } else if(!strcasecmp(songkey->txt,"Compilation")) { - mp3.compilation=1; - } else if(!strcasecmp(songkey->txt,"Location")) { - song_path = scan_xml_urldecode(value->txt,0); - } - } - - DPRINTF(E_DBG,L_SCAN,"Found track at path %s\n",song_path); - - /* song is parsed, now see if we can find a corresponding song */ - if(song_path && (strlen(song_path) > strlen(base_path))) { - sprintf(physical_path,"%siTunes Music/%s",scan_xml_real_base_path, - (char*)&song_path[strlen(base_path)]); - realpath(physical_path,real_path); - - DPRINTF(E_DBG,L_SCAN,"Real Path: %s\n", real_path); - - /* now we have the real path -- is it in the database? */ - pmp3=db_fetch_path(real_path); - if(pmp3) { - /* yup... let's update it with the iTunes info */ - mp3.path = pmp3->path; - mp3.fname = pmp3->fname; - - MAYBECOPY(title); - MAYBECOPY(artist); - MAYBECOPY(album); - MAYBECOPY(genre); - MAYBECOPY(comment); - MAYBECOPY(type); - MAYBECOPY(composer); - MAYBECOPY(orchestra); - MAYBECOPY(conductor); - MAYBECOPY(grouping); - MAYBECOPY(url); - MAYBECOPY(bitrate); - MAYBECOPY(samplerate); - MAYBECOPY(song_length); - MAYBECOPY(file_size); - MAYBECOPY(year); - MAYBECOPY(track); - MAYBECOPY(total_tracks); - MAYBECOPY(disc); - MAYBECOPY(total_discs); - MAYBECOPY(time_added); - MAYBECOPY(time_modified); - MAYBECOPY(time_played); - MAYBECOPY(play_count); - MAYBECOPY(rating); - MAYBECOPY(db_timestamp); - MAYBECOPY(disabled); - MAYBECOPY(bpm); - MAYBECOPY(id); - MAYBECOPY(description); - MAYBECOPY(codectype); - MAYBECOPY(item_kind); - MAYBECOPY(data_kind); - MAYBECOPY(force_update); - MAYBECOPY(sample_count); - MAYBECOPY(compilation); - - db_add(&mp3); - db_dispose_item(pmp3); - } - } - } - - free(base_path); - if(song_path) - free(song_path); - return 0; -} - /** * scan an iTunes xml music database file, augmenting * the metainfo with that found in the xml file */ int scan_xml_playlist(char *filename) { - ezxml_t itpl,maindict,key,value; char *working_base; + RXMLHANDLE xml_handle; - if(scan_xml_itunes_version) { - free(scan_xml_itunes_version); - scan_xml_itunes_version = NULL; - } - - if(scan_xml_itunes_base_path) { - free(scan_xml_itunes_base_path); - scan_xml_itunes_base_path = NULL; - } + MAYBEFREE(scan_xml_itunes_version); + MAYBEFREE(scan_xml_itunes_base_path); + MAYBEFREE(scan_xml_itunes_decoded_base_path); + MAYBEFREE(scan_xml_real_base_path); - if(scan_xml_real_base_path) { - free(scan_xml_real_base_path); - scan_xml_real_base_path = NULL; - } /* find the base dir of the itunes playlist itself */ working_base = strdup(filename); @@ -272,38 +195,335 @@ int scan_xml_playlist(char *filename) { DPRINTF(E_SPAM,L_SCAN,"Parsing xml file: %s\n",filename); - itpl = ezxml_parse_file(filename); - - if(itpl == NULL) { - return -1; - } - - DPRINTF(E_SPAM,L_SCAN,"File parsed... processing\n"); - maindict = ezxml_child(itpl,"dict"); - - /* we are parsing a dict entry -- this will be a seriese of key/value pairs */ - for(key = ezxml_child(maindict,"key"); key; key=key->next) { - DPRINTF(E_SPAM,L_SCAN,"Found key %s\n",key->txt); - value = key->ordered; - if(!value) { /* badly formed xml file */ - ezxml_free(itpl); - return -1; - } - - if(!scan_xml_itunes_version && (strcasecmp(key->txt,"Application Version") == 0)) { - scan_xml_itunes_version=strdup(value->txt); - DPRINTF(E_DBG,L_SCAN,"iTunes Version %s\n",scan_xml_itunes_version); - } else if (!scan_xml_itunes_base_path && (strcasecmp(key->txt,"Music Folder") == 0)) { - scan_xml_itunes_base_path=strdup(value->txt); - DPRINTF(E_DBG,L_SCAN,"iTunes base path: %s\n",scan_xml_itunes_base_path); - } else if (strcasecmp(key->txt,"Tracks") == 0) { - scan_xml_process_tracks(value); - } else if (strcasecmp(key->txt,"Playlists") == 0) { - DPRINTF(E_DBG,L_SCAN,"Skipping iTunes playlists.... for now\n"); + if(!rxml_open(&xml_handle,filename,scan_xml_handler,NULL)) { + DPRINTF(E_LOG,L_SCAN,"Error opening xml file %s: %s\n", + filename,rxml_errorstring(xml_handle)); + } else { + if(!rxml_parse(xml_handle)) { + DPRINTF(E_LOG,L_SCAN,"Error parsing xml file %s: %s\n", + filename,rxml_errorstring(xml_handle)); } } - ezxml_free(itpl); + rxml_close(xml_handle); return 0; } + +#define XML_STATE_PREAMBLE 0 +#define XML_STATE_TRACKS 1 +#define XML_STATE_PLAYLISTS 2 +#define XML_STATE_ERROR 3 + + +/** + * handle new xml events, and dispatch it to the + * appropriate handler. This is a callback from the + * xml parser. + * + * @param action what event (RXML_EVT_OPEN, etc) + * @param puser opaqe data object passed in open (unused) + * @param info char data associated with the event + */ +void scan_xml_handler(int action,void* puser,char* info) { + static int state; + + switch(action) { + case RXML_EVT_OPEN: /* file opened */ + state = XML_STATE_PREAMBLE; + /* send this event to all dispatches to allow them + * to reset + */ + scan_xml_preamble_section(action,info); + scan_xml_tracks_section(action,info); + scan_xml_playlists_section(action,info); + break; + case RXML_EVT_BEGIN: + case RXML_EVT_END: + case RXML_EVT_TEXT: + switch(state) { + case XML_STATE_PREAMBLE: + state=scan_xml_preamble_section(action,info); + break; + case XML_STATE_TRACKS: + state=scan_xml_tracks_section(action,info); + break; + case XML_STATE_PLAYLISTS: + state=scan_xml_playlists_section(action,info); + break; + default: + break; + } + default: + break; + } +} + +#define SCAN_XML_PRE_NOTHING 0 +#define SCAN_XML_PRE_VERSION 1 +#define SCAN_XML_PRE_PATH 2 +#define SCAN_XML_PRE_DONE 3 + +/** + * collect preamble data... version, library id, etc. + * + * @param action xml action (RXML_EVT_TEXT, etc) + * @param info text data associated with event + */ +int scan_xml_preamble_section(int action, char *info) { + static int expecting_next; + static int done; + + switch(action) { + case RXML_EVT_OPEN: /* initialization */ + expecting_next=0; + done=0; + break; + + case RXML_EVT_END: + if(expecting_next == SCAN_XML_PRE_DONE) /* end of tracks tag */ + return XML_STATE_TRACKS; + break; + + case RXML_EVT_TEXT: /* scan for the tags we expect */ + if(!expecting_next) { + if(strcmp(info,"Application Version") == 0) { + expecting_next = SCAN_XML_PRE_VERSION; + } else if (strcmp(info,"Music Folder") == 0) { + expecting_next = SCAN_XML_PRE_PATH; + } else if (strcmp(info,"Tracks") == 0) { + expecting_next = SCAN_XML_PRE_DONE; + } + } else { + /* we were expecting someting! */ + switch(expecting_next) { + case SCAN_XML_PRE_VERSION: + if(!scan_xml_itunes_version) { + scan_xml_itunes_version=strdup(info); + DPRINTF(E_DBG,L_SCAN,"iTunes Version: %s\n",info); + } + break; + case SCAN_XML_PRE_PATH: + if(!scan_xml_itunes_base_path) { + scan_xml_itunes_base_path=strdup(info); + scan_xml_itunes_decoded_base_path=scan_xml_urldecode(info,0); + DPRINTF(E_DBG,L_SCAN,"iTunes base path: %s\n",info); + } + break; + default: + break; + } + expecting_next=0; + } + break; /* RXML_EVT_TEXT */ + + default: + break; + } + + return XML_STATE_PREAMBLE; +} + + +#define XML_TRACK_ST_INITIAL 0 +#define XML_TRACK_ST_MAIN_DICT 1 +#define XML_TRACK_ST_EXPECTING_TRACK_ID 2 +#define XML_TRACK_ST_EXPECTING_TRACK_DICT 3 +#define XML_TRACK_ST_TRACK_INFO 4 +#define XML_TRACK_ST_TRACK_DATA 5 +#define XML_TRACK_ST_EXPECTING_PLAYLISTS 6 + +/** + * collect track data for each track in the itunes playlist + * + * @param action xml action (RXML_EVT_TEXT, etc) + * @param info text data associated with event + */ +#define MAYBESETSTATE(a,b,c) { if((action==(a)) && \ + (strcmp(info,(b)) == 0)) { \ + state = (c); \ + DPRINTF(E_SPAM,L_SCAN,"New state: %d\n",state); \ + return XML_STATE_TRACKS; \ + }} + +int scan_xml_tracks_section(int action, char *info) { + static int state; + static int current_track_id; + static int current_field; + static MP3FILE mp3; + static char *song_path; + char physical_path[PATH_MAX]; + char real_path[PATH_MAX]; + MP3FILE *pmp3; + + if(action == RXML_EVT_OPEN) { + state = XML_TRACK_ST_INITIAL; + memset((void*)&mp3,0,sizeof(MP3FILE)); + song_path = NULL; + return 0; + } + + /* walk through the states */ + switch(state) { + case XML_TRACK_ST_INITIAL: + /* expection only a */ + MAYBESETSTATE(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_MAIN_DICT); + return XML_STATE_ERROR; + break; + + case XML_TRACK_ST_MAIN_DICT: + /* either get a , or a */ + MAYBESETSTATE(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); + MAYBESETSTATE(RXML_EVT_END,"dict",XML_TRACK_ST_EXPECTING_PLAYLISTS); + return XML_STATE_ERROR; + break; + + case XML_TRACK_ST_EXPECTING_TRACK_ID: + /* this is somewhat loose - id */ + MAYBESETSTATE(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); + MAYBESETSTATE(RXML_EVT_END,"key",XML_TRACK_ST_EXPECTING_TRACK_DICT); + if (action == RXML_EVT_TEXT) { + current_track_id = atoi(info); + DPRINTF(E_DBG,L_SCAN,"Scanning iTunes id #%d\n",current_track_id); + } else { + return XML_STATE_ERROR; + } + break; + + case XML_TRACK_ST_EXPECTING_TRACK_DICT: + /* waiting for a dict */ + MAYBESETSTATE(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_TRACK_INFO); + return XML_STATE_ERROR; + break; + + case XML_TRACK_ST_TRACK_INFO: + /* again, kind of loose */ + MAYBESETSTATE(RXML_EVT_BEGIN,"key",XML_TRACK_ST_TRACK_INFO); + MAYBESETSTATE(RXML_EVT_END,"key",XML_TRACK_ST_TRACK_DATA); + if(action == RXML_EVT_TEXT) { + current_field=scan_xml_get_tagindex(info); + if(current_field == SCAN_XML_T_DISABLED) { + mp3.disabled = 1; + } else if(current_field == SCAN_XML_T_COMPILATION) { + mp3.compilation = 1; + } + } else if((action == RXML_EVT_END) && (strcmp(info,"dict")==0)) { + state = XML_TRACK_ST_MAIN_DICT; + /* but more importantly, we gotta process the track */ + if(song_path && (strlen(song_path) > + strlen(scan_xml_itunes_decoded_base_path))) { + sprintf(physical_path,"%siTunes Music/%s", + scan_xml_real_base_path, + (char*)&song_path[strlen(scan_xml_itunes_decoded_base_path)]); + realpath(physical_path,real_path); + pmp3=db_fetch_path(real_path); + if(pmp3) { + MAYBECOPY(title); + MAYBECOPY(artist); + MAYBECOPY(album); + MAYBECOPY(genre); + MAYBECOPY(comment); + MAYBECOPY(type); + MAYBECOPY(composer); + MAYBECOPY(orchestra); + MAYBECOPY(conductor); + MAYBECOPY(grouping); + MAYBECOPY(url); + MAYBECOPY(bitrate); + MAYBECOPY(samplerate); + MAYBECOPY(song_length); + MAYBECOPY(file_size); + MAYBECOPY(year); + MAYBECOPY(track); + MAYBECOPY(total_tracks); + MAYBECOPY(disc); + MAYBECOPY(total_discs); + MAYBECOPY(time_added); + MAYBECOPY(time_modified); + MAYBECOPY(time_played); + MAYBECOPY(play_count); + MAYBECOPY(rating); + MAYBECOPY(db_timestamp); + MAYBECOPY(disabled); + MAYBECOPY(bpm); + MAYBECOPY(id); + MAYBECOPY(description); + MAYBECOPY(codectype); + MAYBECOPY(item_kind); + MAYBECOPY(data_kind); + MAYBECOPY(force_update); + MAYBECOPY(sample_count); + MAYBECOPY(compilation); + + db_add(&mp3); + db_dispose_item(pmp3); + } + } + MAYBEFREE(mp3.title); + MAYBEFREE(mp3.artist); + MAYBEFREE(mp3.album); + MAYBEFREE(mp3.genre); + MAYBEFREE(song_path); + } else { + return XML_STATE_ERROR; + } + break; + + case XML_TRACK_ST_TRACK_DATA: + if(action == RXML_EVT_BEGIN) { + break; + } else if(action == RXML_EVT_TEXT) { + if(current_field == SCAN_XML_T_NAME) { + mp3.title = strdup(info); + } else if(current_field == SCAN_XML_T_ARTIST) { + mp3.artist = strdup(info); + } else if(current_field == SCAN_XML_T_ALBUM) { + mp3.album = strdup(info); + } else if(current_field == SCAN_XML_T_GENRE) { + mp3.genre = strdup(info); + } else if(current_field == SCAN_XML_T_TOTALTIME) { + mp3.song_length = atoi(info); + } else if(current_field == SCAN_XML_T_TRACKNUMBER) { + mp3.track = atoi(info); + } else if(current_field == SCAN_XML_T_TRACKCOUNT) { + mp3.total_tracks = atoi(info); + } else if(current_field == SCAN_XML_T_YEAR) { + mp3.year = atoi(info); + } else if(current_field == SCAN_XML_T_BITRATE) { + mp3.bitrate = atoi(info); + } else if(current_field == SCAN_XML_T_SAMPLERATE) { + mp3.samplerate = atoi(info); + } else if(current_field == SCAN_XML_T_PLAYCOUNT) { + mp3.play_count = atoi(info); + } else if(current_field == SCAN_XML_T_RATING) { + mp3.rating = atoi(info); + } else if(current_field == SCAN_XML_T_DISCNO) { + mp3.disc = atoi(info); + } else if(current_field == SCAN_XML_T_DISCCOUNT) { + mp3.total_discs = atoi(info); + } else if(current_field == SCAN_XML_T_LOCATION) { + song_path = scan_xml_urldecode(info,0); + } + } else if(action == RXML_EVT_END) { + state = XML_TRACK_ST_TRACK_INFO; + } else { + return XML_STATE_ERROR; + } + break; + default: + return XML_STATE_ERROR; + } + + return XML_STATE_TRACKS; +} + +/** + * collect playlist data for each playlist in the itunes xml file + * + * @param action xml action (RXML_EVT_TEXT, etc) + * @param info text data associated with event + */ +int scan_xml_playlists_section(int action, char *info) { + return XML_STATE_PLAYLISTS; +} +