addons: add missing intf and meta categories
[vlc.git] / modules / misc / addons / fsstorage.c
1 /*****************************************************************************
2  * addonsstorage.c : Addons Local filesystem storage
3  *****************************************************************************
4  * Copyright (C) 2014 VLC authors and VideoLAN
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 /*****************************************************************************
22  * Preamble
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_modules.h>
32 #include <vlc_stream.h>
33 #include <vlc_addons.h>
34 #include <vlc_fs.h>
35 #include <vlc_strings.h>
36 #include <vlc_xml.h>
37 #include <vlc_url.h>
38 #include "xmlreading.h"
39
40 #include <sys/stat.h>
41 #include <errno.h>
42
43 #include <unistd.h>     // getpid()
44
45 /*****************************************************************************
46  * Local prototypes
47  *****************************************************************************/
48
49 #define ADDONS_DIR          ""
50 #define ADDONS_SCRIPTS_DIR  ADDONS_DIR DIR_SEP "lua"
51 #define ADDONS_CATALOG      ADDONS_DIR DIR_SEP "catalog.xml"
52
53 static struct
54 {
55     addon_type_t t;
56     const char * const psz_dir;
57 } const addons_dirs[] = {
58     { ADDON_EXTENSION,           ADDONS_SCRIPTS_DIR DIR_SEP "extensions" },
59     { ADDON_PLAYLIST_PARSER,     ADDONS_SCRIPTS_DIR DIR_SEP "playlist" },
60     { ADDON_SERVICE_DISCOVERY,   ADDONS_SCRIPTS_DIR DIR_SEP "sd" },
61     { ADDON_INTERFACE,           ADDONS_SCRIPTS_DIR DIR_SEP "intf" },
62     { ADDON_META,                ADDONS_SCRIPTS_DIR DIR_SEP "meta" },
63     { ADDON_SKIN2,               ADDONS_DIR DIR_SEP "skins2" },
64 };
65
66 static int   OpenStorage ( vlc_object_t * );
67 static void  CloseStorage ( vlc_object_t * );
68 static int   OpenLister ( vlc_object_t * );
69 static void  CloseLister ( vlc_object_t * );
70
71 static int   LoadCatalog ( addons_finder_t * );
72 static bool  FileBelongsToManagedAddon( addons_finder_t *p_finder,
73                                         const addon_type_t e_type,
74                                         const char *psz_file );
75 /*****************************************************************************
76  * Module descriptor
77  ****************************************************************************/
78
79 vlc_module_begin ()
80     set_category(CAT_ADVANCED)
81     set_subcategory(SUBCAT_ADVANCED_MISC)
82     set_shortname(N_("addons local storage"))
83     add_shortcut("addons.store.install")
84     set_description(N_("Addons local storage installer"))
85     set_capability("addons storage", 10)
86     set_callbacks(OpenStorage, CloseStorage)
87
88 add_submodule ()
89     set_category(CAT_ADVANCED)
90     set_subcategory(SUBCAT_ADVANCED_MISC)
91     add_shortcut("addons.store.list")
92     set_description( N_("Addons local storage lister") )
93     set_capability( "addons finder", 0 )
94     set_callbacks( OpenLister, CloseLister )
95
96 vlc_module_end ()
97
98 static char * getAddonInstallDir( addon_type_t t )
99 {
100     const char *psz_subdir = NULL;
101     char *psz_dir;
102     char *psz_userdir = config_GetUserDir( VLC_DATA_DIR );
103     if ( !psz_userdir ) return NULL;
104
105     for ( unsigned int i=0; i< ARRAY_SIZE(addons_dirs); i++ )
106     {
107         if ( addons_dirs[i].t == t )
108         {
109             psz_subdir = addons_dirs[i].psz_dir;
110             break;
111         }
112     }
113
114     if ( !psz_subdir )
115     {
116         free ( psz_userdir );
117         return NULL;
118     }
119
120     if ( asprintf( &psz_dir, "%s%s", psz_userdir, psz_subdir ) < 1 )
121     {
122         free( psz_userdir );
123         return NULL;
124     }
125     free( psz_userdir );
126
127     return psz_dir;
128 }
129
130 static int ListSkin_filter( const char * psz_filename )
131 {
132     int i_len = strlen( psz_filename );
133     if ( i_len  <= 4 )
134         return 0;
135     else
136         return ! strcmp( psz_filename + i_len - 4, ".vlt" );
137 }
138
139 static int ListScript_filter( const char * psz_filename )
140 {
141     int i_len = strlen( psz_filename );
142     if ( i_len  <= 4 )
143         return 0;
144     else
145         return ! strcmp( psz_filename + i_len - 4, ".lua" );
146 }
147
148 static int ParseSkins2Info( addons_finder_t *p_finder, stream_t *p_stream,
149                             char **ppsz_title, char **ppsz_source )
150 {
151     const char *p_node;
152     int i_current_node_type;
153     bool b_done = false;
154
155     xml_reader_t *p_xml_reader = xml_ReaderCreate( p_finder, p_stream );
156     if( !p_xml_reader ) return VLC_EGENERIC;
157
158     if( xml_ReaderNextNode( p_xml_reader, &p_node ) != XML_READER_STARTELEM )
159     {
160         msg_Err( p_finder, "invalid xml file" );
161         goto error;
162     }
163
164     if ( strcmp( p_node, "Theme") )
165     {
166         msg_Err( p_finder, "unsupported XML data format" );
167         goto error;
168     }
169
170     while( !b_done && (i_current_node_type = xml_ReaderNextNode( p_xml_reader, &p_node )) > 0 )
171     {
172         switch( i_current_node_type )
173         {
174         case XML_READER_STARTELEM:
175         {
176             if ( !strcmp( p_node, "ThemeInfo" ) )
177             {
178                 const char *attr, *value;
179                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
180                 {
181                     if ( !strcmp( attr, "name" ) )
182                         *ppsz_title = strdup( value );
183                     else if ( !strcmp( attr, "webpage" ) )
184                         *ppsz_source = strdup( value );
185                 }
186                 b_done = true;
187             }
188             break;
189         }
190
191         default:
192             break;
193         }
194     }
195
196     xml_ReaderDelete( p_xml_reader );
197     return ( b_done ) ? VLC_SUCCESS : VLC_EGENERIC;
198
199 error:
200     xml_ReaderDelete( p_xml_reader );
201     return VLC_EGENERIC;
202 }
203
204 static int ListSkins( addons_finder_t *p_finder )
205 {
206     char *psz_dir = getAddonInstallDir( ADDON_SKIN2 );
207     if ( !psz_dir )
208         return VLC_EGENERIC;
209
210     char **ppsz_list = NULL;
211     int i_count = vlc_scandir( psz_dir, &ppsz_list, ListSkin_filter, NULL );
212
213     for ( int i=0; i< i_count; i++ )
214     {
215         char *psz_file = ppsz_list[i];
216         if( !psz_file )
217             break;
218
219         if ( FileBelongsToManagedAddon( p_finder, ADDON_SKIN2, psz_file ) )
220              continue;
221
222         char *psz_uri;
223         if( asprintf( &psz_uri, "unzip://%s/%s!/theme.xml", psz_dir, psz_file ) >= 0)
224         {
225             int i_ret;
226             char *psz_name = NULL;
227             char *psz_source = NULL;
228             stream_t *p_stream = stream_UrlNew( p_finder, psz_uri );
229             free( psz_uri );
230             if ( !p_stream )
231             {
232                 i_ret = VLC_EGENERIC;
233             }
234             else
235             {
236                 i_ret = ParseSkins2Info( p_finder, p_stream, &psz_name, &psz_source );
237                 if ( i_ret != VLC_SUCCESS )
238                 {
239                     free( psz_name );
240                     free( psz_source );
241                 }
242                 stream_Delete( p_stream );
243             }
244
245             addon_entry_t *p_entry = addon_entry_New();
246             p_entry->e_type = ADDON_SKIN2;
247             p_entry->e_state = ADDON_INSTALLED;
248             if ( i_ret == VLC_SUCCESS )
249             {
250                 p_entry->psz_name = psz_name;
251                 p_entry->psz_description = strdup("Skins2 theme");
252                 p_entry->psz_source_uri = psz_source;
253             }
254             else
255             {
256                 p_entry->e_flags |= ADDON_BROKEN;
257                 p_entry->psz_name = strdup(psz_file);
258                 p_entry->psz_description = strdup("Skins2 theme");
259             }
260
261             ARRAY_APPEND( p_finder->entries, p_entry );
262         }
263         free( psz_file );
264     }
265
266     free( ppsz_list );
267     free( psz_dir );
268     return VLC_SUCCESS;
269 }
270
271 static bool FileBelongsToManagedAddon( addons_finder_t *p_finder,
272                                        const addon_type_t e_type,
273                                        const char *psz_file )
274 {
275     FOREACH_ARRAY( const addon_entry_t *p_entry, p_finder->entries )
276         if ( ( p_entry->e_flags & ADDON_MANAGEABLE ) == 0 )
277             continue;
278         FOREACH_ARRAY( const addon_file_t *p_file, p_entry->files )
279             if ( p_file->e_filetype == e_type
280                  && !strcmp( p_file->psz_filename, psz_file ) )
281                 return true;
282         FOREACH_END();
283     FOREACH_END();
284     return false;
285 }
286
287 static int ListScripts( addons_finder_t *p_finder, addon_type_t type )
288 {
289     char *psz_dir = getAddonInstallDir( type );
290     if ( ! psz_dir ) return VLC_EGENERIC;
291
292     char **ppsz_list = NULL;
293     int i_count = vlc_scandir( psz_dir, &ppsz_list, ListScript_filter, NULL );
294
295     for ( int i=0; i< i_count; i++ )
296     {
297         char *psz_file = ppsz_list[i];
298         if( !psz_file )
299             break;
300         if ( FileBelongsToManagedAddon( p_finder, type, psz_file ) )
301              continue;
302         addon_entry_t *p_entry = addon_entry_New();
303         p_entry->e_state = ADDON_INSTALLED;
304         p_entry->e_type = type;
305         p_entry->e_flags |= ADDON_BROKEN;
306         p_entry->psz_name = strdup(psz_file);
307         p_entry->psz_description = strdup("Lua script");
308
309         ARRAY_APPEND( p_finder->entries, p_entry );
310         free( psz_file );
311     }
312
313     free( ppsz_list );
314     free( psz_dir );
315
316     return VLC_SUCCESS;
317 }
318
319 static int List( addons_finder_t *p_finder )
320 {
321     addon_type_t types[] = {
322         ADDON_EXTENSION,
323         ADDON_PLAYLIST_PARSER,
324         ADDON_SERVICE_DISCOVERY,
325         ADDON_INTERFACE,
326         ADDON_META,
327     };
328     unsigned int i_type = 0;
329
330     LoadCatalog( p_finder );
331
332     /* Browse dirs to find rogue files */
333     while( i_type < ARRAY_SIZE( types ) )
334     {
335         ListScripts( p_finder, types[i_type++] );
336     }
337     ListSkins( p_finder );
338
339     return VLC_SUCCESS;
340 }
341
342 static int recursive_mkdir( vlc_object_t *p_this, const char *psz_dirname )
343 {/* stolen from config_CreateDir() */
344     if( !psz_dirname || !*psz_dirname ) return -1;
345
346     if( vlc_mkdir( psz_dirname, 0700 ) == 0 )
347         return 0;
348
349     switch( errno )
350     {
351         case EEXIST:
352             return 0;
353
354         case ENOENT:
355         {
356             /* Let's try to create the parent directory */
357             char psz_parent[strlen( psz_dirname ) + 1], *psz_end;
358             strcpy( psz_parent, psz_dirname );
359
360             psz_end = strrchr( psz_parent, DIR_SEP_CHAR );
361             if( psz_end && psz_end != psz_parent )
362             {
363                 *psz_end = '\0';
364                 if( recursive_mkdir( p_this, psz_parent ) == 0 )
365                 {
366                     if( !vlc_mkdir( psz_dirname, 0700 ) )
367                         return 0;
368                 }
369             }
370         }
371     }
372
373     msg_Warn( p_this, "could not create %s: %m", psz_dirname );
374     return -1;
375 }
376
377 static int InstallFile( addons_storage_t *p_this, const char *psz_downloadlink,
378                         const char *psz_dest )
379 {
380     stream_t *p_stream;
381     FILE *p_destfile;
382     char buffer[1<<10];
383     int i_read = 0;
384
385     p_stream = stream_UrlNew( p_this, psz_downloadlink );
386     if( !p_stream )
387     {
388         msg_Err( p_this, "Failed to access Addon download url %s", psz_downloadlink );
389         return VLC_EGENERIC;
390     }
391
392     char *psz_path = strdup( psz_dest );
393     if ( !psz_path )
394     {
395         stream_Delete( p_stream );
396         return VLC_ENOMEM;
397     }
398     char *psz_buf = strrchr( psz_path, DIR_SEP_CHAR );
399     if( psz_buf )
400     {
401         *++psz_buf = '\0';
402         /* ensure directory exists */
403         if( !EMPTY_STR( psz_path ) ) recursive_mkdir( VLC_OBJECT(p_this), psz_path );
404         free( psz_path );
405     }
406
407     p_destfile = vlc_fopen( psz_dest, "w" );
408     if( !p_destfile )
409     {
410         msg_Err( p_this, "Failed to open Addon storage file %s", psz_dest );
411         stream_Delete( p_stream );
412         return VLC_EGENERIC;
413     }
414
415     while ( ( i_read = stream_Read( p_stream, &buffer, 1<<10 ) ) )
416     {
417         if ( fwrite( &buffer, i_read, 1, p_destfile ) < 1 )
418         {
419             msg_Err( p_this, "Failed to write to Addon file" );
420             fclose( p_destfile );
421             stream_Delete( p_stream );
422             return VLC_EGENERIC;
423         }
424     }
425
426     fclose( p_destfile );
427     stream_Delete( p_stream );
428     return VLC_SUCCESS;
429 }
430
431 static int InstallAllFiles( addons_storage_t *p_this, const addon_entry_t *p_entry )
432 {
433     const addon_file_t *p_file;
434     char *psz_dest;
435
436     if ( p_entry->files.i_size < 1 )
437         return VLC_EGENERIC;
438
439     FOREACH_ARRAY( p_file, p_entry->files )
440
441     switch( p_file->e_filetype )
442     {
443         case ADDON_EXTENSION:
444         case ADDON_PLAYLIST_PARSER:
445         case ADDON_SERVICE_DISCOVERY:
446         case ADDON_INTERFACE:
447         case ADDON_META:
448         case ADDON_SKIN2:
449         {
450             if ( strstr( p_file->psz_filename, ".." ) )
451                 return VLC_EGENERIC;
452
453             char *psz_translated_filename = strdup( p_file->psz_filename );
454             if ( !psz_translated_filename )
455                 return VLC_ENOMEM;
456             char *tmp = psz_translated_filename;
457             while (*tmp++) if ( *tmp == '/' ) *tmp = DIR_SEP_CHAR;
458
459             char *psz_dir = getAddonInstallDir( p_file->e_filetype );
460             if ( !psz_dir || asprintf( &psz_dest, "%s"DIR_SEP"%s", psz_dir,
461                            psz_translated_filename ) < 1 )
462             {
463                 free( psz_dir );
464                 free( psz_translated_filename );
465                 return VLC_EGENERIC;
466             }
467             free( psz_translated_filename );
468             free( psz_dir );
469
470             if ( InstallFile( p_this, p_file->psz_download_uri, psz_dest ) != VLC_SUCCESS )
471             {
472                 free( psz_dest );
473                 return VLC_EGENERIC;
474             }
475
476             free( psz_dest );
477             break;
478         }
479         /* Ignore all other unhandled files */
480         case ADDON_UNKNOWN:
481         case ADDON_PLUGIN:
482         case ADDON_OTHER:
483         default:
484             break;
485     }
486
487     FOREACH_END()
488
489     return VLC_SUCCESS;
490 }
491
492 static int Install( addons_storage_t *p_storage, addon_entry_t *p_entry )
493 {
494     vlc_object_t *p_this = VLC_OBJECT( p_storage );
495     int i_ret = VLC_EGENERIC;
496
497     if ( ! p_entry->psz_source_module )
498         return i_ret;
499
500     /* Query origin module for download path */
501     addons_finder_t *p_finder = vlc_object_create( p_this, sizeof( addons_finder_t ) );
502     if( !p_finder )
503         return VLC_ENOMEM;
504
505     module_t *p_module = module_need( p_finder, "addons finder",
506                                       p_entry->psz_source_module, true );
507     if( p_module )
508     {
509         if ( p_finder->pf_retrieve( p_finder, p_entry ) == VLC_SUCCESS )
510         {
511             /* Do things while retrieved data is here */
512             vlc_mutex_lock( &p_entry->lock );
513             i_ret = InstallAllFiles( p_storage, p_entry );
514             vlc_mutex_unlock( &p_entry->lock );
515             /* !Do things while retrieved data is here */
516         }
517
518         module_unneed( p_finder, p_module );
519     }
520
521     vlc_object_release( p_finder );
522
523     return i_ret;
524 }
525
526 #define WRITE_WITH_ENTITIES( formatstring, varname ) \
527 if ( varname ) \
528 {\
529     psz_tempstring = convert_xml_special_chars( varname );\
530     fprintf( p_catalog, formatstring, psz_tempstring );\
531     free( psz_tempstring );\
532 }\
533
534 static int WriteCatalog( addons_storage_t *p_storage,
535                          addon_entry_t **pp_entries, int i_entries )
536 {
537     addon_entry_t *p_entry;
538     char *psz_file;
539     char *psz_file_tmp;
540     char *psz_tempstring;
541     char *psz_userdir = config_GetUserDir( VLC_DATA_DIR );
542     if ( !psz_userdir ) return VLC_ENOMEM;
543
544     if ( asprintf( &psz_file, "%s%s", psz_userdir, ADDONS_CATALOG ) < 1 )
545     {
546         free( psz_userdir );
547         return VLC_ENOMEM;
548     }
549     free( psz_userdir );
550
551     if ( asprintf( &psz_file_tmp, "%s.tmp%"PRIu32, psz_file, (uint32_t)getpid() ) < 1 )
552     {
553         free( psz_file );
554         return VLC_ENOMEM;
555     }
556
557     char *psz_path = strdup( psz_file );
558     if ( !psz_path )
559     {
560         free( psz_file );
561         free( psz_file_tmp );
562         return VLC_ENOMEM;
563     }
564
565     char *psz_buf = strrchr( psz_path, DIR_SEP_CHAR );
566     if( psz_buf )
567     {
568         *++psz_buf = '\0';
569         /* ensure directory exists */
570         if( !EMPTY_STR( psz_path ) ) recursive_mkdir( VLC_OBJECT(p_storage), psz_path );
571     }
572     free( psz_path );
573
574     FILE *p_catalog = vlc_fopen( psz_file_tmp, "wt" );
575     if ( !p_catalog )
576     {
577         free( psz_file );
578         free( psz_file_tmp );
579         return VLC_EGENERIC;
580     }
581
582     /* write XML header */
583     fprintf( p_catalog, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
584     fprintf( p_catalog, "<videolan xmlns=\"http://videolan.org/ns/vlc/addons/1.0\">\n" );
585     fprintf( p_catalog, "\t<addons>\n" );
586
587     for ( int i=0; i<i_entries; i++ )
588     {
589         p_entry = pp_entries[i];
590         vlc_mutex_lock( &p_entry->lock );
591         psz_tempstring = NULL;
592
593         if ( ( p_entry->e_state != ADDON_INSTALLED ) ||
594              !( p_entry->e_flags & ADDON_MANAGEABLE ) )
595         {
596             vlc_mutex_unlock( &p_entry->lock );
597             continue;
598         }
599
600         if ( p_entry->psz_source_module )
601             psz_tempstring = convert_xml_special_chars( p_entry->psz_source_module );
602
603         char *psz_uuid = addons_uuid_to_psz( ( const addon_uuid_t * ) & p_entry->uuid );
604         fprintf( p_catalog, "\t\t<addon source=\"%s\" type=\"%s\" id=\"%s\" "
605                                  "downloads=\"%ld\" score=\"%d\"",
606                  ( psz_tempstring ) ? psz_tempstring : "",
607                  getTypePsz( p_entry->e_type ),
608                  psz_uuid,
609                  p_entry->i_downloads,
610                  p_entry->i_score );
611         free( psz_uuid );
612         free( psz_tempstring );
613
614         WRITE_WITH_ENTITIES( " version=\"%s\"", p_entry->psz_version )
615         fprintf( p_catalog, ">\n" );
616
617         WRITE_WITH_ENTITIES( "\t\t\t<name>%s</name>\n", p_entry->psz_name )
618         WRITE_WITH_ENTITIES( "\t\t\t<summary>%s</summary>\n", p_entry->psz_summary )
619
620         if ( p_entry->psz_description )
621         {
622             psz_tempstring = p_entry->psz_description;
623             /* FIXME: do real escaping */
624             while( ( psz_tempstring = strstr( psz_tempstring, "]]>" ) ) )
625                 *psz_tempstring = ' ';
626             fprintf( p_catalog, "\t\t\t<description><![CDATA[%s]]></description>\n", p_entry->psz_description );
627         }
628
629         WRITE_WITH_ENTITIES( "\t\t\t<image>%s</image>\n", p_entry->psz_image_data )
630         WRITE_WITH_ENTITIES( "\t\t\t<archive>%s</archive>\n", p_entry->psz_archive_uri )
631
632         fprintf( p_catalog, "\t\t\t<authorship>\n" );
633         WRITE_WITH_ENTITIES( "\t\t\t\t<creator>%s</creator>\n", p_entry->psz_author )
634         WRITE_WITH_ENTITIES( "\t\t\t\t<sourceurl>%s</sourceurl>\n", p_entry->psz_source_uri )
635         fprintf( p_catalog, "\t\t\t</authorship>\n" );
636
637         FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
638             psz_tempstring = convert_xml_special_chars( p_file->psz_filename );
639             fprintf( p_catalog, "\t\t\t<resource type=\"%s\">%s</resource>\n",
640                      getTypePsz( p_file->e_filetype ), psz_tempstring );
641             free( psz_tempstring );
642         FOREACH_END();
643
644         fprintf( p_catalog, "\t\t</addon>\n" );
645
646         vlc_mutex_unlock( &p_entry->lock );
647     }
648
649     fprintf( p_catalog, "\t</addons>\n" );
650     fprintf( p_catalog, "</videolan>\n" );
651     fclose( p_catalog );
652
653     int i_ret = vlc_rename( psz_file_tmp, psz_file );
654     free( psz_file );
655     free( psz_file_tmp );
656
657     if( i_ret == -1 )
658     {
659         msg_Err( p_storage, "could not rename temp catalog: %s",
660                  vlc_strerror_c(errno) );
661         return VLC_EGENERIC;
662     }
663
664     return VLC_SUCCESS;
665 }
666
667 static int LoadCatalog( addons_finder_t *p_finder )
668 {
669     char *psz_path;
670     char * psz_userdir = config_GetUserDir( VLC_DATA_DIR );
671     if ( !psz_userdir ) return VLC_ENOMEM;
672
673     if ( asprintf( &psz_path, "%s%s", psz_userdir, ADDONS_CATALOG ) < 1 )
674     {
675         free( psz_userdir );
676         return VLC_ENOMEM;
677     }
678     free( psz_userdir );
679
680     addon_entry_t *p_entry = NULL;
681     const char *p_node;
682     int i_current_node_type;
683     int i_ret = VLC_SUCCESS;
684
685     /* attr */
686     const char *attr, *value;
687
688     /* temp reading */
689     char *psz_filename = NULL;
690     int i_filetype = -1;
691
692     struct stat stat_;
693     if ( vlc_stat( psz_path, &stat_ ) )
694     {
695         free( psz_path );
696         return VLC_EGENERIC;
697     }
698
699     char *psz_catalog_uri = vlc_path2uri( psz_path, "file" );
700     free( psz_path );
701     if ( !psz_catalog_uri )
702         return VLC_EGENERIC;
703
704     stream_t *p_stream = stream_UrlNew( p_finder, psz_catalog_uri );
705     free( psz_catalog_uri );
706     if (! p_stream ) return VLC_EGENERIC;
707
708     xml_reader_t *p_xml_reader = xml_ReaderCreate( p_finder, p_stream );
709     if( !p_xml_reader )
710     {
711         stream_Delete( p_stream );
712         return VLC_EGENERIC;
713     }
714
715     if( xml_ReaderNextNode( p_xml_reader, &p_node ) != XML_READER_STARTELEM )
716     {
717         msg_Err( p_finder, "invalid catalog" );
718         i_ret = VLC_EGENERIC;
719         goto end;
720     }
721
722     if ( strcmp( p_node, "videolan") )
723     {
724         msg_Err( p_finder, "unsupported catalog data format" );
725         i_ret = VLC_EGENERIC;
726         goto end;
727     }
728
729     while( (i_current_node_type = xml_ReaderNextNode( p_xml_reader, &p_node )) > 0 )
730     {
731         switch( i_current_node_type )
732         {
733         case XML_READER_STARTELEM:
734         {
735             if ( ! strcmp( p_node, "addon" ) )
736             {
737                 if ( p_entry ) /* ?!? Unclosed tag */
738                     addon_entry_Release( p_entry );
739
740                 p_entry = addon_entry_New();
741                 //p_entry->psz_source_module = strdup( ADDONS_MODULE_SHORTCUT );
742                 p_entry->e_flags = ADDON_MANAGEABLE;
743                 p_entry->e_state = ADDON_INSTALLED;
744
745                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
746                 {
747                     if ( !strcmp( attr, "type" ) )
748                     {
749                         p_entry->e_type = ReadType( value );
750                     }
751                     else if ( !strcmp( attr, "id" ) )
752                     {
753                         addons_uuid_read( value, & p_entry->uuid );
754                     }
755                     else if ( !strcmp( attr, "downloads" ) )
756                     {
757                         p_entry->i_downloads = atoi( value );
758                         if ( p_entry->i_downloads < 0 )
759                             p_entry->i_downloads = 0;
760                     }
761                     else if ( !strcmp( attr, "score" ) )
762                     {
763                         p_entry->i_score = atoi( value );
764                         if ( p_entry->i_score < 0 )
765                             p_entry->i_score = 0;
766                         else if ( p_entry->i_score > ADDON_MAX_SCORE )
767                             p_entry->i_score = ADDON_MAX_SCORE;
768                     }
769                     else if ( !strcmp( attr, "source" ) )
770                     {
771                         p_entry->psz_source_module = strdup( value );
772                     }
773                     else if ( !strcmp( attr, "version" ) )
774                     {
775                         p_entry->psz_version = strdup( value );
776                     }
777                 }
778
779                 break;
780             }
781             if ( !p_entry ) break;
782
783             BINDNODE("name", p_entry->psz_name, TYPE_STRING)
784             BINDNODE("archive", p_entry->psz_archive_uri, TYPE_STRING)
785             BINDNODE("summary", p_entry->psz_summary, TYPE_STRING)
786             BINDNODE("description", p_entry->psz_description, TYPE_STRING)
787             BINDNODE("image", p_entry->psz_image_data, TYPE_STRING)
788             BINDNODE("resource", psz_filename, TYPE_STRING)
789             BINDNODE("creator", p_entry->psz_author, TYPE_STRING)
790             BINDNODE("sourceurl", p_entry->psz_source_uri, TYPE_STRING)
791             data_pointer.e_type = TYPE_NONE;
792
793             if ( ! strcmp( p_node, "resource" ) )
794             {
795                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
796                 {
797                     if ( !strcmp( attr, "type" ) )
798                     {
799                         i_filetype = ReadType( value );
800                     }
801                 }
802             }
803
804             break;
805         }
806         case XML_READER_TEXT:
807             if ( data_pointer.e_type == TYPE_NONE || !p_entry ) break;
808             if ( data_pointer.e_type == TYPE_STRING )
809                 *data_pointer.u_data.ppsz = strdup( p_node );
810             else
811             if ( data_pointer.e_type == TYPE_LONG )
812                 *data_pointer.u_data.pl = atol( p_node );
813             else
814             if ( data_pointer.e_type == TYPE_INTEGER )
815                 *data_pointer.u_data.pi = atoi( p_node );
816             break;
817
818         case XML_READER_ENDELEM:
819             if ( !p_entry ) break;
820
821             if ( ! strcmp( p_node, "addon" ) )
822             {
823                 /* then append entry */
824                 ARRAY_APPEND( p_finder->entries, p_entry );
825                 p_entry = NULL;
826             }
827
828             if ( ! strcmp( p_node, "resource" ) )
829             {
830                 if ( p_entry && psz_filename && i_filetype >= 0 )
831                 {
832                     addon_file_t *p_file = malloc( sizeof(addon_file_t) );
833                     p_file->e_filetype = i_filetype;
834                     p_file->psz_filename = psz_filename;
835                     p_file->psz_download_uri = NULL;
836                     ARRAY_APPEND( p_entry->files, p_file );
837                 }
838                 /* reset temp */
839                 psz_filename = NULL;
840                 i_filetype = -1;
841             }
842
843             data_pointer.e_type = TYPE_NONE;
844             break;
845
846         default:
847             break;
848         }
849     }
850
851 end:
852    if ( p_entry ) /* ?!? Unclosed tag */
853        addon_entry_Release( p_entry );
854    xml_ReaderDelete( p_xml_reader );
855    stream_Delete( p_stream );
856    return i_ret;
857 }
858
859 static int Remove( addons_storage_t *p_storage, addon_entry_t *p_entry )
860 {
861     vlc_mutex_lock( &p_entry->lock );
862     FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
863     switch( p_file->e_filetype )
864     {
865         case ADDON_EXTENSION:
866         case ADDON_PLAYLIST_PARSER:
867         case ADDON_SERVICE_DISCOVERY:
868         case ADDON_INTERFACE:
869         case ADDON_META:
870         case ADDON_SKIN2:
871         {
872             char *psz_dest;
873
874             char *psz_translated_filename = strdup( p_file->psz_filename );
875             if ( !psz_translated_filename )
876                 return VLC_ENOMEM;
877             char *tmp = psz_translated_filename;
878             while (*tmp++) if ( *tmp == '/' ) *tmp = DIR_SEP_CHAR;
879
880             char *psz_dir = getAddonInstallDir( p_file->e_filetype );
881             if ( !psz_dir || asprintf( &psz_dest, "%s"DIR_SEP"%s", psz_dir,
882                                        psz_translated_filename ) < 1 )
883             {
884                 free( psz_dir );
885                 free( psz_translated_filename );
886                 return VLC_EGENERIC;
887             }
888             free( psz_dir );
889             free( psz_translated_filename );
890
891             vlc_unlink( psz_dest );
892             msg_Dbg( p_storage, "removing %s", psz_dest );
893
894             free( psz_dest );
895             break;
896         }
897             /* Ignore all other unhandled files */
898         case ADDON_UNKNOWN:
899         case ADDON_PLUGIN:
900         case ADDON_OTHER:
901         default:
902             break;
903     }
904     FOREACH_END()
905
906     /* Remove file info on success */
907     FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
908     free( p_file->psz_filename );
909     free( p_file->psz_download_uri );
910     free( p_file );
911     FOREACH_END()
912     ARRAY_RESET( p_entry->files );
913
914     vlc_mutex_unlock( &p_entry->lock );
915     return VLC_SUCCESS;
916 }
917
918 static int OpenStorage(vlc_object_t *p_this)
919 {
920     addons_storage_t *p_storage = (addons_storage_t*) p_this;
921
922     p_storage->pf_install = Install;
923     p_storage->pf_remove = Remove;
924     p_storage->pf_catalog = WriteCatalog;
925
926     return VLC_SUCCESS;
927 }
928
929 static void CloseStorage(vlc_object_t *p_this)
930 {
931     VLC_UNUSED( p_this );
932 }
933
934 static int OpenLister(vlc_object_t *p_this)
935 {
936     addons_finder_t *p_finder = (addons_finder_t*) p_this;
937     p_finder->pf_find = List;
938     p_finder->pf_retrieve = NULL;
939
940     return VLC_SUCCESS;
941 }
942
943 static void CloseLister(vlc_object_t *p_this)
944 {
945     VLC_UNUSED( p_this );
946 }