]> git.sesse.net Git - vlc/blob - modules/media_library/ml_watch.c
ML: Avoid deadlocks
[vlc] / modules / media_library / ml_watch.c
1 /*****************************************************************************
2  * ml_watch.c: SQL-based media library: Medias watching system
3  *****************************************************************************
4  * Copyright (C) 2008-2010 the VideoLAN team and AUTHORS
5  * $Id$
6  *
7  * Authors: Antoine Lejeune <phytos@videolan.org>
8  *          Jean-Philippe André <jpeg@videolan.org>
9  *          Rémi Duraffort <ivoire@videolan.org>
10  *          Adrien Maglo <magsoft@videolan.org>
11  *          Srikanth Raju <srikiraju at gmail dot com>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #include "sql_media_library.h"
29 #include "item_list.h"
30 #include <vlc_events.h>
31
32 static void* watch_Thread( vlc_object_t *p_this );
33 static void watch_ItemChange( const vlc_event_t *, void * );
34 static int watch_PlaylistItemCurrent( vlc_object_t *p_this, char const *psz_var,
35                                   vlc_value_t oldval, vlc_value_t newval,
36                                   void *data );
37 static int watch_PlaylistItemAppend( vlc_object_t *p_this, char const *psz_var,
38                                   vlc_value_t oldval, vlc_value_t newval,
39                                   void *data );
40 static int watch_PlaylistItemDeleted( vlc_object_t *p_this, char const *psz_var,
41                                   vlc_value_t oldval, vlc_value_t newval,
42                                   void *data );
43 static void watch_loop( media_library_t *p_ml, bool b_force );
44 static void watch_Thread_Cleanup( void* p_object );
45 static int watch_update_Item( media_library_t *p_ml, int i_media_id,
46                        input_item_t *p_item, bool b_raise_count, bool locked );
47 static void watch_ProcessAppendQueue( media_library_t* p_ml );
48
49 /**
50  * @brief Watching thread
51  */
52 static void* watch_Thread( vlc_object_t *p_this )
53 {
54     watch_thread_t *p_watch = ( watch_thread_t* ) p_this;
55     media_library_t *p_ml = p_watch->p_ml;
56     int i_ret = 0;
57
58     vlc_mutex_lock( &p_watch->lock );
59     vlc_cleanup_push( watch_Thread_Cleanup, p_ml );
60     while( vlc_object_alive( p_watch ) )
61     {
62         watch_loop( p_ml, !i_ret );
63         if( !vlc_object_alive( p_watch ) )
64             break;
65         i_ret = vlc_cond_timedwait( &p_watch->cond, &p_watch->lock,
66                                 mdate() + 1000000 * THREAD_SLEEP_DELAY );
67     }
68     vlc_cleanup_run();
69     return NULL;
70 }
71
72 /**
73  * @brief Callback for thread exit
74  */
75 static void watch_Thread_Cleanup( void* p_object )
76 {
77     media_library_t* p_ml = ( media_library_t* )p_object;
78     watch_loop( p_ml, true );
79     vlc_mutex_unlock( &p_ml->p_sys->p_watch->lock );
80 }
81 /**
82  * @brief Init watching system
83  * @return Error if the object or the thread could not be created
84  */
85 int watch_Init( media_library_t *p_ml )
86 {
87     /* init and launch watching thread */
88     p_ml->p_sys->p_watch = vlc_object_create( p_ml, sizeof(watch_thread_t) );
89     if( !p_ml->p_sys->p_watch )
90         return VLC_ENOMEM;
91
92     watch_thread_t* p_wt = p_ml->p_sys->p_watch;
93     vlc_mutex_init( &p_wt->list_mutex );
94     p_wt->p_ml = p_ml;
95
96     vlc_object_attach( p_wt, p_ml );
97
98     vlc_cond_init( &p_wt->cond );
99     vlc_mutex_init( &p_wt->lock );
100
101     if( vlc_thread_create( p_wt, "Media Library Auto-Update",
102                             watch_Thread, VLC_THREAD_PRIORITY_LOW ) )
103     {
104         msg_Dbg( p_ml, "unable to launch the auto-updating thread" );
105         vlc_object_release( p_wt );
106         return VLC_EGENERIC;
107     }
108
109     /* Wait on playlist events
110      * playlist-item-append -> entry to playlist
111      * item-current -> to ensure that we catch played item only!
112      * playlist-item-deleted -> exit from playlist
113      * item-change -> Currently not required, as we monitor input_item events
114      */
115     playlist_t *p_pl = pl_Get( p_ml );
116     var_AddCallback( p_pl, "item-current", watch_PlaylistItemCurrent, p_ml );
117     var_AddCallback( p_pl, "playlist-item-append", watch_PlaylistItemAppend, p_ml );
118     var_AddCallback( p_pl, "playlist-item-deleted", watch_PlaylistItemDeleted, p_ml );
119
120     /* Initialise item append queue */
121     vlc_mutex_init( &p_wt->item_append_queue_lock );
122     p_wt->item_append_queue = NULL;
123     p_wt->item_append_queue_count = 0;
124
125     return VLC_SUCCESS;
126 }
127
128 /**
129  * @brief Add the input to the watch system
130  * @param p_ml The Media Library Object
131  * @param p_item Item to be watched
132  * @param p_media Corresponding media item to sync with
133  * @param locked Status of item list lock
134  * @return VLC_SUCCESS or error code
135  */
136 int __watch_add_Item( media_library_t *p_ml, input_item_t *p_item,
137                         ml_media_t* p_media, bool locked )
138 {
139     vlc_gc_incref( p_item );
140     ml_gc_incref( p_media );
141     int i_ret = __item_list_add( p_ml->p_sys->p_watch, p_media, p_item, locked );
142     if( i_ret != VLC_SUCCESS )
143         return i_ret;
144     vlc_event_manager_t *p_em = &p_item->event_manager;
145     vlc_event_attach( p_em, vlc_InputItemMetaChanged, watch_ItemChange, p_ml );
146     vlc_event_attach( p_em, vlc_InputItemNameChanged, watch_ItemChange, p_ml );
147     vlc_event_attach( p_em, vlc_InputItemInfoChanged, watch_ItemChange, p_ml );
148     /*
149     Note: vlc_InputItemDurationChanged is disabled because
150           it is triggered too often, even without consequent changes
151     */
152     return VLC_SUCCESS;
153 }
154
155
156 /**
157  * @brief Detach event manager
158  * @param p_ml The Media Library Object
159  */
160 static void detachItemEvents( media_library_t *p_ml, input_item_t *p_item )
161 {
162     vlc_event_manager_t *p_em = &p_item->event_manager;
163     vlc_event_detach( p_em, vlc_InputItemMetaChanged, watch_ItemChange, p_ml );
164     vlc_event_detach( p_em, vlc_InputItemNameChanged, watch_ItemChange, p_ml );
165     vlc_event_detach( p_em, vlc_InputItemInfoChanged, watch_ItemChange, p_ml );
166 }
167
168
169 /**
170  * @brief Close the watching system
171  * @param p_ml The Media Library Object
172  */
173 void watch_Close( media_library_t *p_ml )
174 {
175     playlist_t *p_pl = pl_Get( p_ml );
176     var_DelCallback( p_pl, "playlist-item-deleted", watch_PlaylistItemDeleted, p_ml );
177     var_DelCallback( p_pl, "playlist-item-append", watch_PlaylistItemAppend, p_ml );
178     var_DelCallback( p_pl, "item-current", watch_PlaylistItemCurrent, p_ml );
179
180     /* Flush item list */
181     il_foreachhashlist( p_ml->p_sys->p_watch->p_hlist, p_elt, ixx )
182     {
183         detachItemEvents( p_ml, p_elt->p_item );
184         ml_gc_decref( p_elt->p_media );
185         vlc_gc_decref( p_elt->p_item );
186     }
187     item_list_destroy( p_ml->p_sys->p_watch );
188
189     /* Stop the watch thread and join in */
190     vlc_object_kill( p_ml->p_sys->p_watch );
191     vlc_thread_join( p_ml->p_sys->p_watch );
192
193     /* Clear up other stuff */
194     vlc_mutex_destroy( &p_ml->p_sys->p_watch->lock );
195     vlc_cond_destroy( &p_ml->p_sys->p_watch->cond );
196     vlc_mutex_destroy( &p_ml->p_sys->p_watch->list_mutex );
197     vlc_object_release( p_ml->p_sys->p_watch );
198
199     free( p_ml->p_sys->p_watch->item_append_queue );
200     vlc_mutex_destroy( &p_ml->p_sys->p_watch->item_append_queue_lock );
201     p_ml->p_sys->p_watch = NULL;
202 }
203
204 /**
205  * @brief Del item that is currently being watched
206  * @param p_ml The Media Library Object
207  * @param p_item Item to stop watching
208  * @param locked Lock state of item list
209  */
210 int __watch_del_Item( media_library_t* p_ml, input_item_t* p_item, bool locked )
211 {
212     assert( p_item );
213     item_list_t* p_tmp = item_list_delItem( p_ml->p_sys->p_watch, p_item, locked );
214     if( p_tmp == NULL )
215         return VLC_EGENERIC;
216     detachItemEvents( p_ml, p_tmp->p_item );
217     vlc_gc_decref( p_tmp->p_item );
218     ml_gc_decref( p_tmp->p_media );
219     free( p_tmp );
220     return VLC_SUCCESS;
221 }
222
223 /**
224  * @brief Del media from watching by ID
225  * @param p_ml The Media Library Object
226  * @param i_media_id Media ID
227  */
228 int watch_del_MediaById( media_library_t* p_ml, int i_media_id )
229 {
230     assert( i_media_id > 0 );
231     item_list_t* p_elt = item_list_delMedia( p_ml->p_sys->p_watch, i_media_id );
232     if( p_elt == NULL )
233         return VLC_EGENERIC;
234     detachItemEvents( p_ml, p_elt->p_item );
235     vlc_gc_decref( p_elt->p_item );
236     ml_gc_decref( p_elt->p_media );
237     free( p_elt );
238     return VLC_SUCCESS;
239 }
240
241 /**
242  * @brief Get item using media id, if exists in item list
243  * @param p_ml The Media Library Object
244  * @param i_media_id Media ID
245  */
246 input_item_t* watch_get_itemOfMediaId( media_library_t *p_ml, int i_media_id )
247 {
248     input_item_t* p_tmp = item_list_itemOfMediaId( p_ml->p_sys->p_watch, i_media_id );
249     if( p_tmp == NULL )
250         return NULL;
251     vlc_gc_incref( p_tmp );
252     return p_tmp;
253 }
254
255 /**
256  * @brief Get media using media id, if exists in item list
257  * @param p_ml The Media Library Object
258  * @param i_media_id Media ID
259  */
260 ml_media_t* watch_get_mediaOfMediaId( media_library_t* p_ml, int i_media_id )
261 {
262     ml_media_t* p_tmp = item_list_mediaOfMediaId( p_ml->p_sys->p_watch, i_media_id );
263     if( p_tmp == NULL )
264         return NULL;
265     ml_gc_incref( p_tmp );
266     return p_tmp;
267 }
268
269 /**
270  * @brief Get mediaid of existing item
271  * @param p_ml The Media Library Object
272  * @param p_item Pointer to input item
273  */
274 int watch_get_mediaIdOfItem( media_library_t *p_ml, input_item_t *p_item )
275 {
276     return item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
277 }
278
279 /**
280  * @brief Updates a media each time it is changed (name, info or meta)
281  */
282 static void watch_ItemChange( const vlc_event_t *event, void *data )
283 {
284     input_item_t *p_item = ( input_item_t* ) event->p_obj;
285     media_library_t *p_ml = ( media_library_t* ) data;
286     /* Note: we don't add items to the item_list, but normally there should
287        not be any item at this point that is not in the list. */
288     if( item_list_updateInput( p_ml->p_sys->p_watch, p_item, false ) <= 0 )
289     {
290 #ifndef NDEBUG
291         msg_Dbg( p_ml, "Couldn't update in watch_ItemChange(): (%s:%d)",
292                  __FILE__, __LINE__ );
293 #endif
294     }
295
296     /*
297     if( event->type == vlc_InputItemMetaChanged )
298     {
299         int id = item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
300         if( !id ) return;
301
302         * Tell the world what happened *
303         var_SetInteger( p_ml, "media-meta-change", id );
304     }
305     */
306 }
307
308 /**
309  * @brief Callback when item is added to playlist
310  */
311 static int watch_PlaylistItemAppend( vlc_object_t *p_this, char const *psz_var,
312                                   vlc_value_t oldval, vlc_value_t newval,
313                                   void *data )
314 {
315     VLC_UNUSED( oldval );
316     VLC_UNUSED( p_this );
317     VLC_UNUSED( psz_var );
318     media_library_t* p_ml = ( media_library_t* ) data;
319     playlist_t* p_playlist = pl_Get( p_ml );
320     playlist_add_t* p_add;
321     p_add = ( playlist_add_t* ) newval.p_address;
322     playlist_item_t* p_pitem = playlist_ItemGetById( p_playlist, p_add->i_item );
323     input_item_t* p_item = p_pitem->p_input;
324     watch_thread_t* p_wt = p_ml->p_sys->p_watch;
325
326     vlc_mutex_lock( &p_wt->list_mutex );
327     /* Check if we are already watching this item */
328     il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
329     {
330         if( p_elt->p_item->i_id == p_item->i_id )
331         {
332             p_elt->i_refs++;
333             vlc_mutex_unlock( &p_wt->list_mutex );
334             goto quit_playlistitemappend;
335         }
336     }
337     vlc_mutex_unlock( &p_wt->list_mutex );
338
339     /* Add the the append queue */
340     vlc_mutex_lock( &p_wt->item_append_queue_lock );
341     p_wt->item_append_queue_count++;
342     p_wt->item_append_queue = realloc( p_wt->item_append_queue,
343             sizeof( input_item_t* ) * p_wt->item_append_queue_count );
344     vlc_gc_incref( p_item );
345     p_wt->item_append_queue[ p_wt->item_append_queue_count - 1 ] = p_item;
346     vlc_mutex_unlock( &p_wt->item_append_queue_lock );
347 quit_playlistitemappend:
348     return VLC_SUCCESS;
349 }
350
351 /**
352  * @brief Callback when item is deleted from playlist
353  */
354 static int watch_PlaylistItemDeleted( vlc_object_t *p_this, char const *psz_var,
355                                   vlc_value_t oldval, vlc_value_t newval,
356                                   void *data )
357 {
358     VLC_UNUSED( oldval );
359     VLC_UNUSED( p_this );
360     VLC_UNUSED( psz_var );
361     media_library_t* p_ml = ( media_library_t* ) data;
362     playlist_t* p_playlist = pl_Get( p_ml );
363
364     /* Luckily this works, because the item isn't deleted from PL, yet */
365     playlist_item_t* p_pitem = playlist_ItemGetById( p_playlist, newval.i_int );
366     input_item_t* p_item = p_pitem->p_input;
367
368     /* Find the new item and decrement its ref */
369     il_foreachlist( p_ml->p_sys->p_watch->p_hlist[ item_hash( p_item ) ], p_elt )
370     {
371         if( p_elt->p_item->i_id == p_item->i_id )
372         {
373             p_elt->i_refs--;
374             break;
375         }
376     }
377
378     return VLC_SUCCESS;
379 }
380 /**
381  * @brief Callback when watched input item starts playing
382  * @note This will update playcount mainly
383  * TODO: Increment playcount on playing 50%(configurable)
384  */
385 static int watch_PlaylistItemCurrent( vlc_object_t *p_this, char const *psz_var,
386                                   vlc_value_t oldval, vlc_value_t newval,
387                                   void *data )
388 {
389     (void)p_this;
390     (void)oldval;
391     (void)newval;
392     media_library_t *p_ml = ( media_library_t* ) data;
393     input_item_t *p_item = NULL;
394
395     if( strcmp( psz_var, "item-current" ) != 0 )
396         /* This case should not happen... */
397         return VLC_EGENERIC;
398
399     /* Get current input */
400     input_thread_t *p_input = pl_CurrentInput( p_ml );
401     p_item = p_input ? input_GetItem( p_input ) : NULL;
402
403     if( p_input )
404         vlc_object_release( p_input );
405
406     if( !p_item )
407         return VLC_EGENERIC;
408
409     if( item_list_updateInput( p_ml->p_sys->p_watch, p_item, true ) == 0 )
410     {
411 #ifndef NDEBUG
412         msg_Dbg( p_ml, "couldn't in watch_PlaylistItemCurrent(): (%s:%d)",
413                  __FILE__, __LINE__ );
414 #endif
415     }
416
417     return VLC_SUCCESS;
418 }
419
420 /**
421  * @brief Update informations in the DB for an input item
422  *
423  * @param p_ml this media library instance
424  * @param i_media_id may be 0 (but not recommended)
425  * @param p_item input item that was updated
426  * @param b_raise_count increment the played count
427  * @return result of UpdateMedia()
428  */
429 static int watch_update_Item( media_library_t *p_ml,
430                        int i_media_id, input_item_t *p_item,
431                        bool b_raise_count, bool locked )
432 {
433 #ifndef NDEBUG
434     msg_Dbg( p_ml, "automatically updating media %d", i_media_id );
435 #endif
436     ml_media_t* p_media = item_list_mediaOfItem( p_ml->p_sys->p_watch, p_item, locked );
437     CopyInputItemToMedia( p_media, p_item );
438     ml_LockMedia( p_media );
439     p_media->i_played_count += b_raise_count ? 1 : 0;
440     ml_UnlockMedia( p_media );
441     int i_ret = UpdateMedia( p_ml, p_media );
442
443     /* Add the poster to the album */
444     ml_LockMedia( p_media );
445     if( p_media->i_album_id && p_media->psz_cover )
446     {
447         SetArtCover( p_ml, p_media->i_album_id, p_media->psz_cover );
448     }
449     ml_UnlockMedia( p_media );
450
451     return i_ret;
452 }
453
454 /**
455  * @brief Signals the watch system to update all medias
456  */
457 void watch_Force_Update( media_library_t* p_ml )
458 {
459     vlc_mutex_lock( &p_ml->p_sys->p_watch->lock );
460     vlc_cond_signal( &p_ml->p_sys->p_watch->cond );
461     vlc_mutex_unlock( &p_ml->p_sys->p_watch->lock );
462 }
463
464 /**
465  * @brief Loop on the item_list: old objects collector and automatic updater
466  *
467  * This function is *not* a garbage collector. It actually decrefs items
468  * when they are too old. ITEM_GC_MAX_AGE is the maximum 'time' an item
469  * can stay in the list. After that, it is gc_decref'ed but not removed
470  * from this list. If you try to get it after that, either the input item
471  * is still alive, then you get it, or you'll have
472  *
473  * The update of an item is done when its age is >= ITEM_LOOP_UPDATE
474  * (0 could lead to a too early update)
475  *
476  * A thread should call this function every N seconds
477  *
478  * @param p_ml the media library instance
479  */
480 static void watch_loop( media_library_t *p_ml, bool b_force )
481 {
482     /* Do the garbage collection */
483     pool_GC( p_ml );
484
485     /* Process the append queue */
486     watch_ProcessAppendQueue( p_ml );
487
488     /* Do the item update if necessary */
489     vlc_mutex_lock( &p_ml->p_sys->p_watch->list_mutex );
490     item_list_t *p_prev = NULL;
491     il_foreachhashlist( p_ml->p_sys->p_watch->p_hlist, p_elt, ixx )
492     {
493         if( ( p_elt->i_update && p_elt->i_age >= ITEM_LOOP_UPDATE )
494                 || b_force )
495         {
496             /* This is the automatic delayed update */
497             watch_update_Item( p_ml, p_elt->i_media_id, p_elt->p_item,
498                                ( p_elt->i_update & 2 ) ? true : false, true );
499             /* The item gets older */
500             p_prev = p_elt;
501             p_elt->i_age++;
502             p_elt->i_update = false;
503         }
504         else if( p_elt->i_refs == 0 )
505         {
506             if( p_elt->i_update )
507             watch_update_Item( p_ml, p_elt->i_media_id, p_elt->p_item,
508                                ( p_elt->i_update & 2 ) ? true : false, true );
509             __watch_del_Item( p_ml, p_elt->p_item, true );
510             /* TODO: Do something about below crazy hack */
511             if( p_prev != NULL )
512                 p_elt = p_prev;
513             else
514             {
515                 ixx--;
516                 break;
517             }
518         }
519         else
520         {
521             p_prev = p_elt;
522             p_elt->i_age++;
523         }
524     }
525     vlc_mutex_unlock( &p_ml->p_sys->p_watch->list_mutex );
526 }
527
528 /**
529  * This function goes through a queue of input_items and checks
530  * if they are present in ML. All the items we wish to add in the
531  * watch Queue
532  */
533 static void watch_ProcessAppendQueue( media_library_t* p_ml )
534 {
535     watch_thread_t* p_wt = p_ml->p_sys->p_watch;
536     vlc_mutex_lock( &p_wt->item_append_queue_lock );
537     bool b_add = var_CreateGetBool( p_ml, "ml-auto-add" );
538     for( int i = 0; i < p_wt->item_append_queue_count; i++ )
539     {
540         input_item_t* p_item = p_wt->item_append_queue[i];
541         ml_media_t* p_media = NULL;
542         /* Is this item in ML? */
543         int i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
544         int i_ret = 0;
545         if( i_media_id <= 0 )
546         {
547             if( b_add )
548             {
549                 i_ret = AddInputItem( p_ml, p_item );
550                 /* FIXME: Need to skip? */
551                 if( i_ret != VLC_SUCCESS )
552                     continue;
553                 i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
554             }
555             else
556                 continue;
557         }
558         vlc_mutex_lock( &p_wt->list_mutex );
559         p_media = media_New( p_ml, i_media_id, ML_MEDIA, true );
560         if( p_media == NULL )
561         {
562             vlc_mutex_unlock( &p_wt->list_mutex );
563             continue;
564         }
565         /* If duplicate, then it just continues */
566         i_ret = __watch_add_Item( p_ml, p_item, p_media, true );
567         if( i_ret != VLC_SUCCESS )
568         {
569             ml_gc_decref( p_media );
570             vlc_mutex_unlock( &p_wt->list_mutex );
571             continue;
572         }
573
574         /* Find the new item and increment its ref */
575         il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
576         {
577             if( p_elt->p_item->i_id == p_item->i_id )
578             {
579                 p_elt->i_refs++;
580                 break;
581             }
582         }
583         vlc_mutex_unlock( &p_wt->list_mutex );
584         ml_gc_decref( p_media );
585     }
586     p_wt->item_append_queue_count = 0;
587     FREENULL( p_wt->item_append_queue );
588     vlc_mutex_unlock( &p_wt->item_append_queue_lock );
589 }