1 /*****************************************************************************
2 * addonsstorage.c : Addons Local filesystem storage
3 *****************************************************************************
4 * Copyright (C) 2014 VLC authors and VideoLAN
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.
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.
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 *****************************************************************************/
21 /*****************************************************************************
23 *****************************************************************************/
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>
35 #include <vlc_strings.h>
38 #include "xmlreading.h"
43 #include <unistd.h> // getpid()
45 /*****************************************************************************
47 *****************************************************************************/
50 #define ADDONS_SCRIPTS_DIR ADDONS_DIR DIR_SEP "lua"
51 #define ADDONS_CATALOG ADDONS_DIR DIR_SEP "catalog.xml"
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" },
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 * );
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 /*****************************************************************************
77 ****************************************************************************/
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)
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 )
98 static char * getAddonInstallDir( addon_type_t t )
100 const char *psz_subdir = NULL;
102 char *psz_userdir = config_GetUserDir( VLC_DATA_DIR );
103 if ( !psz_userdir ) return NULL;
105 for ( unsigned int i=0; i< ARRAY_SIZE(addons_dirs); i++ )
107 if ( addons_dirs[i].t == t )
109 psz_subdir = addons_dirs[i].psz_dir;
116 free ( psz_userdir );
120 if ( asprintf( &psz_dir, "%s%s", psz_userdir, psz_subdir ) < 1 )
130 static int ListSkin_filter( const char * psz_filename )
132 int i_len = strlen( psz_filename );
136 return ! strcmp( psz_filename + i_len - 4, ".vlt" );
139 static int ListScript_filter( const char * psz_filename )
141 int i_len = strlen( psz_filename );
145 return ! strcmp( psz_filename + i_len - 4, ".lua" );
148 static int ParseSkins2Info( addons_finder_t *p_finder, stream_t *p_stream,
149 char **ppsz_title, char **ppsz_source )
152 int i_current_node_type;
155 xml_reader_t *p_xml_reader = xml_ReaderCreate( p_finder, p_stream );
156 if( !p_xml_reader ) return VLC_EGENERIC;
158 if( xml_ReaderNextNode( p_xml_reader, &p_node ) != XML_READER_STARTELEM )
160 msg_Err( p_finder, "invalid xml file" );
164 if ( strcmp( p_node, "Theme") )
166 msg_Err( p_finder, "unsupported XML data format" );
170 while( !b_done && (i_current_node_type = xml_ReaderNextNode( p_xml_reader, &p_node )) > 0 )
172 switch( i_current_node_type )
174 case XML_READER_STARTELEM:
176 if ( !strcmp( p_node, "ThemeInfo" ) )
178 const char *attr, *value;
179 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
181 if ( !strcmp( attr, "name" ) )
182 *ppsz_title = strdup( value );
183 else if ( !strcmp( attr, "webpage" ) )
184 *ppsz_source = strdup( value );
196 xml_ReaderDelete( p_xml_reader );
197 return ( b_done ) ? VLC_SUCCESS : VLC_EGENERIC;
200 xml_ReaderDelete( p_xml_reader );
204 static int ListSkins( addons_finder_t *p_finder )
206 char *psz_dir = getAddonInstallDir( ADDON_SKIN2 );
210 char **ppsz_list = NULL;
211 int i_count = vlc_scandir( psz_dir, &ppsz_list, ListSkin_filter, NULL );
213 for ( int i=0; i< i_count; i++ )
215 char *psz_file = ppsz_list[i];
219 if ( FileBelongsToManagedAddon( p_finder, ADDON_SKIN2, psz_file ) )
223 if( asprintf( &psz_uri, "unzip://%s/%s!/theme.xml", psz_dir, psz_file ) >= 0)
226 char *psz_name = NULL;
227 char *psz_source = NULL;
228 stream_t *p_stream = stream_UrlNew( p_finder, psz_uri );
232 i_ret = VLC_EGENERIC;
236 i_ret = ParseSkins2Info( p_finder, p_stream, &psz_name, &psz_source );
237 if ( i_ret != VLC_SUCCESS )
242 stream_Delete( p_stream );
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 )
250 p_entry->psz_name = psz_name;
251 p_entry->psz_description = strdup("Skins2 theme");
252 p_entry->psz_source_uri = psz_source;
256 p_entry->e_flags |= ADDON_BROKEN;
257 p_entry->psz_name = strdup(psz_file);
258 p_entry->psz_description = strdup("Skins2 theme");
261 ARRAY_APPEND( p_finder->entries, p_entry );
271 static bool FileBelongsToManagedAddon( addons_finder_t *p_finder,
272 const addon_type_t e_type,
273 const char *psz_file )
275 FOREACH_ARRAY( const addon_entry_t *p_entry, p_finder->entries )
276 if ( ( p_entry->e_flags & ADDON_MANAGEABLE ) == 0 )
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 ) )
287 static int ListScripts( addons_finder_t *p_finder, addon_type_t type )
289 char *psz_dir = getAddonInstallDir( type );
290 if ( ! psz_dir ) return VLC_EGENERIC;
292 char **ppsz_list = NULL;
293 int i_count = vlc_scandir( psz_dir, &ppsz_list, ListScript_filter, NULL );
295 for ( int i=0; i< i_count; i++ )
297 char *psz_file = ppsz_list[i];
300 if ( FileBelongsToManagedAddon( p_finder, type, psz_file ) )
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");
309 ARRAY_APPEND( p_finder->entries, p_entry );
319 static int List( addons_finder_t *p_finder )
321 addon_type_t types[] = {
323 ADDON_PLAYLIST_PARSER,
324 ADDON_SERVICE_DISCOVERY,
328 unsigned int i_type = 0;
330 LoadCatalog( p_finder );
332 /* Browse dirs to find rogue files */
333 while( i_type < ARRAY_SIZE( types ) )
335 ListScripts( p_finder, types[i_type++] );
337 ListSkins( p_finder );
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;
346 if( vlc_mkdir( psz_dirname, 0700 ) == 0 )
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 );
360 psz_end = strrchr( psz_parent, DIR_SEP_CHAR );
361 if( psz_end && psz_end != psz_parent )
364 if( recursive_mkdir( p_this, psz_parent ) == 0 )
366 if( !vlc_mkdir( psz_dirname, 0700 ) )
373 msg_Warn( p_this, "could not create %s: %m", psz_dirname );
377 static int InstallFile( addons_storage_t *p_this, const char *psz_downloadlink,
378 const char *psz_dest )
385 p_stream = stream_UrlNew( p_this, psz_downloadlink );
388 msg_Err( p_this, "Failed to access Addon download url %s", psz_downloadlink );
392 char *psz_path = strdup( psz_dest );
395 stream_Delete( p_stream );
398 char *psz_buf = strrchr( psz_path, DIR_SEP_CHAR );
402 /* ensure directory exists */
403 if( !EMPTY_STR( psz_path ) ) recursive_mkdir( VLC_OBJECT(p_this), psz_path );
407 p_destfile = vlc_fopen( psz_dest, "w" );
410 msg_Err( p_this, "Failed to open Addon storage file %s", psz_dest );
411 stream_Delete( p_stream );
415 while ( ( i_read = stream_Read( p_stream, &buffer, 1<<10 ) ) )
417 if ( fwrite( &buffer, i_read, 1, p_destfile ) < 1 )
419 msg_Err( p_this, "Failed to write to Addon file" );
420 fclose( p_destfile );
421 stream_Delete( p_stream );
426 fclose( p_destfile );
427 stream_Delete( p_stream );
431 static int InstallAllFiles( addons_storage_t *p_this, const addon_entry_t *p_entry )
433 const addon_file_t *p_file;
436 if ( p_entry->files.i_size < 1 )
439 FOREACH_ARRAY( p_file, p_entry->files )
441 switch( p_file->e_filetype )
443 case ADDON_EXTENSION:
444 case ADDON_PLAYLIST_PARSER:
445 case ADDON_SERVICE_DISCOVERY:
446 case ADDON_INTERFACE:
450 if ( strstr( p_file->psz_filename, ".." ) )
453 char *psz_translated_filename = strdup( p_file->psz_filename );
454 if ( !psz_translated_filename )
456 char *tmp = psz_translated_filename;
457 while (*tmp++) if ( *tmp == '/' ) *tmp = DIR_SEP_CHAR;
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 )
464 free( psz_translated_filename );
467 free( psz_translated_filename );
470 if ( InstallFile( p_this, p_file->psz_download_uri, psz_dest ) != VLC_SUCCESS )
479 /* Ignore all other unhandled files */
492 static int Install( addons_storage_t *p_storage, addon_entry_t *p_entry )
494 vlc_object_t *p_this = VLC_OBJECT( p_storage );
495 int i_ret = VLC_EGENERIC;
497 if ( ! p_entry->psz_source_module )
500 /* Query origin module for download path */
501 addons_finder_t *p_finder = vlc_object_create( p_this, sizeof( addons_finder_t ) );
505 module_t *p_module = module_need( p_finder, "addons finder",
506 p_entry->psz_source_module, true );
509 if ( p_finder->pf_retrieve( p_finder, p_entry ) == VLC_SUCCESS )
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 */
518 module_unneed( p_finder, p_module );
521 vlc_object_release( p_finder );
526 #define WRITE_WITH_ENTITIES( formatstring, varname ) \
529 psz_tempstring = convert_xml_special_chars( varname );\
530 fprintf( p_catalog, formatstring, psz_tempstring );\
531 free( psz_tempstring );\
534 static int WriteCatalog( addons_storage_t *p_storage,
535 addon_entry_t **pp_entries, int i_entries )
537 addon_entry_t *p_entry;
540 char *psz_tempstring;
541 char *psz_userdir = config_GetUserDir( VLC_DATA_DIR );
542 if ( !psz_userdir ) return VLC_ENOMEM;
544 if ( asprintf( &psz_file, "%s%s", psz_userdir, ADDONS_CATALOG ) < 1 )
551 if ( asprintf( &psz_file_tmp, "%s.tmp%"PRIu32, psz_file, (uint32_t)getpid() ) < 1 )
557 char *psz_path = strdup( psz_file );
561 free( psz_file_tmp );
565 char *psz_buf = strrchr( psz_path, DIR_SEP_CHAR );
569 /* ensure directory exists */
570 if( !EMPTY_STR( psz_path ) ) recursive_mkdir( VLC_OBJECT(p_storage), psz_path );
574 FILE *p_catalog = vlc_fopen( psz_file_tmp, "wt" );
578 free( psz_file_tmp );
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" );
587 for ( int i=0; i<i_entries; i++ )
589 p_entry = pp_entries[i];
590 vlc_mutex_lock( &p_entry->lock );
591 psz_tempstring = NULL;
593 if ( ( p_entry->e_state != ADDON_INSTALLED ) ||
594 !( p_entry->e_flags & ADDON_MANAGEABLE ) )
596 vlc_mutex_unlock( &p_entry->lock );
600 if ( p_entry->psz_source_module )
601 psz_tempstring = convert_xml_special_chars( p_entry->psz_source_module );
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 ),
609 p_entry->i_downloads,
612 free( psz_tempstring );
614 WRITE_WITH_ENTITIES( " version=\"%s\"", p_entry->psz_version )
615 fprintf( p_catalog, ">\n" );
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 )
620 if ( p_entry->psz_description )
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 );
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 )
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" );
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 );
644 fprintf( p_catalog, "\t\t</addon>\n" );
646 vlc_mutex_unlock( &p_entry->lock );
649 fprintf( p_catalog, "\t</addons>\n" );
650 fprintf( p_catalog, "</videolan>\n" );
653 int i_ret = vlc_rename( psz_file_tmp, psz_file );
655 free( psz_file_tmp );
659 msg_Err( p_storage, "could not rename temp catalog: %s",
660 vlc_strerror_c(errno) );
667 static int LoadCatalog( addons_finder_t *p_finder )
670 char * psz_userdir = config_GetUserDir( VLC_DATA_DIR );
671 if ( !psz_userdir ) return VLC_ENOMEM;
673 if ( asprintf( &psz_path, "%s%s", psz_userdir, ADDONS_CATALOG ) < 1 )
680 addon_entry_t *p_entry = NULL;
682 int i_current_node_type;
683 int i_ret = VLC_SUCCESS;
686 const char *attr, *value;
689 char *psz_filename = NULL;
693 if ( vlc_stat( psz_path, &stat_ ) )
699 char *psz_catalog_uri = vlc_path2uri( psz_path, "file" );
701 if ( !psz_catalog_uri )
704 stream_t *p_stream = stream_UrlNew( p_finder, psz_catalog_uri );
705 free( psz_catalog_uri );
706 if (! p_stream ) return VLC_EGENERIC;
708 xml_reader_t *p_xml_reader = xml_ReaderCreate( p_finder, p_stream );
711 stream_Delete( p_stream );
715 if( xml_ReaderNextNode( p_xml_reader, &p_node ) != XML_READER_STARTELEM )
717 msg_Err( p_finder, "invalid catalog" );
718 i_ret = VLC_EGENERIC;
722 if ( strcmp( p_node, "videolan") )
724 msg_Err( p_finder, "unsupported catalog data format" );
725 i_ret = VLC_EGENERIC;
729 while( (i_current_node_type = xml_ReaderNextNode( p_xml_reader, &p_node )) > 0 )
731 switch( i_current_node_type )
733 case XML_READER_STARTELEM:
735 if ( ! strcmp( p_node, "addon" ) )
737 if ( p_entry ) /* ?!? Unclosed tag */
738 addon_entry_Release( p_entry );
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;
745 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
747 if ( !strcmp( attr, "type" ) )
749 p_entry->e_type = ReadType( value );
751 else if ( !strcmp( attr, "id" ) )
753 addons_uuid_read( value, & p_entry->uuid );
755 else if ( !strcmp( attr, "downloads" ) )
757 p_entry->i_downloads = atoi( value );
758 if ( p_entry->i_downloads < 0 )
759 p_entry->i_downloads = 0;
761 else if ( !strcmp( attr, "score" ) )
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;
769 else if ( !strcmp( attr, "source" ) )
771 p_entry->psz_source_module = strdup( value );
773 else if ( !strcmp( attr, "version" ) )
775 p_entry->psz_version = strdup( value );
781 if ( !p_entry ) break;
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;
793 if ( ! strcmp( p_node, "resource" ) )
795 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
797 if ( !strcmp( attr, "type" ) )
799 i_filetype = ReadType( value );
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 );
811 if ( data_pointer.e_type == TYPE_LONG )
812 *data_pointer.u_data.pl = atol( p_node );
814 if ( data_pointer.e_type == TYPE_INTEGER )
815 *data_pointer.u_data.pi = atoi( p_node );
818 case XML_READER_ENDELEM:
819 if ( !p_entry ) break;
821 if ( ! strcmp( p_node, "addon" ) )
823 /* then append entry */
824 ARRAY_APPEND( p_finder->entries, p_entry );
828 if ( ! strcmp( p_node, "resource" ) )
830 if ( p_entry && psz_filename && i_filetype >= 0 )
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 );
843 data_pointer.e_type = TYPE_NONE;
852 if ( p_entry ) /* ?!? Unclosed tag */
853 addon_entry_Release( p_entry );
854 xml_ReaderDelete( p_xml_reader );
855 stream_Delete( p_stream );
859 static int Remove( addons_storage_t *p_storage, addon_entry_t *p_entry )
861 vlc_mutex_lock( &p_entry->lock );
862 FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
863 switch( p_file->e_filetype )
865 case ADDON_EXTENSION:
866 case ADDON_PLAYLIST_PARSER:
867 case ADDON_SERVICE_DISCOVERY:
868 case ADDON_INTERFACE:
874 char *psz_translated_filename = strdup( p_file->psz_filename );
875 if ( !psz_translated_filename )
877 char *tmp = psz_translated_filename;
878 while (*tmp++) if ( *tmp == '/' ) *tmp = DIR_SEP_CHAR;
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 )
885 free( psz_translated_filename );
889 free( psz_translated_filename );
891 vlc_unlink( psz_dest );
892 msg_Dbg( p_storage, "removing %s", psz_dest );
897 /* Ignore all other unhandled files */
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 );
912 ARRAY_RESET( p_entry->files );
914 vlc_mutex_unlock( &p_entry->lock );
918 static int OpenStorage(vlc_object_t *p_this)
920 addons_storage_t *p_storage = (addons_storage_t*) p_this;
922 p_storage->pf_install = Install;
923 p_storage->pf_remove = Remove;
924 p_storage->pf_catalog = WriteCatalog;
929 static void CloseStorage(vlc_object_t *p_this)
931 VLC_UNUSED( p_this );
934 static int OpenLister(vlc_object_t *p_this)
936 addons_finder_t *p_finder = (addons_finder_t*) p_this;
937 p_finder->pf_find = List;
938 p_finder->pf_retrieve = NULL;
943 static void CloseLister(vlc_object_t *p_this)
945 VLC_UNUSED( p_this );