1 /*****************************************************************************
2 * vorepository.c : Videolan.org's Addons Lister
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 *****************************************************************************/
31 #include <unistd.h> /* write() */
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_stream.h>
36 #include <vlc_stream_extractor.h>
37 #include <vlc_addons.h>
41 #include "xmlreading.h"
43 /*****************************************************************************
45 *****************************************************************************/
47 static int Open ( vlc_object_t
* );
48 static void Close ( vlc_object_t
* );
49 static int Retrieve ( addons_finder_t
*p_finder
, addon_entry_t
*p_entry
);
50 static int OpenDesignated ( vlc_object_t
* );
51 static int FindDesignated ( addons_finder_t
*p_finder
);
53 #define ADDONS_MODULE_SHORTCUT "addons.vo"
54 #define ADDONS_REPO_SCHEMEHOST "https://api-addons.videolan.org"
55 /*****************************************************************************
57 ****************************************************************************/
60 set_category(CAT_ADVANCED
)
61 set_subcategory(SUBCAT_ADVANCED_MISC
)
62 set_shortname(N_("Videolan.org's addons finder"))
63 add_shortcut(ADDONS_MODULE_SHORTCUT
)
64 set_description(N_("addons.videolan.org addons finder"))
65 set_capability("addons finder", 100)
66 set_callbacks(Open
, Close
)
68 set_category(CAT_ADVANCED
)
69 set_subcategory(SUBCAT_ADVANCED_MISC
)
70 set_shortname(N_("Videolan.org's single archive addons finder"))
71 add_shortcut(ADDONS_MODULE_SHORTCUT
".vlp")
72 set_description(N_("single .vlp archive addons finder"))
73 set_capability("addons finder", 101)
74 set_callbacks(OpenDesignated
, NULL
)
77 struct addons_finder_sys_t
82 static int ParseManifest( addons_finder_t
*p_finder
, addon_entry_t
*p_entry
,
83 const char *psz_tempfileuri
, stream_t
*p_stream
)
85 int i_num_entries_created
= 0;
87 int i_current_node_type
;
90 const char *attr
, *value
;
93 char *psz_filename
= NULL
;
96 xml_reader_t
*p_xml_reader
= xml_ReaderCreate( p_finder
, p_stream
);
97 if( !p_xml_reader
) return 0;
99 if( xml_ReaderNextNode( p_xml_reader
, &p_node
) != XML_READER_STARTELEM
)
101 msg_Err( p_finder
, "invalid xml file" );
105 if ( strcmp( p_node
, "videolan") )
107 msg_Err( p_finder
, "unsupported XML data format" );
111 while( (i_current_node_type
= xml_ReaderNextNode( p_xml_reader
, &p_node
)) > 0 )
113 switch( i_current_node_type
)
115 case XML_READER_STARTELEM
:
117 BINDNODE("resource", psz_filename
, TYPE_STRING
)
118 data_pointer
.e_type
= TYPE_NONE
;
121 * Manifests are not allowed to update addons properties
122 * such as uuid, score, downloads, ...
123 * On the other hand, repo API must not set files directly.
126 if ( ! strcmp( p_node
, "resource" ) )
128 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &value
)) )
130 if ( !strcmp( attr
, "type" ) )
132 i_filetype
= ReadType( value
);
136 else if ( ! strcmp( p_node
, "addon" ) )
138 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &value
)) )
140 if ( !strcmp( attr
, "type" ) )
142 p_entry
->e_type
= ReadType( value
);
149 case XML_READER_TEXT
:
150 if ( data_pointer
.e_type
== TYPE_NONE
|| !p_entry
) break;
151 if ( data_pointer
.e_type
== TYPE_STRING
)
153 if( data_pointer
.u_data
.ppsz
)
154 free( *data_pointer
.u_data
.ppsz
);
155 *data_pointer
.u_data
.ppsz
= strdup( p_node
);
158 if ( data_pointer
.e_type
== TYPE_LONG
)
159 *data_pointer
.u_data
.pl
= atol( p_node
);
161 if ( data_pointer
.e_type
== TYPE_INTEGER
)
162 *data_pointer
.u_data
.pi
= atoi( p_node
);
165 case XML_READER_ENDELEM
:
167 if ( ! strcmp( p_node
, "resource" ) )
169 if ( psz_filename
&& i_filetype
>= 0 )
171 addon_file_t
*p_file
= malloc( sizeof(addon_file_t
) );
172 p_file
->e_filetype
= i_filetype
;
173 p_file
->psz_filename
= strdup( psz_filename
);
174 if ( asprintf( & p_file
->psz_download_uri
, "%s#!/%s",
175 psz_tempfileuri
, psz_filename
) > 0 )
177 ARRAY_APPEND( p_entry
->files
, p_file
);
178 msg_Dbg( p_finder
, "manifest lists file %s extractable from %s",
179 psz_filename
, p_file
->psz_download_uri
);
180 i_num_entries_created
++;
184 free( p_file
->psz_filename
);
189 free( psz_filename
);
194 data_pointer
.e_type
= TYPE_NONE
;
203 xml_ReaderDelete( p_xml_reader
);
204 return i_num_entries_created
;
207 static int ParseCategoriesInfo( addons_finder_t
*p_finder
, stream_t
*p_stream
)
209 int i_num_entries_created
= 0;
212 const char *attr
, *value
;
213 int i_current_node_type
;
214 addon_entry_t
*p_entry
= NULL
;
216 xml_reader_t
*p_xml_reader
= xml_ReaderCreate( p_finder
, p_stream
);
217 if( !p_xml_reader
) return 0;
219 if( xml_ReaderNextNode( p_xml_reader
, &p_node
) != XML_READER_STARTELEM
)
221 msg_Err( p_finder
, "invalid xml file" );
225 if ( strcmp( p_node
, "videolan") )
227 msg_Err( p_finder
, "unsupported XML data format" );
231 while( (i_current_node_type
= xml_ReaderNextNode( p_xml_reader
, &p_node
)) > 0 )
233 switch( i_current_node_type
)
235 case XML_READER_STARTELEM
:
237 if ( ! strcmp( p_node
, "addon" ) )
239 if ( p_entry
) /* Unclosed tag */
240 addon_entry_Release( p_entry
);
241 p_entry
= addon_entry_New();
242 p_entry
->psz_source_module
= strdup( ADDONS_MODULE_SHORTCUT
);
243 p_entry
->e_flags
= ADDON_MANAGEABLE
;
244 p_entry
->e_state
= ADDON_NOTINSTALLED
;
246 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &value
)) )
248 if ( !strcmp( attr
, "type" ) )
250 p_entry
->e_type
= ReadType( value
);
252 else if ( !strcmp( attr
, "id" ) )
254 addons_uuid_read( value
, & p_entry
->uuid
);
256 else if ( !strcmp( attr
, "downloads" ) )
258 p_entry
->i_downloads
= atoi( value
);
259 if ( p_entry
->i_downloads
< 0 )
260 p_entry
->i_downloads
= 0;
262 else if ( !strcmp( attr
, "score" ) )
264 p_entry
->i_score
= atoi( value
);
265 if ( p_entry
->i_score
< 0 )
266 p_entry
->i_score
= 0;
267 else if ( p_entry
->i_score
> ADDON_MAX_SCORE
)
268 p_entry
->i_score
= ADDON_MAX_SCORE
;
270 else if ( !strcmp( attr
, "version" ) )
272 p_entry
->psz_version
= strdup( value
);
278 if ( !p_entry
) break;
280 BINDNODE("name", p_entry
->psz_name
, TYPE_STRING
)
281 BINDNODE("archive", p_entry
->psz_archive_uri
, TYPE_STRING
)
282 BINDNODE("summary", p_entry
->psz_summary
, TYPE_STRING
)
283 BINDNODE("description", p_entry
->psz_description
, TYPE_STRING
)
284 BINDNODE("image", p_entry
->psz_image_data
, TYPE_STRING
)
285 BINDNODE("creator", p_entry
->psz_author
, TYPE_STRING
)
286 BINDNODE("sourceurl", p_entry
->psz_source_uri
, TYPE_STRING
)
287 data_pointer
.e_type
= TYPE_NONE
;
291 case XML_READER_TEXT
:
292 if ( data_pointer
.e_type
== TYPE_NONE
|| !p_entry
) break;
293 if ( data_pointer
.e_type
== TYPE_STRING
)
295 if( data_pointer
.u_data
.ppsz
)
296 free( *data_pointer
.u_data
.ppsz
);
297 *data_pointer
.u_data
.ppsz
= strdup( p_node
);
300 if ( data_pointer
.e_type
== TYPE_LONG
)
301 *data_pointer
.u_data
.pl
= atol( p_node
);
303 if ( data_pointer
.e_type
== TYPE_INTEGER
)
304 *data_pointer
.u_data
.pi
= atoi( p_node
);
307 case XML_READER_ENDELEM
:
308 if ( !p_entry
) break;
309 if ( ! strcmp( p_node
, "addon" ) )
311 /* then append entry */
312 ARRAY_APPEND( p_finder
->entries
, p_entry
);
314 i_num_entries_created
++;
317 data_pointer
.e_type
= TYPE_NONE
;
326 if ( p_entry
) /* Unclosed tag */
327 addon_entry_Release( p_entry
);
328 xml_ReaderDelete( p_xml_reader
);
329 return i_num_entries_created
;
332 static stream_t
* vlc_stream_NewURL_ND( addons_finder_t
*p_obj
, const char *psz_uri
)
334 stream_t
*p_stream
= vlc_stream_NewURL( p_obj
, psz_uri
);
337 /* (non applicable everywhere) remove extra
338 * compression, bad wine madness :YYY */
339 stream_t
*p_chain
= vlc_stream_FilterNew( p_stream
, "inflate" );
346 static int Find( addons_finder_t
*p_finder
)
352 char *psz_uri
= NULL
;
354 if ( ! asprintf( &psz_uri
, ADDONS_REPO_SCHEMEHOST
"/xml" ) ) return VLC_ENOMEM
;
357 stream_t
*p_stream
= vlc_stream_NewURL_ND( p_finder
, psz_uri
);
359 if ( !p_stream
) return VLC_EGENERIC
;
361 if ( ! ParseCategoriesInfo( p_finder
, p_stream
) )
363 /* no more entries have been read: was last page or error */
367 vlc_stream_Delete( p_stream
);
373 static int Retrieve( addons_finder_t
*p_finder
, addon_entry_t
*p_entry
)
375 vlc_mutex_lock( &p_entry
->lock
);
376 if ( !p_entry
->psz_archive_uri
)
378 vlc_mutex_unlock( &p_entry
->lock
);
381 char *psz_archive_uri
= strdup( p_entry
->psz_archive_uri
);
382 vlc_mutex_unlock( &p_entry
->lock
);
383 if ( !psz_archive_uri
)
386 /* get archive and parse manifest */
389 if ( psz_archive_uri
[0] == '/' )
393 if ( ! asprintf( &psz_uri
, ADDONS_REPO_SCHEMEHOST
"%s", psz_archive_uri
) )
395 free( psz_archive_uri
);
398 p_stream
= vlc_stream_NewURL_ND( p_finder
, psz_uri
);
403 p_stream
= vlc_stream_NewURL_ND( p_finder
, psz_archive_uri
);
406 msg_Dbg( p_finder
, "downloading archive %s", psz_archive_uri
);
407 free ( psz_archive_uri
);
408 if ( !p_stream
) return VLC_EGENERIC
;
410 /* In case of pf_ reuse */
411 if ( p_finder
->p_sys
->psz_tempfile
)
413 vlc_unlink( p_finder
->p_sys
->psz_tempfile
);
414 FREENULL( p_finder
->p_sys
->psz_tempfile
);
417 p_finder
->p_sys
->psz_tempfile
= tempnam( NULL
, "vlp" );
418 if ( !p_finder
->p_sys
->psz_tempfile
)
420 msg_Err( p_finder
, "Can't create temp storage file" );
421 vlc_stream_Delete( p_stream
);
425 int fd
= vlc_open( p_finder
->p_sys
->psz_tempfile
,
426 O_WRONLY
| O_CREAT
| O_EXCL
, 0600 );
429 msg_Err( p_finder
, "Failed to open addon temp storage file" );
430 FREENULL(p_finder
->p_sys
->psz_tempfile
);
431 vlc_stream_Delete( p_stream
);
437 int i_ret
= VLC_SUCCESS
;
439 while ( ( i_read
= vlc_stream_Read( p_stream
, &buffer
, 1<<10 ) ) > 0 )
441 if ( write( fd
, buffer
, i_read
) != i_read
)
443 msg_Err( p_finder
, "Failed to write to Addon file" );
444 i_ret
= VLC_EGENERIC
;
450 vlc_stream_Delete( p_stream
);
455 msg_Dbg( p_finder
, "Reading manifest from %s", p_finder
->p_sys
->psz_tempfile
);
457 char *psz_tempfileuri
= vlc_path2uri( p_finder
->p_sys
->psz_tempfile
, NULL
);
458 if ( !psz_tempfileuri
)
461 char *psz_manifest_uri
;
462 if ( asprintf( &psz_manifest_uri
, "%s#!/manifest.xml", psz_tempfileuri
) < 1 )
464 free( psz_tempfileuri
);
468 p_stream
= vlc_stream_NewMRL( p_finder
, psz_manifest_uri
);
469 free( psz_manifest_uri
);
472 free( psz_tempfileuri
);
476 vlc_mutex_lock( &p_entry
->lock
);
477 i_ret
= ( ParseManifest( p_finder
, p_entry
, psz_tempfileuri
, p_stream
) > 0 )
478 ? VLC_SUCCESS
: VLC_EGENERIC
;
479 vlc_mutex_unlock( &p_entry
->lock
);
480 free( psz_tempfileuri
);
481 vlc_stream_Delete( p_stream
);
486 static int FindDesignated( addons_finder_t
*p_finder
)
489 const char *psz_path
= p_finder
->psz_uri
+ 7; // remove scheme
491 if ( asprintf( &psz_manifest
, "file://%s#!/manifest.xml",
495 stream_t
*p_stream
= vlc_stream_NewMRL( p_finder
, psz_manifest
);
496 free( psz_manifest
);
497 if ( !p_stream
) return VLC_EGENERIC
;
499 if ( ParseCategoriesInfo( p_finder
, p_stream
) )
501 /* Do archive uri fixup */
502 FOREACH_ARRAY( addon_entry_t
*p_entry
, p_finder
->entries
)
503 if ( likely( !p_entry
->psz_archive_uri
) )
504 p_entry
->psz_archive_uri
= strdup( p_finder
->psz_uri
);
509 vlc_stream_Delete( p_stream
);
513 vlc_stream_Delete( p_stream
);
518 static int Open(vlc_object_t
*p_this
)
520 addons_finder_t
*p_finder
= (addons_finder_t
*) p_this
;
522 p_finder
->p_sys
= (addons_finder_sys_t
*) malloc(sizeof(addons_finder_sys_t
));
523 if ( !p_finder
->p_sys
)
525 p_finder
->p_sys
->psz_tempfile
= NULL
;
527 if ( p_finder
->psz_uri
&&
528 strcmp( "repo://"ADDONS_MODULE_SHORTCUT
, p_finder
->psz_uri
) &&
529 memcmp( "repo://", p_finder
->psz_uri
, 8 ) )
531 free( p_finder
->p_sys
);
535 p_finder
->pf_find
= Find
;
536 p_finder
->pf_retrieve
= Retrieve
;
541 static void Close(vlc_object_t
*p_this
)
543 addons_finder_t
*p_finder
= (addons_finder_t
*) p_this
;
544 if ( p_finder
->p_sys
->psz_tempfile
)
546 vlc_unlink( p_finder
->p_sys
->psz_tempfile
);
547 free( p_finder
->p_sys
->psz_tempfile
);
549 free( p_finder
->p_sys
);
552 static int OpenDesignated(vlc_object_t
*p_this
)
554 addons_finder_t
*p_finder
= (addons_finder_t
*) p_this
;
555 if ( !p_finder
->psz_uri
556 || strncmp( "file://", p_finder
->psz_uri
, 7 )
557 || strncmp( ".vlp", p_finder
->psz_uri
+ strlen( p_finder
->psz_uri
) - 4, 4 )
561 p_finder
->pf_find
= FindDesignated
;
562 p_finder
->pf_retrieve
= Retrieve
;