Mercurial > audlegacy-plugins
view src/xspf/xspf.c @ 1512:7b1b24af319f
now xspf plugin try to load non well formed xspf file. closes #969.
author | Yoshiki Yazawa <yaz@cc.rim.or.jp> |
---|---|
date | Thu, 23 Aug 2007 03:03:41 +0900 |
parents | 6edbd225b100 |
children | a3e002f56bd6 |
line wrap: on
line source
/* * Audacious: A cross-platform multimedia player * Copyright (c) 2006 William Pitcock, Tony Vroon, George Averill, * Giacomo Lozito, Derek Pomery and Yoshiki Yazawa. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <config.h> #include <glib.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/errno.h> #include "audacious/main.h" #include "audacious/util.h" #include "audacious/playlist.h" #include "audacious/playlist_container.h" #include "audacious/plugin.h" #include <libxml/tree.h> #include <libxml/parser.h> #include <libxml/xmlreader.h> #include <libxml/xpath.h> #include <libxml/xpathInternals.h> #include <libxml/uri.h> #define XSPF_ROOT_NODE_NAME "playlist" #define XSPF_XMLNS "http://xspf.org/ns/0/" #define TMP_BUF_LEN 128 gchar *base = NULL; static gboolean is_uri(gchar *uri) { if(strstr(uri, "://")) return TRUE; else return FALSE; } #if 0 static gboolean is_remote(gchar *uri) { if(strstr(uri, "file://")) return FALSE; if(strstr(uri, "://")) return TRUE; else return FALSE; } #endif // this function is taken from libxml2-2.6.27. static xmlChar *audPathToURI(const xmlChar *path) { xmlURIPtr uri; xmlURI temp; xmlChar *ret, *cal; if(path == NULL) return NULL; if((uri = xmlParseURI((const char *)path)) != NULL) { xmlFreeURI(uri); return xmlStrdup(path); } cal = xmlCanonicPath(path); if(cal == NULL) return NULL; memset(&temp, 0, sizeof(temp)); temp.path = (char *)cal; ret = xmlSaveUri(&temp); xmlFree(cal); return ret; } static void add_file(xmlNode *track, const gchar *filename, gint pos) { xmlNode *nptr; Tuple *tuple; gchar *location = NULL; Playlist *playlist = playlist_get_active(); tuple = tuple_new(); tuple_associate_int(tuple, "length", -1); tuple_associate_int(tuple, "mtime", -1); // mark as uninitialized. // creator, album, title, duration, trackNum, annotation, image, for(nptr = track->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"location")) { gchar *str = (gchar *)xmlNodeGetContent(nptr); gchar *tmp = NULL; // tmp is escaped uri or a part of escaped uri. tmp = g_strdup_printf("%s%s", base ? base : "", str); location = g_filename_from_uri(tmp, NULL, NULL); if(!location) // http:// or something. location = g_strdup(tmp); xmlFree(str); str = NULL; g_free(tmp); tmp = NULL; } else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"title")) { xmlChar *str = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "title", (gchar *) str); xmlFree(str); } else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"creator")) { xmlChar *str = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "artist", (gchar *) str); xmlFree(str); } else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"annotation")) { xmlChar *str = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "comment", (gchar *) str); xmlFree(str); } else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"album")) { xmlChar *str = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "album", (gchar *) str); xmlFree(str); } else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"trackNum")) { xmlChar *str = xmlNodeGetContent(nptr); tuple_associate_int(tuple, "track-number", atol((char *)str)); xmlFree(str); } else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"duration")) { xmlChar *str = xmlNodeGetContent(nptr); tuple_associate_int(tuple, "length", atol((char *)str)); xmlFree(str); } // // additional metadata // // year, date, genre, formatter, mtime // else if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"meta")) { xmlChar *rel = NULL; rel = xmlGetProp(nptr, (xmlChar *)"rel"); if(!xmlStrcmp(rel, (xmlChar *)"year")) { xmlChar *cont = xmlNodeGetContent(nptr); tuple_associate_int(tuple, "year", atol((char *)cont)); xmlFree(cont); continue; } else if(!xmlStrcmp(rel, (xmlChar *)"date")) { xmlChar *cont = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "date", (gchar *) cont); xmlFree(cont); continue; } else if(!xmlStrcmp(rel, (xmlChar *)"genre")) { xmlChar *cont = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "genre", (gchar *) cont); xmlFree(cont); continue; } else if(!xmlStrcmp(rel, (xmlChar *)"formatter")) { xmlChar *cont = xmlNodeGetContent(nptr); tuple_associate_string(tuple, "formatter", (gchar *) cont); xmlFree(cont); continue; } else if(!xmlStrcmp(rel, (xmlChar *)"mtime")) { xmlChar *str = NULL; str = xmlNodeGetContent(nptr); tuple_associate_int(tuple, "mtime", atoll((char *)str)); xmlFree(str); continue; } xmlFree(rel); rel = NULL; } } if(location) { gchar *uri = NULL; gchar *scratch; scratch = g_path_get_basename(location); tuple_associate_string(tuple, "file-name", scratch); g_free(scratch); scratch = g_path_get_dirname(location); tuple_associate_string(tuple, "file-path", scratch); g_free(scratch); #ifdef DEBUG printf("xspf: tuple->file_name = %s\n", tuple_get_string(tuple, "file-name")); printf("xspf: tuple->file_path = %s\n", tuple_get_string(tuple, "file-path")); #endif tuple_associate_string(tuple, "file-ext", strrchr(location, '.')); // add file to playlist uri = g_filename_to_uri(location, NULL, NULL); // uri would be NULL if location is already uri. --yaz playlist_load_ins_file_tuple(playlist, uri ? uri: location, filename, pos, tuple); g_free(uri); uri = NULL; pos++; } g_free(location); location = NULL; } static void find_track(xmlNode *tracklist, const gchar *filename, gint pos) { xmlNode *nptr; for(nptr = tracklist->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"track")) { add_file(nptr, filename, pos); } } } static void find_audoptions(xmlNode *tracklist, const gchar *filename, gint pos) { xmlNode *nptr; Playlist *playlist = playlist_get_active(); for(nptr = tracklist->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"options")) { xmlChar *opt = NULL; opt = xmlGetProp(nptr, (xmlChar *)"staticlist"); if(!strcasecmp((char *)opt, "true")) { playlist->attribute |= PLAYLIST_STATIC; } else playlist->attribute ^= PLAYLIST_STATIC; xmlFree(opt); opt = NULL; } } } static void playlist_load_xspf(const gchar *filename, gint pos) { xmlDocPtr doc; xmlNode *nptr, *nptr2; gchar *tmp = NULL; g_return_if_fail(filename != NULL); #ifdef DEBUG printf("playlist_load_xspf: filename = %s\n", filename); #endif doc = xmlRecoverFile(filename); if(doc == NULL) return; xmlFree(base); base = NULL; // find trackList for(nptr = doc->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"playlist")) { base = (gchar *)xmlNodeGetBase(doc, nptr); #ifdef DEBUG printf("playlist_load_xspf: base @1 = %s\n", base); #endif // if filename is specified as a base, ignore it. tmp = xmlURIUnescapeString(base, -1, NULL); if(tmp) { if(!strcmp(tmp, filename)) { xmlFree(base); base = NULL; } g_free(tmp); tmp = NULL; } #ifdef DEBUG printf("playlist_load_xspf: base @2 = %s\n", base); #endif for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) { if(nptr2->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr2->name, (xmlChar *)"extension")) { //check if application is audacious xmlChar *app = NULL; app = xmlGetProp(nptr2, (xmlChar *)"application"); if(!xmlStrcmp(app, (xmlChar *)"audacious")) { find_audoptions(nptr2, filename, pos); } xmlFree(app); } if(nptr2->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr2->name, (xmlChar *)"trackList")) { find_track(nptr2, filename, pos); } } } } xmlFreeDoc(doc); } static void playlist_save_xspf(const gchar *filename, gint pos) { xmlDocPtr doc; xmlNodePtr rootnode, tmp, tracklist; GList *node; gint baselen = 0; Playlist *playlist = playlist_get_active(); #ifdef DEBUG printf("playlist_save_xspf: filename = %s\n", filename); #endif xmlFree(base); base = NULL; doc = xmlNewDoc((xmlChar *)"1.0"); doc->charset = XML_CHAR_ENCODING_UTF8; doc->encoding = xmlStrdup((xmlChar *)"UTF-8"); rootnode = xmlNewNode(NULL, (xmlChar *)XSPF_ROOT_NODE_NAME); xmlSetProp(rootnode, (xmlChar *)"version", (xmlChar *)"1"); xmlSetProp(rootnode, (xmlChar *)"xmlns", (xmlChar *)XSPF_XMLNS); PLAYLIST_LOCK(playlist->mutex); /* relative */ if(playlist->attribute & PLAYLIST_USE_RELATIVE) { /* prescan to determine base uri */ for(node = playlist->entries; node != NULL; node = g_list_next(node)) { gchar *ptr1, *ptr2; PlaylistEntry *entry = PLAYLIST_ENTRY(node->data); gchar *tmp; gint tmplen = 0; if(!is_uri(entry->filename)) { //obsolete gchar *tmp2; tmp2 = g_path_get_dirname(entry->filename); tmp = g_strdup_printf("%s/", tmp2); g_free(tmp2); tmp2 = NULL; } else { //uri tmp = g_strdup(entry->filename); } if(!base) { base = strdup(tmp); baselen = strlen(base); } ptr1 = base; ptr2 = tmp; while(ptr1 && ptr2 && *ptr1 && *ptr2 && *ptr1 == *ptr2) { ptr1++; ptr2++; } *ptr2 = '\0'; //terminate tmplen = ptr2 - tmp; if(tmplen <= baselen) { g_free(base); base = tmp; baselen = tmplen; #ifdef DEBUG printf("base = \"%s\" baselen = %d\n", base, baselen); #endif } else { g_free(tmp); tmp = NULL; } } /* set base URI */ if(base) { gchar *tmp; if(!is_uri(base)) { tmp = (gchar *)audPathToURI((xmlChar *)base); if(tmp) { g_free(base); base = tmp; } } if(!is_uri(base)) { #ifdef DEBUG printf("base is not uri. something is wrong.\n"); #endif tmp = g_strdup_printf("file://%s", base); xmlSetProp(rootnode, (xmlChar *)"xml:base", (xmlChar *)tmp); g_free(tmp); tmp = NULL; } else xmlSetProp(rootnode, (xmlChar *)"xml:base", (xmlChar *)base); } } /* USE_RELATIVE */ /* common */ xmlDocSetRootElement(doc, rootnode); tmp = xmlNewNode(NULL, (xmlChar *)"creator"); xmlAddChild(tmp, xmlNewText((xmlChar *)PACKAGE "-" VERSION)); xmlAddChild(rootnode, tmp); // add staticlist marker if(playlist->attribute & PLAYLIST_STATIC) { xmlNodePtr extension, options; extension = xmlNewNode(NULL, (xmlChar *)"extension"); xmlSetProp(extension, (xmlChar *)"application", (xmlChar *)"audacious"); options = xmlNewNode(NULL, (xmlChar *)"options"); xmlSetProp(options, (xmlChar *)"staticlist", (xmlChar *)"true"); xmlAddChild(extension, options); xmlAddChild(rootnode, extension); } tracklist = xmlNewNode(NULL, (xmlChar *)"trackList"); xmlAddChild(rootnode, tracklist); for(node = playlist->entries; node != NULL; node = g_list_next(node)) { PlaylistEntry *entry = PLAYLIST_ENTRY(node->data); xmlNodePtr track, location; gchar *filename = NULL; track = xmlNewNode(NULL, (xmlChar *)"track"); location = xmlNewNode(NULL, (xmlChar *)"location"); if(is_uri(entry->filename)) { /* uri */ #ifdef DEBUG printf("filename is uri\n"); #endif filename = g_strdup(entry->filename + baselen); // entry->filename is always uri now. } else { /* local file (obsolete) */ gchar *tmp = (gchar *)audPathToURI((const xmlChar *)entry->filename + baselen); if(base) { /* relative */ filename = g_strdup_printf("%s", tmp); } else { #ifdef DEBUG printf("absolute and local (obsolete)\n"); #endif filename = g_filename_to_uri(tmp, NULL, NULL); } g_free(tmp); tmp = NULL; } /* obsolete */ if(!g_utf8_validate(filename, -1, NULL)) continue; xmlAddChild(location, xmlNewText((xmlChar *)filename)); xmlAddChild(track, location); xmlAddChild(tracklist, track); /* do we have a tuple? */ if(entry->tuple != NULL) { const gchar *scratch; if((scratch = tuple_get_string(entry->tuple, "title")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"title"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } if((scratch = tuple_get_string(entry->tuple, "artist")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"creator"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } if((scratch = tuple_get_string(entry->tuple, "comment")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"annotation"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } if((scratch = tuple_get_string(entry->tuple, "album")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"album"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } if(tuple_get_int(entry->tuple, "track-number") != 0) { gchar *str; str = g_malloc(TMP_BUF_LEN); tmp = xmlNewNode(NULL, (xmlChar *)"trackNum"); sprintf(str, "%d", tuple_get_int(entry->tuple, "track-number")); xmlAddChild(tmp, xmlNewText((xmlChar *)str)); g_free(str); str = NULL; xmlAddChild(track, tmp); } if(tuple_get_int(entry->tuple, "length") > 0) { gchar *str; str = g_malloc(TMP_BUF_LEN); tmp = xmlNewNode(NULL, (xmlChar *)"duration"); sprintf(str, "%d", tuple_get_int(entry->tuple, "length")); xmlAddChild(tmp, xmlNewText((xmlChar *) str)); g_free(str); str = NULL; xmlAddChild(track, tmp); } // // additional metadata // // year, date, genre, formatter, mtime // if(tuple_get_int(entry->tuple, "year") != 0) { gchar *str; str = g_malloc(TMP_BUF_LEN); tmp = xmlNewNode(NULL, (xmlChar *)"meta"); xmlSetProp(tmp, (xmlChar *)"rel", (xmlChar *)"year"); sprintf(str, "%d", tuple_get_int(entry->tuple, "year")); xmlAddChild(tmp, xmlNewText((xmlChar *)str)); xmlAddChild(track, tmp); g_free(str); str = NULL; } if((scratch = tuple_get_string(entry->tuple, "date")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"meta"); xmlSetProp(tmp, (xmlChar *)"rel", (xmlChar *)"date"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } if((scratch = tuple_get_string(entry->tuple, "genre")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"meta"); xmlSetProp(tmp, (xmlChar *)"rel", (xmlChar *)"genre"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } if((scratch = tuple_get_string(entry->tuple, "formatter")) != NULL && g_utf8_validate(scratch, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"meta"); xmlSetProp(tmp, (xmlChar *)"rel", (xmlChar *)"formatter"); xmlAddChild(tmp, xmlNewText((xmlChar *) scratch)); xmlAddChild(track, tmp); } // mtime: write mtime unconditionally to support staticlist. { gchar *str = g_malloc(TMP_BUF_LEN); tmp = xmlNewNode(NULL, (xmlChar *)"meta"); xmlSetProp(tmp, (xmlChar *)"rel", (xmlChar *)"mtime"); sprintf(str, "%ld", (long) tuple_get_int(entry->tuple, "mtime")); xmlAddChild(tmp, xmlNewText((xmlChar *)str)); xmlAddChild(track, tmp); g_free(str); str = NULL; } } /* tuple */ else { if(entry->title != NULL && g_utf8_validate(entry->title, -1, NULL)) { tmp = xmlNewNode(NULL, (xmlChar *)"title"); xmlAddChild(tmp, xmlNewText((xmlChar *)entry->title)); xmlAddChild(track, tmp); } if(entry->length > 0) { gchar *str; str = g_malloc(TMP_BUF_LEN); tmp = xmlNewNode(NULL, (xmlChar *)"duration"); sprintf(str, "%d", entry->length); xmlAddChild(tmp, xmlNewText((xmlChar *)str)); g_free(str); str = NULL; xmlAddChild(track, tmp); } /* add mtime of -1 */ { gchar *str = g_malloc(TMP_BUF_LEN); tmp = xmlNewNode(NULL, (xmlChar *)"meta"); xmlSetProp(tmp, (xmlChar *)"rel", (xmlChar *)"mtime"); sprintf(str, "%ld", -1L); xmlAddChild(tmp, xmlNewText((xmlChar *)str)); xmlAddChild(track, tmp); g_free(str); str = NULL; } } /* no tuple */ g_free(filename); filename = NULL; } PLAYLIST_UNLOCK(playlist->mutex); xmlSaveFormatFile(filename, doc, 1); xmlFreeDoc(doc); doc = NULL; xmlFree(base); base = NULL; } PlaylistContainer plc_xspf = { .name = "XSPF Playlist Format", .ext = "xspf", .plc_read = playlist_load_xspf, .plc_write = playlist_save_xspf, }; static void init(void) { playlist_container_register(&plc_xspf); } static void cleanup(void) { playlist_container_unregister(&plc_xspf); } DECLARE_PLUGIN(xspf, init, cleanup, NULL, NULL, NULL, NULL, NULL, NULL);