From 8e8e64419dbc027b464e227b7d1b4f6056394ebb Mon Sep 17 00:00:00 2001 From: Antoine Cellerier Date: Sat, 11 Mar 2006 23:19:59 +0000 Subject: [PATCH] src/playlist/loadsave.c, modules/demux/playlist/*, modules/gui/*, modules/misc/playlist/*, modules/access/http.c: XSPF playlist support (read and write) by Daniel Stranger. Many thanks src/misc/modules.c, src/misc/strings.c, include/vlc_strings.h: string handling functions. modules/control/http/*, modules/services_discovery/upnp_intel.cpp: use these string handling functions. --- include/vlc_strings.h | 44 ++ include/vlc_symbols.h | 12 + modules/access/http.c | 3 + modules/control/http/rpn.c | 38 +- modules/control/http/util.c | 34 +- modules/demux/playlist/Modules.am | 22 +- modules/demux/playlist/playlist.c | 5 + modules/demux/playlist/playlist.h | 2 + modules/demux/playlist/xspf.c | 682 ++++++++++++++++ modules/demux/playlist/xspf.h | 62 ++ modules/gui/skins2/src/dialogs.cpp | 6 +- modules/gui/wxwidgets/dialogs/playlist.cpp | 6 +- modules/misc/playlist/Modules.am | 10 +- modules/misc/playlist/export.c | 8 +- modules/misc/playlist/xspf.c | 295 +++++++ modules/misc/playlist/xspf.h | 37 + modules/services_discovery/upnp_intel.cpp | 861 ++++++++++----------- src/Makefile.am | 2 + src/misc/modules.c | 1 + src/misc/strings.c | 179 +++++ src/playlist/loadsave.c | 9 +- 21 files changed, 1773 insertions(+), 545 deletions(-) create mode 100644 include/vlc_strings.h create mode 100644 modules/demux/playlist/xspf.c create mode 100644 modules/demux/playlist/xspf.h create mode 100644 modules/misc/playlist/xspf.c create mode 100644 modules/misc/playlist/xspf.h create mode 100644 src/misc/strings.c diff --git a/include/vlc_strings.h b/include/vlc_strings.h new file mode 100644 index 0000000000..c70a5edea0 --- /dev/null +++ b/include/vlc_strings.h @@ -0,0 +1,44 @@ +/***************************************************************************** + * vlc_strings.h: String functions + ***************************************************************************** + * Copyright (C) 2006 the VideoLAN team + * $Id$ + * + * Authors: Antoine Cellerier + * + * 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. + *****************************************************************************/ + +#ifndef _VLC_STRINGS_H +#define _VLC_STRINGS_H 1 + +#include + +/** + * \defgroup strings Strings + * @{ + */ + +VLC_EXPORT( char *, decode_encoded_URI_duplicate, ( const char *psz ) ); +VLC_EXPORT( void, decode_encoded_URI, ( char *psz ) ); + +VLC_EXPORT( void, resolve_xml_special_chars, ( char *psz_value ) ); +VLC_EXPORT( char *, convert_xml_special_chars, ( const char *psz_content ) ); + +/** + * @} + */ + +#endif diff --git a/include/vlc_symbols.h b/include/vlc_symbols.h index 8099fb4254..7a756eea7f 100644 --- a/include/vlc_symbols.h +++ b/include/vlc_symbols.h @@ -480,6 +480,10 @@ struct module_symbols_t double (*us_atof_inner) (const char *); double (*us_strtod_inner) (const char *, char **); lldiv_t (*vlc_lldiv_inner) (long long numer, long long denom); + void (*decode_encoded_URI_inner) (char *psz); + char * (*convert_xml_special_chars_inner) (const char *psz_content); + char * (*decode_encoded_URI_duplicate_inner) (const char *psz); + void (*resolve_xml_special_chars_inner) (char *psz_value); }; # if defined (__PLUGIN__) # define aout_FiltersCreatePipeline (p_symbols)->aout_FiltersCreatePipeline_inner @@ -940,6 +944,10 @@ struct module_symbols_t # define us_atof (p_symbols)->us_atof_inner # define us_strtod (p_symbols)->us_strtod_inner # define vlc_lldiv (p_symbols)->vlc_lldiv_inner +# define decode_encoded_URI (p_symbols)->decode_encoded_URI_inner +# define convert_xml_special_chars (p_symbols)->convert_xml_special_chars_inner +# define decode_encoded_URI_duplicate (p_symbols)->decode_encoded_URI_duplicate_inner +# define resolve_xml_special_chars (p_symbols)->resolve_xml_special_chars_inner # elif defined (HAVE_DYNAMIC_PLUGINS) && !defined (__BUILTIN__) /****************************************************************** * STORE_SYMBOLS: store VLC APIs into p_symbols for plugin access. @@ -1403,6 +1411,10 @@ struct module_symbols_t ((p_symbols)->us_atof_inner) = us_atof; \ ((p_symbols)->us_strtod_inner) = us_strtod; \ ((p_symbols)->vlc_lldiv_inner) = vlc_lldiv; \ + ((p_symbols)->decode_encoded_URI_inner) = decode_encoded_URI; \ + ((p_symbols)->convert_xml_special_chars_inner) = convert_xml_special_chars; \ + ((p_symbols)->decode_encoded_URI_duplicate_inner) = decode_encoded_URI_duplicate; \ + ((p_symbols)->resolve_xml_special_chars_inner) = resolve_xml_special_chars; \ (p_symbols)->net_ConvertIPv4_deprecated = NULL; \ (p_symbols)->__stats_CounterGet_deprecated = NULL; \ (p_symbols)->__stats_TimerDumpAll_deprecated = NULL; \ diff --git a/modules/access/http.c b/modules/access/http.c index 102770ffb4..fc62ba25ac 100644 --- a/modules/access/http.c +++ b/modules/access/http.c @@ -403,6 +403,9 @@ connect: /* Grrrr! detect ultravox server and force NSV demuxer */ p_access->psz_demux = strdup( "nsv" ); } + else if( p_sys->psz_mime && + !strcasecmp( p_sys->psz_mime, "application/xspf+xml" ) ) + p_access->psz_demux = strdup( "xspf-open" ); if( p_sys->b_reconnect ) msg_Dbg( p_access, "auto re-connect enabled" ); diff --git a/modules/control/http/rpn.c b/modules/control/http/rpn.c index b21b3ebf30..393002731d 100644 --- a/modules/control/http/rpn.c +++ b/modules/control/http/rpn.c @@ -420,44 +420,8 @@ void E_(EvaluateRPN)( intf_thread_t *p_intf, mvar_t *vars, { char *psz_src = E_(SSPop)( st ); char *psz_dest; - char *str = psz_src; - - p = psz_dest = malloc( strlen( str ) * 6 + 1 ); - while( *str != '\0' ) - { - if( *str == '&' ) - { - strcpy( p, "&" ); - p += 5; - } - else if( *str == '\"' ) - { - strcpy( p, """ ); - p += 6; - } - else if( *str == '\'' ) - { - strcpy( p, "'" ); - p += 6; - } - else if( *str == '<' ) - { - strcpy( p, "<" ); - p += 4; - } - else if( *str == '>' ) - { - strcpy( p, ">" ); - p += 4; - } - else - { - *p++ = *str; - } - str++; - } - *p = '\0'; + psz_dest = convert_xml_special_chars( psz_src ); E_(SSPush)( st, psz_dest ); free( psz_src ); diff --git a/modules/control/http/util.c b/modules/control/http/util.c index 69b469c693..9d9ddce437 100644 --- a/modules/control/http/util.c +++ b/modules/control/http/util.c @@ -24,6 +24,7 @@ *****************************************************************************/ #include "http.h" +#include "vlc_strings.h" /**************************************************************************** * File and directory functions @@ -747,38 +748,7 @@ char *E_(ExtractURIValue)( char *psz_uri, const char *psz_name, void E_(DecodeEncodedURI)( char *psz ) { - char *dup = strdup( psz ); - char *p = dup; - - while( *p ) - { - if( *p == '%' ) - { - char val[3]; - p++; - if( !*p ) - { - break; - } - - val[0] = *p++; - val[1] = *p++; - val[2] = '\0'; - - *psz++ = strtol( val, NULL, 16 ); - } - else if( *p == '+' ) - { - *psz++ = ' '; - p++; - } - else - { - *psz++ = *p++; - } - } - *psz++ = '\0'; - free( dup ); + decode_encoded_URI( psz ); } /* Since the resulting string is smaller we can work in place, so it is diff --git a/modules/demux/playlist/Modules.am b/modules/demux/playlist/Modules.am index 8b730dfa47..e20523c3fc 100644 --- a/modules/demux/playlist/Modules.am +++ b/modules/demux/playlist/Modules.am @@ -1,8 +1,14 @@ -SOURCES_playlist = playlist.c \ - playlist.h \ - old.c \ - m3u.c \ - b4s.c \ - pls.c \ - dvb.c \ - podcast.c +SOURCES_playlist = \ + playlist.c \ + playlist.h \ + old.c \ + m3u.c \ + b4s.c \ + pls.c \ + dvb.c \ + podcast.c \ + xspf.c \ + xspf.h \ + $(NULL) + + diff --git a/modules/demux/playlist/playlist.c b/modules/demux/playlist/playlist.c index 13cff3be9d..8b0777171e 100644 --- a/modules/demux/playlist/playlist.c +++ b/modules/demux/playlist/playlist.c @@ -84,6 +84,11 @@ vlc_module_begin(); add_shortcut( "podcast" ); set_capability( "demux2", 10 ); set_callbacks( E_(Import_podcast), E_(Close_podcast) ); + add_submodule(); + set_description( _("XSPF playlist import") ); + add_shortcut( "xspf-open" ); + set_capability( "demux2", 10 ); + set_callbacks( E_(xspf_import_Activate), NULL ); vlc_module_end(); diff --git a/modules/demux/playlist/playlist.h b/modules/demux/playlist/playlist.h index e96b55d8ce..2eba68cb53 100644 --- a/modules/demux/playlist/playlist.h +++ b/modules/demux/playlist/playlist.h @@ -45,3 +45,5 @@ void E_(Close_DVB) ( vlc_object_t * ); int E_(Import_podcast) ( vlc_object_t * ); void E_(Close_podcast) ( vlc_object_t * ); + +int E_(xspf_import_Activate) ( vlc_object_t * ); diff --git a/modules/demux/playlist/xspf.c b/modules/demux/playlist/xspf.c new file mode 100644 index 0000000000..670785aeb2 --- /dev/null +++ b/modules/demux/playlist/xspf.c @@ -0,0 +1,682 @@ +/****************************************************************************** + * Copyright (C) 2006 Daniel Stränger + * + * 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. + *******************************************************************************/ +/** + * \file modules/demux/playlist/xspf.c + * \brief XSPF playlist import functions + */ + +#include +#include +#include + +#include "playlist.h" +#include "vlc_xml.h" +#include "vlc_strings.h" +#include "xspf.h" + +/** + * \brief XSPF submodule initialization function + */ +int E_(xspf_import_Activate)( vlc_object_t *p_this ) +{ + demux_t *p_demux = (demux_t *)p_this; + char *psz_ext; + + psz_ext = strrchr ( p_demux->psz_path, '.' ); + + if( ( psz_ext && !strcasecmp( psz_ext, ".xspf") ) || + ( p_demux->psz_demux && !strcmp(p_demux->psz_demux, "xspf-open") ) ) + { + ; + } + else + { + return VLC_EGENERIC; + } + msg_Dbg( p_demux, "using xspf playlist import"); + + p_demux->pf_control = xspf_import_Control; + p_demux->pf_demux = xspf_import_Demux; + + return VLC_SUCCESS; +} + +/** + * \brief demuxer function for XSPF parsing + */ +int xspf_import_Demux( demux_t *p_demux ) +{ + playlist_t *p_playlist = NULL; + playlist_item_t *p_current = NULL; + + vlc_bool_t b_play; + int i_ret = VLC_SUCCESS; + + xml_t *p_xml = NULL; + xml_reader_t *p_xml_reader = NULL; + char *psz_name = NULL; + + /* create new xml parser from stream */ + p_xml = xml_Create( p_demux ); + if( !p_xml ) + i_ret = VLC_ENOMOD; + else + { + p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s ); + if( !p_xml_reader ) + i_ret = VLC_EGENERIC; + } + + /* start with parsing the root node */ + if ( i_ret == VLC_SUCCESS ) + if ( xml_ReaderRead( p_xml_reader ) != 1 ) + { + msg_Err( p_demux, "can't read xml stream" ); + i_ret = VLC_EGENERIC; + } + /* checking root nody type */ + if ( i_ret == VLC_SUCCESS ) + if( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM ) + { + msg_Err( p_demux, "invalid root node type: %i", xml_ReaderNodeType( p_xml_reader ) ); + i_ret = VLC_EGENERIC; + } + /* checking root node name */ + if ( i_ret == VLC_SUCCESS ) + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name || strcmp( psz_name, "playlist" ) ) + { + msg_Err( p_demux, "invalid root node name: %s", psz_name ); + i_ret = VLC_EGENERIC; + } + FREE_NAME(); + + /* get the playlist ... */ + if ( i_ret == VLC_SUCCESS ) + { + p_playlist = (playlist_t *) vlc_object_find( p_demux, VLC_OBJECT_PLAYLIST, FIND_PARENT ); + if( !p_playlist ) + { + msg_Err( p_demux, "can't find playlist" ); + i_ret = VLC_ENOOBJ; + } + } + /* ... and its current item (to convert it to a node) */ + if ( i_ret == VLC_SUCCESS ) + { + b_play = E_(FindItem)( p_demux, p_playlist, &p_current ); + playlist_ItemToNode( p_playlist, p_current ); + p_current->input.i_type = ITEM_TYPE_PLAYLIST; + /* parse the playlist node */ + i_ret = parse_playlist_node( p_demux, p_playlist, p_current, + p_xml_reader, "playlist" ); + /* true/false - success/egeneric mapping */ + i_ret = ( i_ret==VLC_TRUE ? VLC_SUCCESS : VLC_EGENERIC ); + + if( b_play ) + { + playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, + p_playlist->status.i_view, + p_playlist->status.p_item, NULL ); + } + } + + if ( p_playlist ) + vlc_object_release( p_playlist ); + if ( p_xml_reader ) + xml_ReaderDelete( p_xml, p_xml_reader ); + if ( p_xml ) + xml_Delete( p_xml ); + + return i_ret; +} + +/** \brief dummy function for demux callback interface */ +int xspf_import_Control( demux_t *p_demux, int i_query, va_list args ) +{ + return VLC_EGENERIC; +} + +/** + * \brief parse the root node of a XSPF playlist + * \param p_demux demuxer instance + * \param p_playlist playlist instance + * \param p_item current playlist node + * \param p_xml_reader xml reader instance + * \param psz_element name of element to parse + */ +static vlc_bool_t parse_playlist_node COMPLEX_INTERFACE +{ + char *psz_name=NULL; + char *psz_value=NULL; + vlc_bool_t b_version_found = VLC_FALSE; + int i_node; + xml_elem_hnd_t *p_handler=NULL; + + xml_elem_hnd_t pl_elements[] = + { {"title", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"creator", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"annotation", SIMPLE_CONTENT, {NULL} }, + {"info", SIMPLE_CONTENT, {NULL} }, + {"location", SIMPLE_CONTENT, {NULL} }, + {"identifier", SIMPLE_CONTENT, {NULL} }, + {"image", SIMPLE_CONTENT, {NULL} }, + {"date", SIMPLE_CONTENT, {NULL} }, + {"license", SIMPLE_CONTENT, {NULL} }, + {"attribution", COMPLEX_CONTENT, {.cmplx = skip_element} }, + {"link", SIMPLE_CONTENT, {NULL} }, + {"meta", SIMPLE_CONTENT, {NULL} }, + {"extension", COMPLEX_CONTENT, {.cmplx = skip_element} }, + {"trackList", COMPLEX_CONTENT, {.cmplx = parse_tracklist_node} }, + {NULL, UNKNOWN_CONTENT, {NULL} } + }; + + /* read all playlist attributes */ + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + psz_name = xml_ReaderName ( p_xml_reader ); + psz_value = xml_ReaderValue ( p_xml_reader ); + if ( !psz_name || !psz_value ) + { + msg_Err( p_demux, "invalid xml stream @ " ); + FREE_ATT(); + return VLC_FALSE; + } + /* attribute: version */ + if ( !strcmp( psz_name, "version" ) ) + { + b_version_found = VLC_TRUE; + if ( strcmp( psz_value, "0" ) && strcmp( psz_value, "1" ) ) + msg_Warn( p_demux, "unsupported XSPF version" ); + } + /* attribute: xmlns */ + else if ( !strcmp ( psz_name, "xmlns" ) ) + ; + /* unknown attribute */ + else + msg_Warn( p_demux, "invalid attribute:\"%s\"", psz_name); + + FREE_ATT(); + } + /* attribute version is mandatory !!! */ + if ( !b_version_found ) + msg_Warn( p_demux, " requires \"version\" attribute" ); + + /* parse the child elements - we only take care of */ + while ( xml_ReaderRead( p_xml_reader ) == 1 ) + { + i_node = xml_ReaderNodeType( p_xml_reader ); + switch ( i_node ) + { + case XML_READER_NONE: + break; + case XML_READER_STARTELEM: + /* element start tag */ + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name || !*psz_name ) + { + msg_Err( p_demux, "invalid xml stream" ); + FREE_ATT(); + return VLC_FALSE; + } + /* choose handler */ + for( p_handler = pl_elements; + p_handler->name && strcmp( psz_name, p_handler->name ); + p_handler++ ); + if ( !p_handler->name ) + { + msg_Err( p_demux, "unexpected element <%s>", psz_name ); + FREE_ATT(); + return VLC_FALSE; + } + FREE_NAME(); + /* complex content is parsed in a separate function */ + if ( p_handler->type == COMPLEX_CONTENT ) + { + if ( p_handler->pf_handler.cmplx( p_demux, + p_playlist, + p_item, + p_xml_reader, + p_handler->name ) ) + { + p_handler = NULL; + FREE_ATT(); + } + else + { + FREE_ATT(); + return VLC_FALSE; + } + } + break; + + case XML_READER_TEXT: + /* simple element content */ + FREE_ATT(); + psz_value = xml_ReaderValue( p_xml_reader ); + if ( !psz_value ) + { + msg_Err( p_demux, "invalid xml stream" ); + FREE_ATT(); + return VLC_FALSE; + } + break; + + case XML_READER_ENDELEM: + /* element end tag */ + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name ) + { + msg_Err( p_demux, "invalid xml stream" ); + FREE_ATT(); + return VLC_FALSE; + } + /* leave if the current parent node is terminated */ + if ( !strcmp( psz_name, psz_element ) ) + { + FREE_ATT(); + return VLC_TRUE; + } + /* there MUST have been a start tag for that element name */ + if ( !p_handler || !p_handler->name + || strcmp( p_handler->name, psz_name )) + { + msg_Err( p_demux, "there's no open element left for <%s>", + psz_name ); + FREE_ATT(); + return VLC_FALSE; + } + + if ( p_handler->pf_handler.smpl ) + { + p_handler->pf_handler.smpl( p_item, p_handler->name, + psz_value ); + } + FREE_ATT(); + p_handler = NULL; + break; + + default: + /* unknown/unexpected xml node */ + msg_Err( p_demux, "unexpected xml node %i", i_node ); + FREE_ATT(); + return VLC_FALSE; + } + FREE_NAME(); + } + return VLC_FALSE; +} + +/** + * \brief parses the tracklist node which only may contain s + */ +static vlc_bool_t parse_tracklist_node COMPLEX_INTERFACE +{ + char *psz_name=NULL; + int i_node; + int i_ntracks = 0; + + /* now parse the s */ + while ( xml_ReaderRead( p_xml_reader ) == 1 ) + { + i_node = xml_ReaderNodeType( p_xml_reader ); + if ( i_node == XML_READER_STARTELEM ) + { + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name ) + { + msg_Err( p_demux, "unexpected end of xml data" ); + FREE_NAME(); + return VLC_FALSE; + } + if ( strcmp( psz_name, "track") ) + { + msg_Err( p_demux, "unexpected child of : <%s>", + psz_name ); + FREE_NAME(); + return VLC_FALSE; + } + FREE_NAME(); + + /* parse the track data in a separate function */ + if ( parse_track_node( p_demux, p_playlist, p_item, p_xml_reader, + "track" ) == VLC_TRUE ) + i_ntracks++; + } + else if ( i_node == XML_READER_ENDELEM ) + break; + } + + /* the has to be terminated */ + if ( xml_ReaderNodeType( p_xml_reader ) != XML_READER_ENDELEM ) + { + msg_Err( p_demux, "there's a missing " ); + FREE_NAME(); + return VLC_FALSE; + } + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name || strcmp( psz_name, "trackList" ) ) + { + msg_Err( p_demux, "expected: , found: ", psz_name ); + FREE_NAME(); + return VLC_FALSE; + } + FREE_NAME(); + + msg_Dbg( p_demux, "parsed %i tracks successfully", i_ntracks ); + + return VLC_TRUE; +} + +/** + * \brief parse one track element + * \param COMPLEX_INTERFACE + */ +static vlc_bool_t parse_track_node COMPLEX_INTERFACE +{ + playlist_item_t *p_new=NULL; + int i_node; + char *psz_name=NULL; + char *psz_value=NULL; + xml_elem_hnd_t *p_handler=NULL; + + xml_elem_hnd_t track_elements[] = + { {"location", SIMPLE_CONTENT, {NULL} }, + {"identifier", SIMPLE_CONTENT, {NULL} }, + {"title", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"creator", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"annotation", SIMPLE_CONTENT, {NULL} }, + {"info", SIMPLE_CONTENT, {NULL} }, + {"image", SIMPLE_CONTENT, {NULL} }, + {"album", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"trackNum", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"duration", SIMPLE_CONTENT, {.smpl = set_item_info} }, + {"link", SIMPLE_CONTENT, {NULL} }, + {"meta", SIMPLE_CONTENT, {NULL} }, + {"extension", COMPLEX_CONTENT, {.cmplx = skip_element} }, + {NULL, UNKNOWN_CONTENT, {NULL} } + }; + + while ( xml_ReaderRead( p_xml_reader ) == 1 ) + { + i_node = xml_ReaderNodeType( p_xml_reader ); + switch ( i_node ) + { + case XML_READER_NONE: + break; + + case XML_READER_STARTELEM: + /* element start tag */ + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name || !*psz_name ) + { + msg_Err( p_demux, "invalid xml stream" ); + FREE_ATT(); + return VLC_FALSE; + } + /* choose handler */ + for( p_handler = track_elements; + p_handler->name && strcmp( psz_name, p_handler->name ); + p_handler++ ); + if ( !p_handler->name ) + { + msg_Err( p_demux, "unexpected element <%s>", psz_name ); + FREE_ATT(); + return VLC_FALSE; + } + FREE_NAME(); + /* complex content is parsed in a separate function */ + if ( p_handler->type == COMPLEX_CONTENT ) + { + if ( !p_new ) + { + msg_Err( p_demux, + "at <%s> level no new item has been allocated", + p_handler->name ); + FREE_ATT(); + return VLC_FALSE; + } + if ( p_handler->pf_handler.cmplx( p_demux, + p_playlist, + p_new, + p_xml_reader, + p_handler->name ) ) + { + p_handler = NULL; + FREE_ATT(); + } + else + { + FREE_ATT(); + return VLC_FALSE; + } + } + break; + + case XML_READER_TEXT: + /* simple element content */ + FREE_ATT(); + psz_value = xml_ReaderValue( p_xml_reader ); + if ( !psz_value ) + { + msg_Err( p_demux, "invalid xml stream" ); + FREE_ATT(); + return VLC_FALSE; + } + break; + + case XML_READER_ENDELEM: + /* element end tag */ + psz_name = xml_ReaderName( p_xml_reader ); + if ( !psz_name ) + { + msg_Err( p_demux, "invalid xml stream" ); + FREE_ATT(); + return VLC_FALSE; + } + /* leave if the current parent node is terminated */ + if ( !strcmp( psz_name, psz_element ) ) + { + FREE_ATT(); + return VLC_TRUE; + } + /* there MUST have been a start tag for that element name */ + if ( !p_handler || !p_handler->name + || strcmp( p_handler->name, psz_name )) + { + msg_Err( p_demux, "there's no open element left for <%s>", + psz_name ); + FREE_ATT(); + return VLC_FALSE; + } + + /* special case: location */ + if ( !strcmp( p_handler->name, "location" ) ) + { + /* there MUST NOT be an item */ + if ( p_new ) + { + msg_Err( p_demux, + "a new item has just been created <%s>", + psz_name ); + FREE_ATT(); + return VLC_FALSE; + } + /* create it now */ + if ( insert_new_item( p_playlist, p_item, + &p_new, psz_value ) ) + { + FREE_ATT(); + p_handler = NULL; + } + else + { + FREE_ATT(); + return VLC_FALSE; + } + } + else + { + /* there MUST be an item */ + if ( !p_new ) + { + msg_Err( p_demux, + "an item hasn't been created yet <%s>", + psz_name ); + FREE_ATT(); + return VLC_FALSE; + } + if ( p_handler->pf_handler.smpl ) + { + p_handler->pf_handler.smpl( p_new, p_handler->name, + psz_value ); + FREE_ATT(); + } + } + FREE_ATT(); + p_handler = NULL; + break; + + default: + /* unknown/unexpected xml node */ + msg_Err( p_demux, "unexpected xml node %i", i_node ); + FREE_ATT(); + return VLC_FALSE; + } + FREE_NAME(); + } + msg_Err( p_demux, "unexpected end of xml data" ); + FREE_ATT(); + return VLC_FALSE; +} + +/** + * \brief handles the supported sub-elements + */ +static vlc_bool_t set_item_info SIMPLE_INTERFACE +{ + /* exit if setting is impossible */ + if ( !psz_name || !psz_value || !p_item ) + return VLC_FALSE; + + /* re-convert xml special characters inside psz_value */ + resolve_xml_special_chars ( psz_value ); + + /* handle each info element in a separate "if" clause */ + if ( !strcmp( psz_name, "title" ) ) + { + if ( playlist_ItemSetName ( p_item, (char *)psz_value ) == VLC_SUCCESS ) + return VLC_TRUE; + return VLC_FALSE; + } + else if ( !strcmp( psz_name, "creator" ) ) + { + if ( vlc_input_item_AddInfo( &(p_item->input), + _(VLC_META_INFO_CAT), _(VLC_META_ARTIST), + "%s", psz_value ) == VLC_SUCCESS ) + return VLC_TRUE; + return VLC_FALSE; + + } + else if ( !strcmp( psz_name, "album" ) ) + { + if ( vlc_input_item_AddInfo( &(p_item->input), + _(VLC_META_INFO_CAT), + _(VLC_META_COLLECTION), + "%s", psz_value ) == VLC_SUCCESS ) + return VLC_TRUE; + return VLC_FALSE; + + } else if ( !strcmp( psz_name, "trackNum" ) ) + { + long i_num = atol( psz_value ); + if ( i_num > 0 + && vlc_input_item_AddInfo( &(p_item->input), + _(VLC_META_INFO_CAT), + _(VLC_META_SEQ_NUM), + "%s", psz_value ) == VLC_SUCCESS ) + return VLC_TRUE; + return VLC_FALSE; + + } else if ( !strcmp( psz_name, "duration" ) ) + { + long i_num = atol( psz_value ); + if ( i_num > 0 + && playlist_ItemSetDuration( p_item, i_num*1000 ) == VLC_SUCCESS ) + return VLC_TRUE; + return VLC_FALSE; + + } + + return VLC_TRUE; +} + +/** + * \brief skips complex element content that we can't manage + */ +static vlc_bool_t skip_element COMPLEX_INTERFACE +{ + char *psz_endname; + + while ( xml_ReaderRead( p_xml_reader ) == 1 ) + { + if ( xml_ReaderNodeType( p_xml_reader ) == XML_READER_ENDELEM ) + { + psz_endname = xml_ReaderName( p_xml_reader ); + if ( !psz_endname ) + return VLC_FALSE; + if ( !strcmp( psz_element, psz_endname ) ) + { + free( psz_endname ); + return VLC_TRUE; + } + else + free( psz_endname ); + } + } + return VLC_FALSE; +} + +/** + * \brief creates a new playlist item from the given mrl + */ +static vlc_bool_t insert_new_item( playlist_t *p_pl, playlist_item_t *p_cur, + playlist_item_t **pp_new, char *psz_location ) +{ + char *psz_uri=NULL; + psz_uri = decode_encoded_URI_duplicate( psz_location ); + + if ( psz_uri ) + { + *pp_new = playlist_ItemNew( p_pl, psz_uri, NULL ); + free( psz_uri ); + psz_uri = NULL; + } + + if ( !*pp_new ) + return VLC_FALSE; + + playlist_NodeAddItem( p_pl, *pp_new, p_cur->pp_parents[0]->i_view, + p_cur, PLAYLIST_APPEND, PLAYLIST_END ); + + playlist_CopyParents( p_cur, *pp_new ); + + vlc_input_item_CopyOptions( &p_cur->input, &((*pp_new)->input) ); + + return VLC_TRUE; +} diff --git a/modules/demux/playlist/xspf.h b/modules/demux/playlist/xspf.h new file mode 100644 index 0000000000..b64e37cd66 --- /dev/null +++ b/modules/demux/playlist/xspf.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * Copyright (C) 2006 Daniel Stränger + * + * 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. + *******************************************************************************/ +/** + * \file modules/demux/playlist/xspf.h + * \brief XSPF playlist import: prototypes, datatypes, defines + */ + +/* defines */ +#define FREE_NAME() if (psz_name) {free(psz_name);psz_name=NULL;} +#define FREE_VALUE() if (psz_value) {free(psz_value);psz_value=NULL;} +#define FREE_ATT() FREE_NAME();FREE_VALUE() + +#define UNKNOWN_CONTENT 0 +#define SIMPLE_CONTENT 1 +#define COMPLEX_CONTENT 2 + +#define SIMPLE_INTERFACE (playlist_item_t *p_item,\ + const char *psz_name,\ + char *psz_value) +#define COMPLEX_INTERFACE (demux_t *p_demux,\ + playlist_t *p_playlist,\ + playlist_item_t *p_item,\ + xml_reader_t *p_xml_reader,\ + const char *psz_element) + +/* prototypes */ +int xspf_import_Demux( demux_t *); +int xspf_import_Control( demux_t *, int, va_list ); + +static vlc_bool_t parse_playlist_node COMPLEX_INTERFACE; +static vlc_bool_t parse_tracklist_node COMPLEX_INTERFACE; +static vlc_bool_t parse_track_node COMPLEX_INTERFACE; +static vlc_bool_t set_item_info SIMPLE_INTERFACE; +static vlc_bool_t skip_element COMPLEX_INTERFACE; +static vlc_bool_t insert_new_item( playlist_t *, playlist_item_t *, playlist_item_t **, char *); + +/* datatypes */ +typedef struct +{ + const char *name; + int type; + union + { + vlc_bool_t (*smpl) SIMPLE_INTERFACE; + vlc_bool_t (*cmplx) COMPLEX_INTERFACE; + } pf_handler; +} xml_elem_hnd_t; diff --git a/modules/gui/skins2/src/dialogs.cpp b/modules/gui/skins2/src/dialogs.cpp index 1e60680c52..40c8dd1fde 100644 --- a/modules/gui/skins2/src/dialogs.cpp +++ b/modules/gui/skins2/src/dialogs.cpp @@ -229,14 +229,16 @@ void Dialogs::showChangeSkin() void Dialogs::showPlaylistLoad() { showFileGeneric( _("Open playlist"), - _("All playlists|*.pls;*.m3u;*.asx;*.b4s|M3U files|*.m3u"), + _("All playlists|*.pls;*.m3u;*.asx;*.b4s;*.xspf|" + "M3U files|*.m3u|" + "XSPF playlist|*.xspf"), showPlaylistLoadCB, kOPEN ); } void Dialogs::showPlaylistSave() { - showFileGeneric( _("Save playlist"), _("M3U file|*.m3u"), + showFileGeneric( _("Save playlist"), _("M3U file|*.m3u|XSPF playlist|*.xspf"), showPlaylistSaveCB, kSAVE ); } diff --git a/modules/gui/wxwidgets/dialogs/playlist.cpp b/modules/gui/wxwidgets/dialogs/playlist.cpp index 814de0ee84..832e77c4c9 100644 --- a/modules/gui/wxwidgets/dialogs/playlist.cpp +++ b/modules/gui/wxwidgets/dialogs/playlist.cpp @@ -908,7 +908,9 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) ) char *psz_desc; char *psz_filter; char *psz_module; - } formats[] = {{ _("M3U file"), "*.m3u", "export-m3u" }}; + } formats[] = {{ _("M3U file"), "*.m3u", "export-m3u" }, + { _("XSPF playlist"), "*.xspf", "export-xspf"} + }; wxString filter = wxT(""); @@ -943,7 +945,7 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) ) void Playlist::OnOpen( wxCommandEvent& WXUNUSED(event) ) { wxFileDialog dialog( this, wxU(_("Open playlist")), wxT(""), wxT(""), - wxT("All playlists|*.pls;*.m3u;*.asx;*.b4s|M3U files|*.m3u"), wxOPEN ); + wxT("All playlists|*.pls;*.m3u;*.asx;*.b4s;*.xspf|XSPF playlist|*.xspf|M3U files|*.m3u"), wxOPEN ); if( dialog.ShowModal() == wxID_OK ) { diff --git a/modules/misc/playlist/Modules.am b/modules/misc/playlist/Modules.am index f650bd03a3..ec0a1a45a1 100644 --- a/modules/misc/playlist/Modules.am +++ b/modules/misc/playlist/Modules.am @@ -1,3 +1,7 @@ -SOURCES_export = export.c \ - m3u.c \ - old.c +SOURCES_export = \ + export.c \ + m3u.c \ + xspf.c \ + xspf.h \ + old.c \ + $(NULL) diff --git a/modules/misc/playlist/export.c b/modules/misc/playlist/export.c index 320743c004..af49869575 100644 --- a/modules/misc/playlist/export.c +++ b/modules/misc/playlist/export.c @@ -31,7 +31,7 @@ ***************************************************************************/ int Export_M3U ( vlc_object_t *p_intf ); int Export_Old ( vlc_object_t *p_intf ); - +int E_(xspf_export_playlist)( vlc_object_t *p_intf ); /***************************************************************************** * Module descriptor @@ -52,4 +52,10 @@ vlc_module_begin(); set_capability( "playlist export" , 0); set_callbacks( Export_Old , NULL ); + add_submodule(); + set_description( _("XSPF playlist export") ); + add_shortcut( "export-xspf" ); + set_capability( "playlist export" , 0); + set_callbacks( E_(xspf_export_playlist) , NULL ); + vlc_module_end(); diff --git a/modules/misc/playlist/xspf.c b/modules/misc/playlist/xspf.c new file mode 100644 index 0000000000..59ab4b93e1 --- /dev/null +++ b/modules/misc/playlist/xspf.c @@ -0,0 +1,295 @@ +/****************************************************************************** + * Copyright (C) 2006 Daniel Stränger + * + * 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. + *******************************************************************************/ + +/** + * \file modules/misc/playlist/xspf.c + * \brief XSPF playlist export functions + */ +#include +#include +#include +#include "vlc_meta.h" +#include "vlc_strings.h" +#include "xspf.h" + +/** + * \brief Prints the XSPF header to file, writes each item by xspf_export_item() + * and closes the open xml elements + * \param p_this the VLC playlist object + * \return VLC_SUCCESS if some memory is available, otherwise VLC_ENONMEM + */ +int E_(xspf_export_playlist)( vlc_object_t *p_this ) +{ + const playlist_t *p_playlist = (playlist_t *)p_this; + const playlist_export_t *p_export = + (playlist_export_t *)p_playlist->p_private; + int i; + char *psz; + char *psz_temp; + playlist_item_t **pp_items = NULL; + int i_size; + playlist_item_t *p_node; + + /* write XSPF XML header - since we don't use , + * we get by with version 0 */ + fprintf( p_export->p_file, "\n" ); + fprintf( p_export->p_file, + "\n" ); + + /* save tho whole playlist or only the current node */ +#define p_item p_playlist->status.p_item + if ( p_item ) + { + for (i = 0; i < p_item->i_parents; i++ ) + { + if ( p_item->pp_parents[i]->p_parent->input.i_type + == ITEM_TYPE_PLAYLIST ) + { + /* set the current node and its children */ + p_node = p_item->pp_parents[i]->p_parent; + pp_items = p_node->pp_children; + i_size = p_node->i_children; +#undef p_item + + /* save name of the playlist node */ + psz_temp = convert_xml_special_chars( p_node->input.psz_name ); + if ( *psz_temp ) + fprintf( p_export->p_file, "\t%s\n", + psz_temp ); + free( psz_temp ); + + /* save the creator of the playlist node */ + psz = vlc_input_item_GetInfo( &p_node->input, + _(VLC_META_INFO_CAT), + _(VLC_META_ARTIST) ); + if ( psz && !*psz ) + { + free ( psz ); + psz = NULL; + } + + if ( !psz ) + psz = vlc_input_item_GetInfo( &p_node->input, + _(VLC_META_INFO_CAT), + _(VLC_META_AUTHOR) ); + + psz_temp = convert_xml_special_chars( psz ); + + if ( psz ) free( psz ); + if ( *psz_temp ) + fprintf( p_export->p_file, "\t%s\n", + psz_temp ); + free( psz_temp ); + + /* save location of the playlist node */ + psz = assertUTF8URI( p_export->psz_filename ); + if ( psz && *psz ) + { + fprintf( p_export->p_file, "\t%s\n", + psz ); + free( psz ); + } + break; + } + } + } + + /* prepare all the playlist children for export */ + if ( !pp_items ) + { + pp_items = p_playlist->pp_items; + i_size = p_playlist->i_size; + } + + /* export all items */ + fprintf( p_export->p_file, "\t\n" ); + for ( i = 0; i < i_size; i++ ) + { + xspf_export_item( pp_items[i], p_export->p_file ); + } + + /* close the header elements */ + fprintf( p_export->p_file, "\t\n" ); + fprintf( p_export->p_file, "\n" ); + + return VLC_SUCCESS; +} + +/** + * \brief exports one item to file or traverse if item is a node + * \param p_item playlist item to export + * \param p_file file to write xml-converted item to + */ +static void xspf_export_item( playlist_item_t *p_item, FILE *p_file ) +{ + int i; /**< iterator for all children if the current item is a node */ + char *psz; + char *psz_temp; + + if ( !p_item ) + return; + + /** \todo only "flat" playlists supported at this time. + * extend to save the tree structure. + */ + /* if we get a node here, we must traverse it */ + if ( p_item->i_children > 0 ) + { + for ( i = 0; i < p_item->i_children; i++ ) + { + xspf_export_item( p_item->pp_children[i], p_file ); + } + return; + } + + /* leaves can be written directly */ + fprintf( p_file, "\t\t\n" ); + + /* -> the location */ + if ( p_item->input.psz_uri && *p_item->input.psz_uri ) + { + psz = assertUTF8URI( p_item->input.psz_uri ); + fprintf( p_file, "\t\t\t%s\n", psz ); + free( psz ); + } + + /* -> the name/title (only if different from uri)*/ + if ( p_item->input.psz_name && + p_item->input.psz_uri && + strcmp( p_item->input.psz_uri, p_item->input.psz_name ) ) + { + psz_temp = convert_xml_special_chars( p_item->input.psz_name ); + if ( *psz_temp ) + fprintf( p_file, "\t\t\t%s\n", psz_temp ); + free( psz_temp ); + } + + /* -> the artist/creator */ + psz = vlc_input_item_GetInfo( &p_item->input, + _(VLC_META_INFO_CAT), + _(VLC_META_ARTIST) ); + if ( psz && !*psz ) + { + free ( psz ); + psz = NULL; + } + if ( !psz ) + psz = vlc_input_item_GetInfo( &p_item->input, + _(VLC_META_INFO_CAT), + _(VLC_META_AUTHOR) ); + psz_temp = convert_xml_special_chars( psz ); + if ( psz ) free( psz ); + if ( *psz_temp ) + fprintf( p_file, "\t\t\t%s\n", psz_temp ); + free( psz_temp ); + + /* -> the album */ + psz = vlc_input_item_GetInfo( &p_item->input, + _(VLC_META_INFO_CAT), + _(VLC_META_COLLECTION) ); + psz_temp = convert_xml_special_chars( psz ); + if ( psz ) free( psz ); + if ( *psz_temp ) + fprintf( p_file, "\t\t\t%s\n", psz_temp ); + free( psz_temp ); + + /* -> the track number */ + psz = vlc_input_item_GetInfo( &p_item->input, + _(VLC_META_INFO_CAT), + _(VLC_META_SEQ_NUM) ); + if ( psz ) + { + if ( *psz ) + fprintf( p_file, "\t\t\t%i\n", atoi( psz ) ); + free( psz ); + } + + /* -> the duration */ + if ( p_item->input.i_duration > 0 ) + { + fprintf( p_file, "\t\t\t%ld\n", + (long)(p_item->input.i_duration / 1000) ); + } + + fprintf( p_file, "\t\t\n" ); + + return; +} + +/** + * \param psz_name the location of the media ressource (e.g. local file, + * device, network stream, etc.) + * \return a new char buffer which asserts that the location is valid UTF-8 + * and a valid URI + * \note the returned buffer must be freed, when it isn't used anymore + */ +static char *assertUTF8URI( char *psz_name ) +{ + char *psz_ret = NULL; /**< the new result buffer to return */ + char *psz_s = NULL, *psz_d = NULL; /**< src & dest pointers for URI conversion */ + vlc_bool_t b_name_is_uri = VLC_FALSE; + + if ( !psz_name || !*psz_name ) + return NULL; + + /* check that string is valid UTF-8 */ + /* XXX: Why do we even need to do that ? (all strings in core are UTF-8 encoded */ + if( !( psz_s = EnsureUTF8( psz_name ) ) ) + return NULL; + + /* max. 3x for URI conversion (percent escaping) and + 8 bytes for "file://" and NULL-termination */ + psz_ret = (char *)malloc( sizeof(char)*strlen(psz_name)*6*3+8 ); + if ( !psz_ret ) + return NULL; + + /** \todo check for a valid scheme part preceding the colon */ + if ( strchr( psz_s, ':' ) ) + { + psz_d = psz_ret; + b_name_is_uri = VLC_TRUE; + } + /* assume "file" scheme if no scheme-part is included */ + else + { + strcpy( psz_ret, "file://" ); + psz_d = psz_ret + 7; + } + + while ( *psz_s ) + { + /* percent-encode all non-ASCII and the XML special characters and the percent sign itself */ + if ( *psz_s & B10000000 || + *psz_s == '<' || + *psz_s == '>' || + *psz_s == '&' || + *psz_s == ' ' || + ( *psz_s == '%' && !b_name_is_uri ) ) + { + *psz_d++ = '%'; + *psz_d++ = hexchars[(*psz_s >> 4) & B00001111]; + *psz_d++ = hexchars[*psz_s & B00001111]; + } else + *psz_d++ = *psz_s; + + psz_s++; + } + *psz_d = '\0'; + + return (char *)realloc( psz_ret, sizeof(char)*strlen( psz_ret ) + 1 ); +} diff --git a/modules/misc/playlist/xspf.h b/modules/misc/playlist/xspf.h new file mode 100644 index 0000000000..e2773735b8 --- /dev/null +++ b/modules/misc/playlist/xspf.h @@ -0,0 +1,37 @@ +/***************************************************************************** + * Copyright (C) 2006 Daniel Stränger + * + * 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. + *******************************************************************************/ +/** + * \file modules/misc/playlist/xspf.h + * \brief XSPF playlist export module header file + */ + +/* defs */ +#define B10000000 0x80 +#define B01000000 0x40 +#define B11000000 0xc0 +#define B00001111 0x0f + +#define XSPF_MAX_CONTENT 2000 + +/* constants */ +const char hexchars[16] = "0123456789ABCDEF"; + +/* prototypes */ +int E_(xspf_export_playlist)( vlc_object_t * ); +static void xspf_export_item( playlist_item_t *, FILE * ); +static char *assertUTF8URI( char * ); diff --git a/modules/services_discovery/upnp_intel.cpp b/modules/services_discovery/upnp_intel.cpp index de045bf9e6..9b81e443bf 100644 --- a/modules/services_discovery/upnp_intel.cpp +++ b/modules/services_discovery/upnp_intel.cpp @@ -5,8 +5,8 @@ * $Id$ * * Authors: Rémi Denis-Courmont (original plugin) - * Christian Henz - * + * Christian Henz + * * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink * * This program is free software; you can redistribute it and/or modify @@ -41,6 +41,8 @@ #include #include +#include "vlc_strings.h" + // VLC handle @@ -57,7 +59,7 @@ const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer: const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1"; -// Classes +// Classes class MediaServer; class MediaServerList; @@ -66,51 +68,51 @@ class Container; // Cookie that is passed to the callback -typedef struct +typedef struct { services_discovery_t* serviceDiscovery; UpnpClient_Handle clientHandle; - MediaServerList* serverList; + MediaServerList* serverList; } Cookie; // Class definitions... -class Lockable -{ +class Lockable +{ public: - Lockable( Cookie* c ) + Lockable( Cookie* c ) { - vlc_mutex_init( c->serviceDiscovery, &_mutex ); + vlc_mutex_init( c->serviceDiscovery, &_mutex ); } - ~Lockable() + ~Lockable() { - vlc_mutex_destroy( &_mutex ); + vlc_mutex_destroy( &_mutex ); } void lock() { vlc_mutex_lock( &_mutex ); } void unlock() { vlc_mutex_unlock( &_mutex ); } private: - + vlc_mutex_t _mutex; }; -class Locker +class Locker { public: - Locker( Lockable* l ) + Locker( Lockable* l ) { - _lockable = l; - _lockable->lock(); + _lockable = l; + _lockable->lock(); } - ~Locker() + ~Locker() { - _lockable->unlock(); + _lockable->unlock(); } private: @@ -123,10 +125,10 @@ class MediaServer public: static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ); - + MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ); ~MediaServer(); - + const char* getUDN() const; const char* getFriendlyName() const; @@ -150,13 +152,13 @@ private: IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* ); Cookie* _cookie; - + Container* _contents; playlist_item_t* _playlistNode; std::string _UDN; std::string _friendlyName; - + std::string _contentDirectoryEventURL; std::string _contentDirectoryControlURL; @@ -186,7 +188,7 @@ private: }; -class Item +class Item { public: @@ -202,7 +204,7 @@ public: private: playlist_item_t* _playlistNode; - + Container* _parent; std::string _objectID; std::string _title; @@ -210,7 +212,7 @@ private: }; -class Container +class Container { public: @@ -268,7 +270,6 @@ vlc_module_end(); static Lockable* CallbackLock; static int Callback( Upnp_EventType eventType, void* event, void* pCookie ); -char* xml_makeSpecialChars( const char* in ); const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ); IXML_Document* parseBrowseResult( IXML_Document* doc ); @@ -279,8 +280,8 @@ static int Open( vlc_object_t *p_this ) { services_discovery_t *p_sd = ( services_discovery_t* )p_this; services_discovery_sys_t *p_sys = ( services_discovery_sys_t * ) - malloc( sizeof( services_discovery_sys_t ) ); - + malloc( sizeof( services_discovery_sys_t ) ); + playlist_view_t *p_view; vlc_value_t val; @@ -289,12 +290,12 @@ static int Open( vlc_object_t *p_this ) /* Create our playlist node */ p_sys->p_playlist = ( playlist_t * )vlc_object_find( p_sd, - VLC_OBJECT_PLAYLIST, - FIND_ANYWHERE ); + VLC_OBJECT_PLAYLIST, + FIND_ANYWHERE ); if( !p_sys->p_playlist ) { - msg_Warn( p_sd, "unable to find playlist, cancelling UPnP listening" ); - return VLC_EGENERIC; + msg_Warn( p_sd, "unable to find playlist, cancelling UPnP listening" ); + return VLC_EGENERIC; } p_view = playlist_ViewFind( p_sys->p_playlist, VIEW_CATEGORY ); @@ -315,9 +316,9 @@ static void Close( vlc_object_t *p_this ) if( p_sys->p_playlist ) { - playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE, - VLC_TRUE ); - vlc_object_release( p_sys->p_playlist ); + playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE, + VLC_TRUE ); + vlc_object_release( p_sys->p_playlist ); } free( p_sys ); @@ -326,12 +327,12 @@ static void Close( vlc_object_t *p_this ) static void Run( services_discovery_t* p_sd ) { int res; - + res = UpnpInit( 0, 0 ); - if( res != UPNP_E_SUCCESS ) + if( res != UPNP_E_SUCCESS ) { - msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); - return; + msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); + return; } Cookie cookie; @@ -341,23 +342,23 @@ static void Run( services_discovery_t* p_sd ) CallbackLock = new Lockable( &cookie ); res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle ); - if( res != UPNP_E_SUCCESS ) + if( res != UPNP_E_SUCCESS ) { - msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); - goto shutDown; + msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); + goto shutDown; } res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie ); - if( res != UPNP_E_SUCCESS ) + if( res != UPNP_E_SUCCESS ) { - msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); - goto shutDown; + msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); + goto shutDown; } - + msg_Dbg( p_sd, "UPnP discovery started" ); - while( !p_sd->b_die ) + while( !p_sd->b_die ) { - msleep( 500 ); + msleep( 500 ); } msg_Dbg( p_sd, "UPnP discovery stopped" ); @@ -372,7 +373,7 @@ static void Run( services_discovery_t* p_sd ) // XML utility functions: // Returns the value of a child element, or 0 on error -const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ) +const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ) { if ( !parent ) return 0; if ( !tagName ) return 0; @@ -392,72 +393,14 @@ const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName return ixmlNode_getNodeValue( textNode ); } - -// Replaces "<" with "<" etc. -// Returns a newly created string that has to be freed by the caller. -// Returns 0 on error ( out of mem ) -// \TODO: Probably not very robust!!! -char* xml_makeSpecialChars( const char* in ) -{ - if ( !in ) return 0; - - char* result = ( char* )malloc( strlen( in ) + 1 ); - if ( !result ) return 0; - - char* out = result; - - while( *in ) - { - if ( strncmp( "&", in, 5 ) == 0 ) - { - *out = '&'; - - in += 5; - out++; - } - else if ( strncmp( """, in, 6 ) == 0 ) - { - *out = '"'; - - in += 6; - out++; - } - else if ( strncmp( ">", in, 4 ) == 0 ) - { - *out = '>'; - - in += 4; - out++; - } - else if ( strncmp( "<", in, 4 ) == 0 ) - { - *out = '<'; - - in += 4; - out++; - } - else - { - *out = *in; - - in++; - out++; - } - } - - *out = '\0'; - return result; -} - - // Extracts the result document from a SOAP response -IXML_Document* parseBrowseResult( IXML_Document* doc ) +IXML_Document* parseBrowseResult( IXML_Document* doc ) { if ( !doc ) return 0; - + IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" ); if ( !resultList ) return 0; - + IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 ); ixmlNodeList_free( resultList ); @@ -468,7 +411,7 @@ IXML_Document* parseBrowseResult( IXML_Document* doc ) if ( !textNode ) return 0; const char* resultString = ixmlNode_getNodeValue( textNode ); - char* resultXML = xml_makeSpecialChars( resultString ); + char* resultXML = convert_xml_special_chars( resultString ); IXML_Document* browseDoc = ixmlParseBuffer( resultXML ); @@ -479,67 +422,67 @@ IXML_Document* parseBrowseResult( IXML_Document* doc ) // Handles all UPnP events -static int Callback( Upnp_EventType eventType, void* event, void* pCookie ) +static int Callback( Upnp_EventType eventType, void* event, void* pCookie ) { Locker locker( CallbackLock ); Cookie* cookie = ( Cookie* )pCookie; switch( eventType ) { - + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: case UPNP_DISCOVERY_SEARCH_RESULT: - { - struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event; - - IXML_Document *descriptionDoc = 0; - - int res; - res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc ); - if ( res != UPNP_E_SUCCESS ) - { - msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ ); - return res; - } - - MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie ); - - ixmlDocument_free( descriptionDoc ); - } - break; - + { + struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event; + + IXML_Document *descriptionDoc = 0; + + int res; + res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc ); + if ( res != UPNP_E_SUCCESS ) + { + msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ ); + return res; + } + + MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie ); + + ixmlDocument_free( descriptionDoc ); + } + break; + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: - { - struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event; + { + struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event; - cookie->serverList->removeServer( discovery->DeviceId ); - } - break; + cookie->serverList->removeServer( discovery->DeviceId ); + } + break; case UPNP_EVENT_RECEIVED: - { - Upnp_Event* e = ( Upnp_Event* )event; + { + Upnp_Event* e = ( Upnp_Event* )event; - MediaServer* server = cookie->serverList->getServerBySID( e->Sid ); - if ( server ) server->fetchContents(); - } - break; + MediaServer* server = cookie->serverList->getServerBySID( e->Sid ); + if ( server ) server->fetchContents(); + } + break; case UPNP_EVENT_AUTORENEWAL_FAILED: case UPNP_EVENT_SUBSCRIPTION_EXPIRED: - { - // Re-subscribe... + { + // Re-subscribe... - Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event; + Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event; + + MediaServer* server = cookie->serverList->getServerBySID( s->Sid ); + if ( server ) server->subscribeToContentDirectory(); + } + break; - MediaServer* server = cookie->serverList->getServerBySID( s->Sid ); - if ( server ) server->subscribeToContentDirectory(); - } - break; - default: - msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType ); - break; + msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType ); + break; } return UPNP_E_SUCCESS; @@ -550,127 +493,127 @@ static int Callback( Upnp_EventType eventType, void* event, void* pCookie ) // MediaServer... -void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ) +void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ) { if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; } if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; } - + const char* baseURL = location; // Try to extract baseURL IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" ); - if ( urlList ) - { - if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) ) - { - IXML_Node* textNode = ixmlNode_getFirstChild( urlNode ); - if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode ); - } - - ixmlNodeList_free( urlList ); + if ( urlList ) + { + if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) ) + { + IXML_Node* textNode = ixmlNode_getFirstChild( urlNode ); + if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode ); + } + + ixmlNodeList_free( urlList ); } - + // Get devices IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" ); if ( deviceList ) { - - for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ ) - { - IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i ); - - const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" ); - if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; } - if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue; - - const char* UDN = xml_getChildElementValue( deviceElement, "UDN" ); - if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; } - if ( cookie->serverList->getServer( UDN ) != 0 ) continue; - - const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" ); - if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; } - - MediaServer* server = new MediaServer( UDN, friendlyName, cookie ); - if ( !cookie->serverList->addServer( server ) ) { - - delete server; - server = 0; - continue; - } - - // Check for ContentDirectory service... - - IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" ); - if ( serviceList ) - { - for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ ) - { - IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j ); - - const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" ); - if ( !serviceType ) continue; - if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue; - - const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" ); - if ( !eventSubURL ) continue; - - const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" ); - if ( !controlURL ) continue; - - // Try to subscribe to ContentDirectory service - - char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 ); - if ( url ) - { - char* s1 = strdup( baseURL ); - char* s2 = strdup( eventSubURL ); - - if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) - { - // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url ); - - server->setContentDirectoryEventURL( url ); - server->subscribeToContentDirectory(); - } - - free( s1 ); - free( s2 ); - free( url ); - } - - // Try to browse content directory... - - url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 ); - if ( url ) - { - char* s1 = strdup( baseURL ); - char* s2 = strdup( controlURL ); - - if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) - { - // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url ); - - server->setContentDirectoryControlURL( url ); - server->fetchContents(); - } - - free( s1 ); - free( s2 ); - free( url ); - } - } - - ixmlNodeList_free( serviceList ); - } - } - - ixmlNodeList_free( deviceList ); + + for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ ) + { + IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i ); + + const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" ); + if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; } + if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue; + + const char* UDN = xml_getChildElementValue( deviceElement, "UDN" ); + if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; } + if ( cookie->serverList->getServer( UDN ) != 0 ) continue; + + const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" ); + if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; } + + MediaServer* server = new MediaServer( UDN, friendlyName, cookie ); + if ( !cookie->serverList->addServer( server ) ) { + + delete server; + server = 0; + continue; + } + + // Check for ContentDirectory service... + + IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" ); + if ( serviceList ) + { + for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ ) + { + IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j ); + + const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" ); + if ( !serviceType ) continue; + if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue; + + const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" ); + if ( !eventSubURL ) continue; + + const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" ); + if ( !controlURL ) continue; + + // Try to subscribe to ContentDirectory service + + char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 ); + if ( url ) + { + char* s1 = strdup( baseURL ); + char* s2 = strdup( eventSubURL ); + + if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) + { + // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url ); + + server->setContentDirectoryEventURL( url ); + server->subscribeToContentDirectory(); + } + + free( s1 ); + free( s2 ); + free( url ); + } + + // Try to browse content directory... + + url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 ); + if ( url ) + { + char* s1 = strdup( baseURL ); + char* s2 = strdup( controlURL ); + + if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) + { + // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url ); + + server->setContentDirectoryControlURL( url ); + server->fetchContents(); + } + + free( s1 ); + free( s2 ); + free( url ); + } + } + + ixmlNodeList_free( serviceList ); + } + } + + ixmlNodeList_free( deviceList ); } } -MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ) +MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ) { _cookie = cookie; @@ -681,14 +624,14 @@ MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* coo _playlistNode = 0; } -MediaServer::~MediaServer() -{ - if ( _contents ) +MediaServer::~MediaServer() +{ + if ( _contents ) { - playlist_NodeDelete( _cookie->serviceDiscovery->p_sys->p_playlist, - _playlistNode, - true, - true ); + playlist_NodeDelete( _cookie->serviceDiscovery->p_sys->p_playlist, + _playlistNode, + true, + true ); } delete _contents; @@ -706,7 +649,7 @@ const char* MediaServer::getFriendlyName() const return s; } -void MediaServer::setContentDirectoryEventURL( const char* url ) +void MediaServer::setContentDirectoryEventURL( const char* url ) { _contentDirectoryEventURL = url; } @@ -717,7 +660,7 @@ const char* MediaServer::getContentDirectoryEventURL() const return s; } -void MediaServer::setContentDirectoryControlURL( const char* url ) +void MediaServer::setContentDirectoryControlURL( const char* url ) { _contentDirectoryControlURL = url; } @@ -727,33 +670,33 @@ const char* MediaServer::getContentDirectoryControlURL() const return _contentDirectoryControlURL.c_str(); } -void MediaServer::subscribeToContentDirectory() +void MediaServer::subscribeToContentDirectory() { const char* url = getContentDirectoryEventURL(); - if ( !url || strcmp( url, "" ) == 0 ) - { - msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); - return; + if ( !url || strcmp( url, "" ) == 0 ) + { + msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); + return; } int timeOut = 1810; Upnp_SID sid; - + int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid ); - if ( res == UPNP_E_SUCCESS ) + if ( res == UPNP_E_SUCCESS ) { - _subscriptionTimeOut = timeOut; - memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) ); - } - else + _subscriptionTimeOut = timeOut; + memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) ); + } + else { - msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) ); + msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) ); } } -IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter, - const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria ) +IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter, + const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria ) { IXML_Document* action = 0; IXML_Document* response = 0; @@ -789,18 +732,18 @@ IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pB res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } - - res = UpnpSendAction( _cookie->clientHandle, - url, - CONTENT_DIRECTORY_SERVICE_TYPE, - 0, - action, - &response ); - if ( res != UPNP_E_SUCCESS ) - { - msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); - ixmlDocument_free( response ); - response = 0; + + res = UpnpSendAction( _cookie->clientHandle, + url, + CONTENT_DIRECTORY_SERVICE_TYPE, + 0, + action, + &response ); + if ( res != UPNP_E_SUCCESS ) + { + msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); + ixmlDocument_free( response ); + response = 0; } browseActionCleanup: @@ -818,22 +761,22 @@ IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pB return response; } -void MediaServer::fetchContents() +void MediaServer::fetchContents() { Container* root = new Container( 0, "0", getFriendlyName() ); _fetchContents( root ); - if ( _contents ) + if ( _contents ) { - vlc_mutex_lock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock ); + vlc_mutex_lock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock ); - playlist_NodeEmpty( _cookie->serviceDiscovery->p_sys->p_playlist, - _playlistNode, - true ); + playlist_NodeEmpty( _cookie->serviceDiscovery->p_sys->p_playlist, + _playlistNode, + true ); - vlc_mutex_unlock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock ); - - delete _contents; + vlc_mutex_unlock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock ); + + delete _contents; } _contents = root; @@ -842,7 +785,7 @@ void MediaServer::fetchContents() _buildPlaylist( _contents ); } -bool MediaServer::_fetchContents( Container* parent ) +bool MediaServer::_fetchContents( Container* parent ) { if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; } @@ -854,111 +797,111 @@ bool MediaServer::_fetchContents( Container* parent ) if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; } IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" ); - if ( containerNodeList ) + if ( containerNodeList ) { - for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ ) - { - IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i ); - - const char* objectID = ixmlElement_getAttribute( containerElement, "id" ); - if ( !objectID ) continue; - - const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" ); - if ( !childCountStr ) continue; - int childCount = atoi( childCountStr ); - - const char* title = xml_getChildElementValue( containerElement, "dc:title" ); - if ( !title ) continue; - - const char* resource = xml_getChildElementValue( containerElement, "res" ); - - if ( resource && childCount < 1 ) - { - Item* item = new Item( parent, objectID, title, resource ); - parent->addItem( item ); - } - else - { - Container* container = new Container( parent, objectID, title ); - parent->addContainer( container ); - - if ( childCount > 0 ) _fetchContents( container ); - } - } - - ixmlNodeList_free( containerNodeList ); + for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ ) + { + IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i ); + + const char* objectID = ixmlElement_getAttribute( containerElement, "id" ); + if ( !objectID ) continue; + + const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" ); + if ( !childCountStr ) continue; + int childCount = atoi( childCountStr ); + + const char* title = xml_getChildElementValue( containerElement, "dc:title" ); + if ( !title ) continue; + + const char* resource = xml_getChildElementValue( containerElement, "res" ); + + if ( resource && childCount < 1 ) + { + Item* item = new Item( parent, objectID, title, resource ); + parent->addItem( item ); + } + else + { + Container* container = new Container( parent, objectID, title ); + parent->addContainer( container ); + + if ( childCount > 0 ) _fetchContents( container ); + } + } + + ixmlNodeList_free( containerNodeList ); } IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" ); - if ( itemNodeList ) + if ( itemNodeList ) + { + for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ ) { - for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ ) - { - IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i ); - - const char* objectID = ixmlElement_getAttribute( itemElement, "id" ); - if ( !objectID ) continue; - - const char* title = xml_getChildElementValue( itemElement, "dc:title" ); - if ( !title ) continue; - - const char* resource = xml_getChildElementValue( itemElement, "res" ); - if ( !resource ) continue; - - Item* item = new Item( parent, objectID, title, resource ); - parent->addItem( item ); - } - - ixmlNodeList_free( itemNodeList ); + IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i ); + + const char* objectID = ixmlElement_getAttribute( itemElement, "id" ); + if ( !objectID ) continue; + + const char* title = xml_getChildElementValue( itemElement, "dc:title" ); + if ( !title ) continue; + + const char* resource = xml_getChildElementValue( itemElement, "res" ); + if ( !resource ) continue; + + Item* item = new Item( parent, objectID, title, resource ); + parent->addItem( item ); + } + + ixmlNodeList_free( itemNodeList ); } - + ixmlDocument_free( result ); - + return true; } -void MediaServer::_buildPlaylist( Container* parent ) -{ - for ( unsigned int i = 0; i < parent->getNumContainers(); i++ ) +void MediaServer::_buildPlaylist( Container* parent ) +{ + for ( unsigned int i = 0; i < parent->getNumContainers(); i++ ) { - Container* container = parent->getContainer( i ); - playlist_item_t* parentNode = parent->getPlaylistNode(); - - char* title = strdup( container->getTitle() ); - playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist, - VIEW_CATEGORY, - title, - parentNode ); - free( title ); - - container->setPlaylistNode( node ); - _buildPlaylist( container ); + Container* container = parent->getContainer( i ); + playlist_item_t* parentNode = parent->getPlaylistNode(); + + char* title = strdup( container->getTitle() ); + playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist, + VIEW_CATEGORY, + title, + parentNode ); + free( title ); + + container->setPlaylistNode( node ); + _buildPlaylist( container ); } - for ( unsigned int i = 0; i < parent->getNumItems(); i++ ) + for ( unsigned int i = 0; i < parent->getNumItems(); i++ ) { - Item* item = parent->getItem( i ); - playlist_item_t* parentNode = parent->getPlaylistNode(); - - playlist_item_t* node = playlist_ItemNew( _cookie->serviceDiscovery, - item->getResource(), - item->getTitle() ); - - playlist_NodeAddItem( _cookie->serviceDiscovery->p_sys->p_playlist, - node, - VIEW_CATEGORY, - parentNode, PLAYLIST_APPEND, PLAYLIST_END ); - - item->setPlaylistNode( node ); + Item* item = parent->getItem( i ); + playlist_item_t* parentNode = parent->getPlaylistNode(); + + playlist_item_t* node = playlist_ItemNew( _cookie->serviceDiscovery, + item->getResource(), + item->getTitle() ); + + playlist_NodeAddItem( _cookie->serviceDiscovery->p_sys->p_playlist, + node, + VIEW_CATEGORY, + parentNode, PLAYLIST_APPEND, PLAYLIST_END ); + + item->setPlaylistNode( node ); } } -void MediaServer::setPlaylistNode( playlist_item_t* playlistNode ) +void MediaServer::setPlaylistNode( playlist_item_t* playlistNode ) { _playlistNode = playlistNode; } -bool MediaServer::compareSID( const char* sid ) +bool MediaServer::compareSID( const char* sid ) { return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 ); } @@ -966,20 +909,20 @@ bool MediaServer::compareSID( const char* sid ) // MediaServerList... -MediaServerList::MediaServerList( Cookie* cookie ) +MediaServerList::MediaServerList( Cookie* cookie ) { _cookie = cookie; } -MediaServerList::~MediaServerList() +MediaServerList::~MediaServerList() { for ( unsigned int i = 0; i < _list.size(); i++ ) { - delete _list[i]; + delete _list[i]; } } -bool MediaServerList::addServer( MediaServer* s ) +bool MediaServerList::addServer( MediaServer* s ) { if ( getServer( s->getUDN() ) != 0 ) return false; @@ -989,73 +932,73 @@ bool MediaServerList::addServer( MediaServer* s ) char* name = strdup( s->getFriendlyName() ); playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist, - VIEW_CATEGORY, - name, - _cookie->serviceDiscovery->p_sys->p_node ); + VIEW_CATEGORY, + name, + _cookie->serviceDiscovery->p_sys->p_node ); free( name ); s->setPlaylistNode( node ); return true; } -MediaServer* MediaServerList::getServer( const char* UDN ) +MediaServer* MediaServerList::getServer( const char* UDN ) { MediaServer* result = 0; - - for ( unsigned int i = 0; i < _list.size(); i++ ) + + for ( unsigned int i = 0; i < _list.size(); i++ ) + { + if( strcmp( UDN, _list[i]->getUDN() ) == 0 ) { - if( strcmp( UDN, _list[i]->getUDN() ) == 0 ) - { - result = _list[i]; - break; - } + result = _list[i]; + break; + } } return result; } -MediaServer* MediaServerList::getServerBySID( const char* sid ) -{ +MediaServer* MediaServerList::getServerBySID( const char* sid ) +{ MediaServer* server = 0; - for ( unsigned int i = 0; i < _list.size(); i++ ) + for ( unsigned int i = 0; i < _list.size(); i++ ) + { + if ( _list[i]->compareSID( sid ) ) { - if ( _list[i]->compareSID( sid ) ) - { - server = _list[i]; - break; - } + server = _list[i]; + break; } - + } + return server; } -void MediaServerList::removeServer( const char* UDN ) +void MediaServerList::removeServer( const char* UDN ) { MediaServer* server = getServer( UDN ); if ( !server ) return; - msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() ); + msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() ); std::vector::iterator it; - for ( it = _list.begin(); it != _list.end(); it++ ) + for ( it = _list.begin(); it != _list.end(); it++ ) { - if ( *it == server ) - { - _list.erase( it ); - delete server; - break; - } + if ( *it == server ) + { + _list.erase( it ); + delete server; + break; + } } } // Item... -Item::Item( Container* parent, const char* objectID, const char* title, const char* resource ) +Item::Item( Container* parent, const char* objectID, const char* title, const char* resource ) { _parent = parent; - + _objectID = objectID; _title = title; _resource = resource; @@ -1063,105 +1006,105 @@ Item::Item( Container* parent, const char* objectID, const char* title, const ch _playlistNode = 0; } -const char* Item::getObjectID() const +const char* Item::getObjectID() const { return _objectID.c_str(); } -const char* Item::getTitle() const +const char* Item::getTitle() const { return _title.c_str(); } -const char* Item::getResource() const +const char* Item::getResource() const { return _resource.c_str(); } -void Item::setPlaylistNode( playlist_item_t* node ) -{ - _playlistNode = node; +void Item::setPlaylistNode( playlist_item_t* node ) +{ + _playlistNode = node; } -playlist_item_t* Item::getPlaylistNode() const -{ - return _playlistNode; +playlist_item_t* Item::getPlaylistNode() const +{ + return _playlistNode; } // Container... -Container::Container( Container* parent, const char* objectID, const char* title ) +Container::Container( Container* parent, const char* objectID, const char* title ) { _parent = parent; - + _objectID = objectID; _title = title; _playlistNode = 0; } -Container::~Container() +Container::~Container() { - for ( unsigned int i = 0; i < _containers.size(); i++ ) + for ( unsigned int i = 0; i < _containers.size(); i++ ) { - delete _containers[i]; + delete _containers[i]; } - for ( unsigned int i = 0; i < _items.size(); i++ ) + for ( unsigned int i = 0; i < _items.size(); i++ ) { - delete _items[i]; + delete _items[i]; } } -void Container::addItem( Item* item ) +void Container::addItem( Item* item ) { _items.push_back( item ); } -void Container::addContainer( Container* container ) +void Container::addContainer( Container* container ) { _containers.push_back( container ); } -const char* Container::getObjectID() const -{ - return _objectID.c_str(); +const char* Container::getObjectID() const +{ + return _objectID.c_str(); } -const char* Container::getTitle() const -{ - return _title.c_str(); +const char* Container::getTitle() const +{ + return _title.c_str(); } -unsigned int Container::getNumItems() const -{ - return _items.size(); +unsigned int Container::getNumItems() const +{ + return _items.size(); } -unsigned int Container::getNumContainers() const -{ - return _containers.size(); +unsigned int Container::getNumContainers() const +{ + return _containers.size(); } -Item* Container::getItem( unsigned int i ) const +Item* Container::getItem( unsigned int i ) const { if ( i < _items.size() ) return _items[i]; return 0; } -Container* Container::getContainer( unsigned int i ) const +Container* Container::getContainer( unsigned int i ) const { if ( i < _containers.size() ) return _containers[i]; return 0; } -void Container::setPlaylistNode( playlist_item_t* node ) -{ - _playlistNode = node; +void Container::setPlaylistNode( playlist_item_t* node ) +{ + _playlistNode = node; } -playlist_item_t* Container::getPlaylistNode() const -{ - return _playlistNode; +playlist_item_t* Container::getPlaylistNode() const +{ + return _playlistNode; } diff --git a/src/Makefile.am b/src/Makefile.am index 038b3e3bed..a28498879f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -89,6 +89,7 @@ HEADERS_include = \ ../include/vlc_playlist.h \ ../include/vlc_spu.h \ ../include/vlc_stream.h \ + ../include/vlc_strings.h \ ../include/vlc_symbols.h \ ../include/vlc_threads_funcs.h \ ../include/vlc_threads.h \ @@ -321,6 +322,7 @@ SOURCES_libvlc_common = \ misc/modules.c \ misc/threads.c \ misc/stats.c \ + misc/strings.c \ misc/unicode.c \ misc/cpu.c \ misc/configuration.c \ diff --git a/src/misc/modules.c b/src/misc/modules.c index e59b55c352..ec20d4884f 100644 --- a/src/misc/modules.c +++ b/src/misc/modules.c @@ -110,6 +110,7 @@ #include "vlc_osd.h" #include "vlc_update.h" +#include "vlc_strings.h" #if defined( _MSC_VER ) && defined( UNDER_CE ) # include "modules_builtin_evc.h" diff --git a/src/misc/strings.c b/src/misc/strings.c new file mode 100644 index 0000000000..645f355d59 --- /dev/null +++ b/src/misc/strings.c @@ -0,0 +1,179 @@ +/***************************************************************************** + * strings.c: String related functions + ***************************************************************************** + * Copyright (C) 2006 the VideoLAN team + * $Id$ + * + * Authors: Antoine Cellerier + * Daniel Stranger + * + * 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. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#include +#include +#include + +#include "vlc_strings.h" + +/** + * Decode URI encoded string + * \return decoded duplicated string + */ +char *decode_encoded_URI_duplicate( const char *psz ) +{ + char *psz_dup = strdup( psz ); + decode_encoded_URI( psz_dup ); + return psz_dup; +} + +/** + * Decode URI encoded string + * \return nothing + */ +void decode_encoded_URI( char *psz ) +{ + char *dup = strdup( psz ); + char *p = dup; + + while( *p ) + { + if( *p == '%' ) + { + char val[3]; + p++; + if( !*p ) + { + break; + } + + val[0] = *p++; + val[1] = *p++; + val[2] = '\0'; + + *psz++ = strtol( val, NULL, 16 ); + } + else if( *p == '+' ) + { + *psz++ = ' '; + p++; + } + else + { + *psz++ = *p++; + } + } + *psz++ = '\0'; + free( dup ); +} + +/** + * Converts "<", ">" and "&" to "<", ">" and "&" + * \param string to convert + */ +void resolve_xml_special_chars( char *psz_value ) +{ + char *p_pos = psz_value; + + while ( *psz_value ) + { + if( !strncmp( psz_value, "<", 4 ) ) + { + *p_pos = '<'; + psz_value += 4; + } + else if( !strncmp( psz_value, ">", 4 ) ) + { + *p_pos = '>'; + psz_value += 4; + } + else if( !strncmp( psz_value, "&", 5 ) ) + { + *p_pos = '&'; + psz_value += 5; + } + else if( !strncmp( psz_value, """, 6 ) ) + { + *p_pos = '\"'; + psz_value += 6; + } + else if( !strncmp( psz_value, "'", 6 ) ) + { + *p_pos = '\''; + psz_value += 6; + } + else + { + *p_pos = *psz_value; + psz_value++; + } + + p_pos++; + } + + *p_pos = '\0'; +} + +/** + * Converts '<', '>', '\"', '\'' and '&' to their html entities + * \param psz_content simple element content that is to be converted + */ +char *convert_xml_special_chars( const char *psz_content ) +{ + char *psz_temp = malloc( 6 * strlen( psz_content ) + 1 ); + const char *p_from = psz_content; + char *p_to = psz_temp; + + while ( *p_from ) + { + if ( *p_from == '<' ) + { + strcpy( p_to, "<" ); + p_to += 4; + } + else if ( *p_from == '>' ) + { + strcpy( p_to, ">" ); + p_to += 4; + } + else if ( *p_from == '&' ) + { + strcpy( p_to, "&" ); + p_to += 5; + } + else if( *p_from == '\"' ) + { + strcpy( p_to, """ ); + p_to += 6; + } + else if( *p_from == '\'' ) + { + strcpy( p_to, "'" ); + p_to += 6; + } + else + { + *p_to = *p_from; + p_to++; + } + p_from++; + } + *p_to = '\0'; + + return psz_temp; +} diff --git a/src/playlist/loadsave.c b/src/playlist/loadsave.c index c3684fba8f..651d44a693 100644 --- a/src/playlist/loadsave.c +++ b/src/playlist/loadsave.c @@ -127,6 +127,9 @@ int playlist_Export( playlist_t * p_playlist, const char *psz_filename , msg_Err( p_playlist, "out of memory"); return VLC_ENOMEM; } + p_export->psz_filename = NULL; + if ( psz_filename ) + p_export->psz_filename = strdup( psz_filename ); p_export->p_file = utf8_fopen( psz_filename, "wt" ); if( !p_export->p_file ) { @@ -149,8 +152,12 @@ int playlist_Export( playlist_t * p_playlist, const char *psz_filename , } module_Unneed( p_playlist , p_module ); + /* Clean up */ fclose( p_export->p_file ); - + if ( p_export->psz_filename ) + free( p_export->psz_filename ); + free ( p_export ); + p_playlist->p_private = NULL; vlc_mutex_unlock( &p_playlist->object_lock ); return VLC_SUCCESS; -- 2.39.2