addons: fix deference before null check (cid #1231840)
[vlc.git] / src / misc / addons.c
1 /*****************************************************************************
2  * addons.c: VLC addons manager
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 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <vlc_common.h>
26 #include <vlc_atomic.h>
27 #include <vlc_modules.h>
28 #include <vlc_arrays.h>
29 #include <vlc_events.h>
30 #include "libvlc.h"
31
32 #include <vlc_addons.h>
33
34 /*****************************************************************************
35  * Structures/definitions
36  *****************************************************************************/
37
38 typedef struct addon_entry_owner
39 {
40     addon_entry_t entry;
41     atomic_uint refs;
42 } addon_entry_owner_t;
43
44 struct addons_manager_private_t
45 {
46     vlc_object_t *p_parent;
47
48     struct
49     {
50         vlc_thread_t thread;
51         vlc_cond_t waitcond;
52         bool b_live;
53         vlc_mutex_t lock;
54         DECL_ARRAY(char*) uris;
55         DECL_ARRAY(addon_entry_t*) entries;
56     } finder;
57
58     struct
59     {
60         vlc_thread_t thread;
61         vlc_cond_t waitcond;
62         bool b_live;
63         vlc_mutex_t lock;
64         DECL_ARRAY(addon_entry_t*) entries;
65     } installer;
66 };
67
68 static void *FinderThread( void * );
69 static void LoadLocalStorage( addons_manager_t *p_manager );
70
71 /*****************************************************************************
72  * Public functions
73  *****************************************************************************/
74
75 addon_entry_t * addon_entry_New()
76 {
77     addon_entry_owner_t *owner = calloc( 1, sizeof(addon_entry_owner_t) );
78     if( unlikely(owner == NULL) )
79         return NULL;
80
81     atomic_init( &owner->refs, 1 );
82
83     addon_entry_t *p_entry = &owner->entry;
84     vlc_mutex_init( &p_entry->lock );
85     ARRAY_INIT( p_entry->files );
86     return p_entry;
87 }
88
89 addon_entry_t * addon_entry_Hold( addon_entry_t * p_entry )
90 {
91     addon_entry_owner_t *owner = (addon_entry_owner_t *) p_entry;
92
93     atomic_fetch_add( &owner->refs, 1 );
94     return p_entry;
95 }
96
97 void addon_entry_Release( addon_entry_t * p_entry )
98 {
99     addon_entry_owner_t *owner = (addon_entry_owner_t *) p_entry;
100
101     if( atomic_fetch_sub(&owner->refs, 1) != 1 )
102         return;
103
104     free( p_entry->psz_name );
105     free( p_entry->psz_summary );
106     free( p_entry->psz_description );
107     free( p_entry->psz_archive_uri );
108     free( p_entry->psz_author );
109     free( p_entry->psz_source_uri );
110     free( p_entry->psz_image_uri );
111     free( p_entry->psz_image_data );
112     free( p_entry->psz_source_module );
113     free( p_entry->psz_version );
114     free( p_entry->p_custom );
115
116     addon_file_t *p_file;
117     FOREACH_ARRAY( p_file, p_entry->files )
118     free( p_file->psz_filename );
119     free( p_file->psz_download_uri );
120     FOREACH_END()
121     ARRAY_RESET( p_entry->files );
122
123     vlc_mutex_destroy( &p_entry->lock );
124     free( owner );
125 }
126
127 addons_manager_t *addons_manager_New( vlc_object_t *p_this )
128 {
129     addons_manager_t *p_manager = malloc( sizeof(addons_manager_t) );
130     if ( !p_manager ) return NULL;
131
132     p_manager->p_priv = malloc( sizeof(addons_manager_private_t) );
133     if ( !p_manager->p_priv )
134     {
135         free( p_manager );
136         return NULL;
137     }
138
139     p_manager->p_event_manager = malloc( sizeof(vlc_event_manager_t) );
140     if ( !p_manager->p_event_manager )
141     {
142         free( p_manager->p_priv );
143         free( p_manager );
144         return NULL;
145     }
146
147     p_manager->p_priv->p_parent = p_this;
148
149 #define INIT_QUEUE( name ) \
150     p_manager->p_priv->name.b_live = false;\
151     vlc_mutex_init( &p_manager->p_priv->name.lock );\
152     vlc_cond_init( &p_manager->p_priv->name.waitcond );\
153     ARRAY_INIT( p_manager->p_priv->name.entries );
154
155     INIT_QUEUE( finder )
156     INIT_QUEUE( installer )
157     ARRAY_INIT( p_manager->p_priv->finder.uris );
158
159     vlc_event_manager_t *em = p_manager->p_event_manager;
160     vlc_event_manager_init( em, p_manager );
161     vlc_event_manager_register_event_type(em, vlc_AddonFound);
162     vlc_event_manager_register_event_type(em, vlc_AddonsDiscoveryEnded);
163     vlc_event_manager_register_event_type(em, vlc_AddonChanged);
164
165     return p_manager;
166 }
167
168 void addons_manager_Delete( addons_manager_t *p_manager )
169 {
170     bool b_live;
171
172     vlc_mutex_lock( &p_manager->p_priv->finder.lock );
173     b_live = p_manager->p_priv->finder.b_live;
174     vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
175     if ( b_live )
176     {
177         vlc_cancel( p_manager->p_priv->finder.thread );
178         vlc_join( p_manager->p_priv->finder.thread, NULL );
179     }
180
181     vlc_mutex_lock( &p_manager->p_priv->installer.lock );
182     b_live = p_manager->p_priv->installer.b_live;
183     vlc_mutex_unlock( &p_manager->p_priv->installer.lock );
184     if ( b_live )
185     {
186         vlc_cancel( p_manager->p_priv->installer.thread );
187         vlc_join( p_manager->p_priv->installer.thread, NULL );
188     }
189
190     vlc_event_manager_fini( p_manager->p_event_manager );
191
192 #define FREE_QUEUE( name ) \
193     vlc_mutex_lock( &p_manager->p_priv->name.lock );\
194     FOREACH_ARRAY( addon_entry_t *p_entry, p_manager->p_priv->name.entries )\
195         addon_entry_Release( p_entry );\
196     FOREACH_END();\
197     ARRAY_RESET( p_manager->p_priv->name.entries );\
198     vlc_mutex_unlock( &p_manager->p_priv->name.lock );\
199     vlc_mutex_destroy( &p_manager->p_priv->name.lock );\
200     vlc_cond_destroy( &p_manager->p_priv->name.waitcond );
201
202     FREE_QUEUE( finder )
203     FREE_QUEUE( installer )
204     FOREACH_ARRAY( char *psz_uri, p_manager->p_priv->finder.uris )
205        free( psz_uri );
206     FOREACH_END();
207     ARRAY_RESET( p_manager->p_priv->finder.uris );
208
209     free( p_manager->p_priv );
210     free( p_manager->p_event_manager );
211     free( p_manager );
212 }
213
214 void addons_manager_Gather( addons_manager_t *p_manager, const char *psz_uri )
215 {
216     if ( !psz_uri )
217         return;
218
219     vlc_mutex_lock( &p_manager->p_priv->finder.lock );
220
221     ARRAY_APPEND( p_manager->p_priv->finder.uris, strdup( psz_uri ) );
222
223     if( !p_manager->p_priv->finder.b_live )
224     {
225         if( vlc_clone( &p_manager->p_priv->finder.thread, FinderThread, p_manager,
226                        VLC_THREAD_PRIORITY_LOW ) )
227         {
228             msg_Err( p_manager->p_priv->p_parent,
229                      "cannot spawn entries provider thread" );
230             vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
231             return;
232         }
233         p_manager->p_priv->finder.b_live = true;
234     }
235
236     vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
237     vlc_cond_signal( &p_manager->p_priv->finder.waitcond );
238 }
239
240 /*****************************************************************************
241  * Private functions
242  *****************************************************************************/
243
244 static addon_entry_t * getHeldEntryByUUID( addons_manager_t *p_manager,
245                                            const addon_uuid_t uuid )
246 {
247     addon_entry_t *p_return = NULL;
248     vlc_mutex_lock( &p_manager->p_priv->finder.lock );
249     FOREACH_ARRAY( addon_entry_t *p_entry, p_manager->p_priv->finder.entries )
250     if ( !memcmp( p_entry->uuid, uuid, sizeof( addon_uuid_t ) ) )
251     {
252         p_return = p_entry;
253         addon_entry_Hold( p_return );
254         break;
255     }
256     FOREACH_END()
257     vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
258     return p_return;
259 }
260
261 static void MergeSources( addons_manager_t *p_manager,
262                           addon_entry_t **pp_addons, int i_count )
263 {
264     addon_entry_t *p_entry, *p_manager_entry;
265     addon_uuid_t zerouuid;
266     memset( zerouuid, 0, sizeof( addon_uuid_t ) );
267     for ( int i=0; i < i_count; i++ )
268     {
269         p_entry = pp_addons[i];
270         vlc_mutex_lock( &p_entry->lock );
271         if ( memcmp( p_entry->uuid, zerouuid, sizeof( addon_uuid_t ) ) )
272             p_manager_entry = getHeldEntryByUUID( p_manager, p_entry->uuid );
273         else
274             p_manager_entry = NULL;
275         if ( !p_manager_entry )
276         {
277             ARRAY_APPEND( p_manager->p_priv->finder.entries, p_entry );
278             vlc_event_t event;
279             event.type = vlc_AddonFound;
280             event.u.addon_generic_event.p_entry = p_entry;
281             vlc_event_send( p_manager->p_event_manager, &event );
282         }
283         else
284         {
285             vlc_mutex_lock( &p_manager_entry->lock );
286             if ( ( p_manager_entry->psz_version && p_entry->psz_version )
287                  && /* FIXME: better version comparison */
288                  strcmp( p_manager_entry->psz_version, p_entry->psz_version )
289                  )
290             {
291                 p_manager_entry->e_flags |= ADDON_UPDATABLE;
292             }
293             vlc_mutex_unlock( &p_manager_entry->lock );
294             addon_entry_Release( p_manager_entry );
295         }
296         vlc_mutex_unlock( &p_entry->lock );
297     }
298 }
299
300 static void LoadLocalStorage( addons_manager_t *p_manager )
301 {
302     addons_finder_t *p_finder =
303         vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_finder ), "entries finder" );
304     p_finder->i_flags |= OBJECT_FLAGS_NOINTERACT;
305
306     module_t *p_module = module_need( p_finder, "addons finder",
307                                       "addons.store.list", true );
308     if( p_module )
309     {
310         ARRAY_INIT( p_finder->entries );
311         p_finder->psz_uri = NULL;
312         p_finder->pf_find( p_finder );
313         module_unneed( p_finder, p_module );
314
315         MergeSources( p_manager, p_finder->entries.p_elems, p_finder->entries.i_size );
316
317         ARRAY_RESET( p_finder->entries );
318     }
319     vlc_object_release( p_finder );
320 }
321
322 static void *FinderThread( void *p_data )
323 {
324     addons_manager_t *p_manager = p_data;
325     int i_cancel;
326     char *psz_uri;
327
328     for( ;; )
329     {
330         vlc_mutex_lock( &p_manager->p_priv->finder.lock );
331         mutex_cleanup_push( &p_manager->p_priv->finder.lock );
332         while( p_manager->p_priv->finder.uris.i_size == 0 )
333         {
334             vlc_cond_wait( &p_manager->p_priv->finder.waitcond,
335                            &p_manager->p_priv->finder.lock );
336         }
337         psz_uri = p_manager->p_priv->finder.uris.p_elems[0];
338         ARRAY_REMOVE( p_manager->p_priv->finder.uris, 0 );
339         vlc_cleanup_run();
340
341         addons_finder_t *p_finder =
342                 vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_finder ), "entries finder" );
343
344         i_cancel = vlc_savecancel();
345         if( p_finder != NULL )
346         {
347             p_finder->i_flags |= OBJECT_FLAGS_NOINTERACT;
348             module_t *p_module;
349             ARRAY_INIT( p_finder->entries );
350             vlc_mutex_lock( &p_manager->p_priv->finder.lock );
351             p_finder->psz_uri = psz_uri;
352             vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
353
354             p_module = module_need( p_finder, "addons finder", NULL, false );
355             if( p_module )
356             {
357                 p_finder->pf_find( p_finder );
358                 module_unneed( p_finder, p_module );
359                 MergeSources( p_manager, p_finder->entries.p_elems, p_finder->entries.i_size );
360             }
361             ARRAY_RESET( p_finder->entries );
362             free( psz_uri );
363             vlc_object_release( p_finder );
364         }
365
366         vlc_event_t event;
367         event.type = vlc_AddonsDiscoveryEnded;
368         event.u.addon_generic_event.p_entry = NULL;
369         vlc_event_send( p_manager->p_event_manager, &event );
370
371         vlc_restorecancel( i_cancel );
372         vlc_testcancel();
373     }
374
375     return NULL;
376 }
377
378 static int addons_manager_WriteCatalog( addons_manager_t *p_manager )
379 {
380     int i_return = VLC_EGENERIC;
381
382     addons_storage_t *p_storage =
383         vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_storage ), "entries storage" );
384     p_storage->i_flags |= OBJECT_FLAGS_NOINTERACT;
385
386     module_t *p_module = module_need( p_storage, "addons storage",
387                                       "addons.store.install", true );
388     if( p_module )
389     {
390         vlc_mutex_lock( &p_manager->p_priv->finder.lock );
391         i_return = p_storage->pf_catalog( p_storage, p_manager->p_priv->finder.entries.p_elems,
392                                           p_manager->p_priv->finder.entries.i_size );
393         vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
394         module_unneed( p_storage, p_module );
395     }
396     vlc_object_release( p_storage );
397
398     return i_return;
399 }
400
401 int addons_manager_LoadCatalog( addons_manager_t *p_manager )
402 {
403     LoadLocalStorage( p_manager );
404     return VLC_SUCCESS;
405 }
406
407 static int installOrRemoveAddon( addons_manager_t *p_manager, addon_entry_t *p_entry, bool b_install )
408 {
409     int i_return = VLC_EGENERIC;
410
411     addons_storage_t *p_storage =
412         vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_storage ), "entries storage" );
413     p_storage->i_flags |= OBJECT_FLAGS_NOINTERACT;
414
415     module_t *p_module = module_need( p_storage, "addons storage",
416                                       "addons.store.install", true );
417     if( p_module )
418     {
419         if ( b_install )
420             i_return = p_storage->pf_install( p_storage, p_entry );
421         else
422             i_return = p_storage->pf_remove( p_storage, p_entry );
423         module_unneed( p_storage, p_module );
424         msg_Dbg( p_manager->p_priv->p_parent, "InstallAddon returns %d", i_return );
425         if ( i_return == VLC_SUCCESS )
426         {
427             /* Reset flags */
428             vlc_mutex_lock( &p_entry->lock );
429             p_entry->e_flags = ADDON_MANAGEABLE;
430             vlc_mutex_unlock( &p_entry->lock );
431         }
432     }
433     vlc_object_release( p_storage );
434
435     return i_return;
436 }
437
438 static void *InstallerThread( void *p_data )
439 {
440     addons_manager_t *p_manager = p_data;
441     int i_ret, i_cancel;
442     vlc_event_t event;
443     event.type = vlc_AddonChanged;
444
445     for( ;; )
446     {
447         vlc_mutex_lock( &p_manager->p_priv->installer.lock );
448         mutex_cleanup_push( &p_manager->p_priv->installer.lock );
449         while ( !p_manager->p_priv->installer.entries.i_size )
450         {
451             /* No queued addons */
452             vlc_cond_wait( &p_manager->p_priv->installer.waitcond,
453                            &p_manager->p_priv->installer.lock );
454         }
455         vlc_cleanup_pop();
456
457         addon_entry_t *p_entry = p_manager->p_priv->installer.entries.p_elems[0];
458         ARRAY_REMOVE( p_manager->p_priv->installer.entries, 0 );
459         addon_entry_Hold( p_entry );
460         vlc_mutex_unlock( &p_manager->p_priv->installer.lock );
461
462         vlc_mutex_lock( &p_entry->lock );
463         /* DO WORK */
464         if ( p_entry->e_state == ADDON_INSTALLED )
465         {
466             p_entry->e_state = ADDON_UNINSTALLING;
467             vlc_mutex_unlock( &p_entry->lock );
468
469             /* notify */
470             i_cancel = vlc_savecancel();
471             event.u.addon_generic_event.p_entry = p_entry;
472             vlc_event_send( p_manager->p_event_manager, &event );
473
474             i_ret = installOrRemoveAddon( p_manager, p_entry, false );
475             vlc_restorecancel( i_cancel );
476
477             vlc_mutex_lock( &p_entry->lock );
478             p_entry->e_state = ( i_ret == VLC_SUCCESS ) ? ADDON_NOTINSTALLED
479                                                         : ADDON_INSTALLED;
480             vlc_mutex_unlock( &p_entry->lock );
481         }
482         else if ( p_entry->e_state == ADDON_NOTINSTALLED )
483         {
484             p_entry->e_state = ADDON_INSTALLING;
485             vlc_mutex_unlock( &p_entry->lock );
486
487             /* notify */
488             i_cancel = vlc_savecancel();
489             event.u.addon_generic_event.p_entry = p_entry;
490             vlc_event_send( p_manager->p_event_manager, &event );
491
492             i_ret = installOrRemoveAddon( p_manager, p_entry, true );
493             vlc_restorecancel( i_cancel );
494
495             vlc_mutex_lock( &p_entry->lock );
496             p_entry->e_state = ( i_ret == VLC_SUCCESS ) ? ADDON_INSTALLED
497                                                         : ADDON_NOTINSTALLED;
498             vlc_mutex_unlock( &p_entry->lock );
499         }
500         else
501             vlc_mutex_unlock( &p_entry->lock );
502         /* !DO WORK */
503
504         i_cancel = vlc_savecancel();
505         event.u.addon_generic_event.p_entry = p_entry;
506         vlc_event_send( p_manager->p_event_manager, &event );
507         vlc_restorecancel( i_cancel );
508
509         addon_entry_Release( p_entry );
510
511         i_cancel = vlc_savecancel();
512         addons_manager_WriteCatalog( p_manager );
513         vlc_restorecancel( i_cancel );
514     }
515
516     return NULL;
517 }
518
519 static int InstallEntry( addons_manager_t *p_manager, addon_entry_t *p_entry )
520 {
521     if ( p_entry->e_type == ADDON_UNKNOWN ||
522          p_entry->e_type == ADDON_PLUGIN ||
523          p_entry->e_type == ADDON_OTHER )
524         return VLC_EBADVAR;
525
526     vlc_mutex_lock( &p_manager->p_priv->installer.lock );
527     ARRAY_APPEND( p_manager->p_priv->installer.entries, p_entry );
528     if( !p_manager->p_priv->installer.b_live )
529     {
530         if( vlc_clone( &p_manager->p_priv->installer.thread, InstallerThread, p_manager,
531                        VLC_THREAD_PRIORITY_LOW ) )
532         {
533             msg_Err( p_manager->p_priv->p_parent,
534                      "cannot spawn addons installer thread" );
535             vlc_mutex_unlock( &p_manager->p_priv->installer.lock );
536             return VLC_EGENERIC;
537         }
538         else
539             p_manager->p_priv->installer.b_live = true;
540     }
541     vlc_mutex_unlock( &p_manager->p_priv->installer.lock );
542     vlc_cond_signal( &p_manager->p_priv->installer.waitcond );
543     return VLC_SUCCESS;
544 }
545
546 int addons_manager_Install( addons_manager_t *p_manager, const addon_uuid_t uuid )
547 {
548     addon_entry_t *p_install_entry = getHeldEntryByUUID( p_manager, uuid );
549     if ( ! p_install_entry ) return VLC_EGENERIC;
550     int i_ret = InstallEntry( p_manager, p_install_entry );
551     addon_entry_Release( p_install_entry );
552     return i_ret;
553 }
554
555 int addons_manager_Remove( addons_manager_t *p_manager, const addon_uuid_t uuid )
556 {
557     return addons_manager_Install( p_manager, uuid );
558 }