]> git.sesse.net Git - vlc/blob - modules/misc/addons/vorepository.c
addons: vorepository: add support for manually downloaded archives
[vlc] / modules / misc / addons / vorepository.c
1 /*****************************************************************************
2  * vorepository.c : Videolan.org's Addons Lister
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_stream.h>
32 #include <vlc_addons.h>
33 #include <vlc_xml.h>
34 #include <vlc_fs.h>
35 #include "xmlreading.h"
36
37 #include "assert.h"
38
39 /*****************************************************************************
40  * Local prototypes
41  *****************************************************************************/
42
43 static int   Open ( vlc_object_t * );
44 static void  Close ( vlc_object_t * );
45 static int   Find ( addons_finder_t *p_finder );
46 static int   Retrieve ( addons_finder_t *p_finder, addon_entry_t *p_entry );
47 static int   OpenDesignated ( vlc_object_t * );
48 static int   FindDesignated ( addons_finder_t *p_finder );
49
50 #define ADDONS_MODULE_SHORTCUT "addons.vo"
51 #define ADDONS_REPO_SCHEMEHOST "http://api.addons.videolan.org"
52 /*****************************************************************************
53  * Module descriptor
54  ****************************************************************************/
55
56 vlc_module_begin ()
57     set_category(CAT_ADVANCED)
58     set_subcategory(SUBCAT_ADVANCED_MISC)
59     set_shortname(N_("Videolan.org's addons finder"))
60     add_shortcut(ADDONS_MODULE_SHORTCUT)
61     set_description(N_("addons.videolan.org addons finder"))
62     set_capability("addons finder", 100)
63     set_callbacks(Open, Close)
64 add_submodule ()
65     set_category(CAT_ADVANCED)
66     set_subcategory(SUBCAT_ADVANCED_MISC)
67     set_shortname(N_("Videolan.org's single archive addons finder"))
68     add_shortcut(ADDONS_MODULE_SHORTCUT".vlp")
69     set_description(N_("single .vlp archive addons finder"))
70     set_capability("addons finder", 101)
71     set_callbacks(OpenDesignated, NULL)
72 vlc_module_end ()
73
74 struct addons_finder_sys_t
75 {
76     char *psz_tempfile;
77 };
78
79 static int ParseManifest( addons_finder_t *p_finder, addon_entry_t *p_entry,
80                           const char *psz_tempfile, stream_t *p_stream )
81 {
82     int i_num_entries_created = 0;
83     const char *p_node;
84     int i_current_node_type;
85
86     /* attr */
87     const char *attr, *value;
88
89     /* temp reading */
90     const char *psz_filename = NULL;
91     int i_filetype = -1;
92
93     xml_reader_t *p_xml_reader = xml_ReaderCreate( p_finder, p_stream );
94     if( !p_xml_reader ) return 0;
95
96     if( xml_ReaderNextNode( p_xml_reader, &p_node ) != XML_READER_STARTELEM )
97     {
98         msg_Err( p_finder, "invalid xml file" );
99         goto end;
100     }
101
102     if ( strcmp( p_node, "videolan") )
103     {
104         msg_Err( p_finder, "unsupported XML data format" );
105         goto end;
106     }
107
108     while( (i_current_node_type = xml_ReaderNextNode( p_xml_reader, &p_node )) > 0 )
109     {
110         switch( i_current_node_type )
111         {
112         case XML_READER_STARTELEM:
113         {
114             BINDNODE("resource", psz_filename, TYPE_STRING)
115             data_pointer.e_type = TYPE_NONE;
116
117             /*
118              * Manifests are not allowed to update addons properties
119              * such as uuid, score, downloads, ...
120              * On the other hand, repo API must not set files directly.
121              */
122
123             if ( ! strcmp( p_node, "resource" ) )
124             {
125                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
126                 {
127                     if ( !strcmp( attr, "type" ) )
128                     {
129                         i_filetype = ReadType( value );
130                     }
131                 }
132             }
133             else if ( ! strcmp( p_node, "addon" ) )
134             {
135                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
136                 {
137                     if ( !strcmp( attr, "type" ) )
138                     {
139                         p_entry->e_type = ReadType( value );
140                     }
141                 }
142             }
143
144             break;
145         }
146         case XML_READER_TEXT:
147             if ( data_pointer.e_type == TYPE_NONE || !p_entry ) break;
148             if ( data_pointer.e_type == TYPE_STRING )
149                 *data_pointer.u_data.ppsz = strdup( p_node );
150             else
151             if ( data_pointer.e_type == TYPE_LONG )
152                 *data_pointer.u_data.pl = atol( p_node );
153             else
154             if ( data_pointer.e_type == TYPE_INTEGER )
155                 *data_pointer.u_data.pi = atoi( p_node );
156             break;
157
158         case XML_READER_ENDELEM:
159
160             if ( ! strcmp( p_node, "resource" ) )
161             {
162                 if ( psz_filename && i_filetype >= 0 )
163                 {
164                     addon_file_t *p_file = malloc( sizeof(addon_file_t) );
165                     p_file->e_filetype = i_filetype;
166                     p_file->psz_filename = strdup( psz_filename );
167                     if ( asprintf( & p_file->psz_download_uri, "unzip://%s!/%s",
168                                    psz_tempfile, psz_filename  ) > 0 )
169                     {
170                         ARRAY_APPEND( p_entry->files, p_file );
171                         msg_Dbg( p_finder, "manifest lists file %s extractable from %s",
172                                  psz_filename, p_file->psz_download_uri );
173                         i_num_entries_created++;
174                     }
175                     else
176                     {
177                         free( p_file->psz_filename );
178                         free( p_file );
179                     }
180                 }
181                 /* reset temp */
182                 psz_filename = NULL;
183                 i_filetype = -1;
184             }
185
186             data_pointer.e_type = TYPE_NONE;
187             break;
188
189         default:
190             break;
191         }
192     }
193
194 end:
195    xml_ReaderDelete( p_xml_reader );
196    return i_num_entries_created;
197 }
198
199 static int ParseCategoriesInfo( addons_finder_t *p_finder, stream_t *p_stream )
200 {
201     int i_num_entries_created = 0;
202
203     const char *p_node;
204     const char *attr, *value;
205     int i_current_node_type;
206     addon_entry_t *p_entry = NULL;
207
208     xml_reader_t *p_xml_reader = xml_ReaderCreate( p_finder, p_stream );
209     if( !p_xml_reader ) return 0;
210
211     if( xml_ReaderNextNode( p_xml_reader, &p_node ) != XML_READER_STARTELEM )
212     {
213         msg_Err( p_finder, "invalid xml file" );
214         goto end;
215     }
216
217     if ( strcmp( p_node, "videolan") )
218     {
219         msg_Err( p_finder, "unsupported XML data format" );
220         goto end;
221     }
222
223     while( (i_current_node_type = xml_ReaderNextNode( p_xml_reader, &p_node )) > 0 )
224     {
225         switch( i_current_node_type )
226         {
227         case XML_READER_STARTELEM:
228         {
229             if ( ! strcmp( p_node, "addon" ) )
230             {
231                 p_entry = addon_entry_New();
232                 p_entry->psz_source_module = strdup( ADDONS_MODULE_SHORTCUT );
233                 p_entry->e_flags = ADDON_MANAGEABLE;
234                 p_entry->e_state = ADDON_NOTINSTALLED;
235
236                 while( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
237                 {
238                     if ( !strcmp( attr, "type" ) )
239                     {
240                         p_entry->e_type = ReadType( value );
241                     }
242                     else if ( !strcmp( attr, "id" ) )
243                     {
244                         addons_uuid_read( value, & p_entry->uuid );
245                     }
246                     else if ( !strcmp( attr, "downloads" ) )
247                     {
248                         p_entry->i_downloads = atoi( value );
249                     }
250                     else if ( !strcmp( attr, "score" ) )
251                     {
252                         p_entry->i_score = atol( value );
253                     }
254                     else if ( !strcmp( attr, "version" ) )
255                     {
256                         p_entry->psz_version = strdup( value );
257                     }
258                 }
259
260                 break;
261             }
262             if ( !p_entry ) break;
263
264             BINDNODE("name", p_entry->psz_name, TYPE_STRING)
265             BINDNODE("archive", p_entry->psz_archive_uri, TYPE_STRING)
266             BINDNODE("summary", p_entry->psz_summary, TYPE_STRING)
267             BINDNODE("description", p_entry->psz_description, TYPE_STRING)
268             BINDNODE("image", p_entry->psz_image_data, TYPE_STRING)
269             BINDNODE("creator", p_entry->psz_author, TYPE_STRING)
270             BINDNODE("sourceurl", p_entry->psz_source_uri, TYPE_STRING)
271             data_pointer.e_type = TYPE_NONE;
272
273             break;
274         }
275         case XML_READER_TEXT:
276             if ( data_pointer.e_type == TYPE_NONE || !p_entry ) break;
277             if ( data_pointer.e_type == TYPE_STRING )
278                 *data_pointer.u_data.ppsz = strdup( p_node );
279             else
280             if ( data_pointer.e_type == TYPE_LONG )
281                 *data_pointer.u_data.pl = atol( p_node );
282             else
283             if ( data_pointer.e_type == TYPE_INTEGER )
284                 *data_pointer.u_data.pi = atoi( p_node );
285             break;
286
287         case XML_READER_ENDELEM:
288             if ( !p_entry ) break;
289             if ( ! strcmp( p_node, "addon" ) )
290             {
291                 /* then append entry */
292                 ARRAY_APPEND( p_finder->entries, p_entry );
293                 p_entry = NULL;
294                 i_num_entries_created++;
295             }
296
297             data_pointer.e_type = TYPE_NONE;
298             break;
299
300         default:
301             break;
302         }
303     }
304
305 end:
306    xml_ReaderDelete( p_xml_reader );
307    return i_num_entries_created;
308 }
309
310 static int Find( addons_finder_t *p_finder )
311 {
312     bool b_done = false;
313
314     while ( !b_done )
315     {
316         char *psz_uri = NULL;
317
318         if ( ! asprintf( &psz_uri, ADDONS_REPO_SCHEMEHOST"/xml" ) ) return VLC_ENOMEM;
319         b_done = true;
320
321         stream_t *p_stream = stream_UrlNew( p_finder, psz_uri );
322         free( psz_uri );
323         if ( !p_stream ) return VLC_EGENERIC;
324
325         if ( ! ParseCategoriesInfo( p_finder, p_stream ) )
326         {
327             /* no more entries have been read: was last page or error */
328             b_done = true;
329         }
330
331         stream_Delete( p_stream );
332     }
333
334     return VLC_SUCCESS;
335 }
336
337 static int Retrieve( addons_finder_t *p_finder, addon_entry_t *p_entry )
338 {
339     if ( !p_entry->psz_archive_uri )
340         return VLC_EGENERIC;
341
342     /* get archive and parse manifest */
343     stream_t *p_stream;
344
345     if ( p_entry->psz_archive_uri[0] == '/' )
346     {
347         /* Relative path */
348         char *psz_uri;
349         if ( ! asprintf( &psz_uri, ADDONS_REPO_SCHEMEHOST"%s", p_entry->psz_archive_uri ) )
350             return VLC_ENOMEM;
351         p_stream = stream_UrlNew( p_finder, psz_uri );
352         free( psz_uri );
353     }
354     else
355     {
356         p_stream = stream_UrlNew( p_finder, p_entry->psz_archive_uri );
357     }
358
359     msg_Dbg( p_finder, "downloading archive %s", p_entry->psz_archive_uri );
360     if ( !p_stream ) return VLC_EGENERIC;
361
362     /* In case of pf_ reuse */
363     if ( p_finder->p_sys->psz_tempfile )
364     {
365         vlc_unlink( p_finder->p_sys->psz_tempfile );
366         FREENULL( p_finder->p_sys->psz_tempfile );
367     }
368
369     p_finder->p_sys->psz_tempfile = tempnam( NULL, "vlp" );
370     if ( !p_finder->p_sys->psz_tempfile )
371     {
372         msg_Err( p_finder, "Can't create temp storage file" );
373         stream_Delete( p_stream );
374         return VLC_EGENERIC;
375     }
376
377     FILE *p_destfile = vlc_fopen( p_finder->p_sys->psz_tempfile, "w" );
378     if( !p_destfile )
379     {
380         msg_Err( p_finder, "Failed to open addon temp storage file" );
381         FREENULL(p_finder->p_sys->psz_tempfile);
382         stream_Delete( p_stream );
383         return VLC_EGENERIC;
384     }
385
386     char buffer[1<<10];
387     int i_read = 0;
388     while ( ( i_read = stream_Read( p_stream, &buffer, 1<<10 ) ) )
389     {
390         if ( fwrite( &buffer, i_read, 1, p_destfile ) < 1 )
391         {
392             msg_Err( p_finder, "Failed to write to Addon file" );
393             fclose( p_destfile );
394             stream_Delete( p_stream );
395             return VLC_EGENERIC;
396         }
397     }
398     fclose( p_destfile );
399     stream_Delete( p_stream );
400
401     msg_Dbg( p_finder, "Reading manifest from %s", p_finder->p_sys->psz_tempfile );
402
403     char *psz_manifest;
404     if ( asprintf( &psz_manifest, "unzip://%s!/manifest.xml",
405                    p_finder->p_sys->psz_tempfile ) < 1 )
406         return VLC_ENOMEM;
407
408     p_stream = stream_UrlNew( p_finder, psz_manifest );
409     free( psz_manifest );
410
411     int i_ret = ( ParseManifest( p_finder, p_entry,
412                                  p_finder->p_sys->psz_tempfile, p_stream ) > 0 )
413                     ? VLC_SUCCESS : VLC_EGENERIC;
414
415     stream_Delete( p_stream );
416
417     return i_ret;
418 }
419
420 static int FindDesignated( addons_finder_t *p_finder )
421 {
422     char *psz_manifest;
423     const char *psz_path = p_finder->psz_uri + 7; // remove scheme
424
425     if ( asprintf( &psz_manifest, "unzip://%s!/manifest.xml",
426                    psz_path ) < 1 )
427         return VLC_ENOMEM;
428
429     stream_t *p_stream = stream_UrlNew( p_finder, psz_manifest );
430     free( psz_manifest );
431     if ( !p_stream ) return VLC_EGENERIC;
432
433     if ( ParseCategoriesInfo( p_finder, p_stream ) )
434     {
435         /* Do archive uri fixup */
436         FOREACH_ARRAY( addon_entry_t *p_entry, p_finder->entries )
437         if ( likely( !p_entry->psz_archive_uri ) )
438                 p_entry->psz_archive_uri = strdup( p_finder->psz_uri );
439         FOREACH_END()
440     }
441     else
442     {
443         stream_Delete( p_stream );
444         return VLC_EGENERIC;
445     }
446
447     stream_Delete( p_stream );
448
449     return VLC_SUCCESS;
450 }
451
452 static int Open(vlc_object_t *p_this)
453 {
454     addons_finder_t *p_finder = (addons_finder_t*) p_this;
455
456     p_finder->p_sys = (addons_finder_sys_t*) malloc(sizeof(addons_finder_sys_t));
457     if ( !p_finder->p_sys )
458         return VLC_ENOMEM;
459     p_finder->p_sys->psz_tempfile = NULL;
460     /* We only support listing the whole repo */
461     if ( p_finder->psz_uri )
462         return VLC_EGENERIC;
463
464     p_finder->pf_find = Find;
465     p_finder->pf_retrieve = Retrieve;
466
467     return VLC_SUCCESS;
468 }
469
470 static void Close(vlc_object_t *p_this)
471 {
472     addons_finder_t *p_finder = (addons_finder_t*) p_this;
473     if ( p_finder->p_sys->psz_tempfile )
474     {
475         unlink( p_finder->p_sys->psz_tempfile );
476         free( p_finder->p_sys );
477     }
478 }
479
480 static int OpenDesignated(vlc_object_t *p_this)
481 {
482     addons_finder_t *p_finder = (addons_finder_t*) p_this;
483     if ( !p_finder->psz_uri
484          || strncmp( "file://", p_finder->psz_uri, 7 )
485          || strncmp( ".vlp", p_finder->psz_uri + strlen( p_finder->psz_uri ) - 4, 4 )
486        )
487         return VLC_EGENERIC;
488
489     p_finder->pf_find = FindDesignated;
490     p_finder->pf_retrieve = Retrieve;
491
492     return VLC_SUCCESS;
493 }