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