1 /*****************************************************************************
2 * ml_watch.c: SQL-based media library: Medias watching system
3 *****************************************************************************
4 * Copyright (C) 2008-2010 the VideoLAN team and AUTHORS
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>
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.
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.
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 *****************************************************************************/
28 #include "sql_media_library.h"
29 #include "item_list.h"
30 #include <vlc_events.h>
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,
37 static int watch_PlaylistItemAppend( vlc_object_t *p_this, char const *psz_var,
38 vlc_value_t oldval, vlc_value_t newval,
40 static int watch_PlaylistItemDeleted( vlc_object_t *p_this, char const *psz_var,
41 vlc_value_t oldval, vlc_value_t newval,
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 );
50 * @brief Watching thread
52 static void* watch_Thread( vlc_object_t *p_this )
54 watch_thread_t *p_watch = ( watch_thread_t* ) p_this;
55 media_library_t *p_ml = p_watch->p_ml;
58 vlc_mutex_lock( &p_watch->lock );
59 vlc_cleanup_push( watch_Thread_Cleanup, p_ml );
60 while( vlc_object_alive( p_watch ) )
62 watch_loop( p_ml, !i_ret );
63 if( !vlc_object_alive( p_watch ) )
65 i_ret = vlc_cond_timedwait( &p_watch->cond, &p_watch->lock,
66 mdate() + 1000000 * THREAD_SLEEP_DELAY );
73 * @brief Callback for thread exit
75 static void watch_Thread_Cleanup( void* p_object )
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 );
82 * @brief Init watching system
83 * @return Error if the object or the thread could not be created
85 int watch_Init( media_library_t *p_ml )
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 )
92 watch_thread_t* p_wt = p_ml->p_sys->p_watch;
93 vlc_mutex_init( &p_wt->list_mutex );
96 vlc_object_attach( p_wt, p_ml );
98 vlc_cond_init( &p_wt->cond );
99 vlc_mutex_init( &p_wt->lock );
101 if( vlc_thread_create( p_wt, "Media Library Auto-Update",
102 watch_Thread, VLC_THREAD_PRIORITY_LOW ) )
104 msg_Dbg( p_ml, "unable to launch the auto-updating thread" );
105 vlc_object_release( p_wt );
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
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 );
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;
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
136 int __watch_add_Item( media_library_t *p_ml, input_item_t *p_item,
137 ml_media_t* p_media, bool locked )
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 )
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 );
149 Note: vlc_InputItemDurationChanged is disabled because
150 it is triggered too often, even without consequent changes
157 * @brief Detach event manager
158 * @param p_ml The Media Library Object
160 static void detachItemEvents( media_library_t *p_ml, input_item_t *p_item )
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 );
170 * @brief Close the watching system
171 * @param p_ml The Media Library Object
173 void watch_Close( media_library_t *p_ml )
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 );
180 /* Flush item list */
181 il_foreachhashlist( p_ml->p_sys->p_watch->p_hlist, p_elt, ixx )
183 detachItemEvents( p_ml, p_elt->p_item );
184 ml_gc_decref( p_elt->p_media );
185 vlc_gc_decref( p_elt->p_item );
187 item_list_destroy( p_ml->p_sys->p_watch );
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 );
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 );
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;
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
210 int __watch_del_Item( media_library_t* p_ml, input_item_t* p_item, bool locked )
213 item_list_t* p_tmp = item_list_delItem( p_ml->p_sys->p_watch, p_item, locked );
216 detachItemEvents( p_ml, p_tmp->p_item );
217 vlc_gc_decref( p_tmp->p_item );
218 ml_gc_decref( p_tmp->p_media );
224 * @brief Del media from watching by ID
225 * @param p_ml The Media Library Object
226 * @param i_media_id Media ID
228 int watch_del_MediaById( media_library_t* p_ml, int i_media_id )
230 assert( i_media_id > 0 );
231 item_list_t* p_elt = item_list_delMedia( p_ml->p_sys->p_watch, i_media_id );
234 detachItemEvents( p_ml, p_elt->p_item );
235 vlc_gc_decref( p_elt->p_item );
236 ml_gc_decref( p_elt->p_media );
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
246 input_item_t* watch_get_itemOfMediaId( media_library_t *p_ml, int i_media_id )
248 input_item_t* p_tmp = item_list_itemOfMediaId( p_ml->p_sys->p_watch, i_media_id );
251 vlc_gc_incref( p_tmp );
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
260 ml_media_t* watch_get_mediaOfMediaId( media_library_t* p_ml, int i_media_id )
262 ml_media_t* p_tmp = item_list_mediaOfMediaId( p_ml->p_sys->p_watch, i_media_id );
265 ml_gc_incref( p_tmp );
270 * @brief Get mediaid of existing item
271 * @param p_ml The Media Library Object
272 * @param p_item Pointer to input item
274 int watch_get_mediaIdOfItem( media_library_t *p_ml, input_item_t *p_item )
276 return item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
280 * @brief Updates a media each time it is changed (name, info or meta)
282 static void watch_ItemChange( const vlc_event_t *event, void *data )
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 )
291 msg_Dbg( p_ml, "Couldn't update in watch_ItemChange(): (%s:%d)",
292 __FILE__, __LINE__ );
297 if( event->type == vlc_InputItemMetaChanged )
299 int id = item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
302 * Tell the world what happened *
303 var_SetInteger( p_ml, "media-meta-change", id );
309 * @brief Callback when item is added to playlist
311 static int watch_PlaylistItemAppend( vlc_object_t *p_this, char const *psz_var,
312 vlc_value_t oldval, vlc_value_t newval,
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;
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 )
330 if( p_elt->p_item->i_id == p_item->i_id )
333 vlc_mutex_unlock( &p_wt->list_mutex );
334 goto quit_playlistitemappend;
337 vlc_mutex_unlock( &p_wt->list_mutex );
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:
352 * @brief Callback when item is deleted from playlist
354 static int watch_PlaylistItemDeleted( vlc_object_t *p_this, char const *psz_var,
355 vlc_value_t oldval, vlc_value_t newval,
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 );
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;
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 )
371 if( p_elt->p_item->i_id == p_item->i_id )
381 * @brief Callback when watched input item starts playing
382 * @note This will update playcount mainly
383 * TODO: Increment playcount on playing 50%(configurable)
385 static int watch_PlaylistItemCurrent( vlc_object_t *p_this, char const *psz_var,
386 vlc_value_t oldval, vlc_value_t newval,
392 media_library_t *p_ml = ( media_library_t* ) data;
393 input_item_t *p_item = NULL;
395 if( strcmp( psz_var, "item-current" ) != 0 )
396 /* This case should not happen... */
399 /* Get current input */
400 input_thread_t *p_input = pl_CurrentInput( p_ml );
401 p_item = p_input ? input_GetItem( p_input ) : NULL;
404 vlc_object_release( p_input );
409 if( item_list_updateInput( p_ml->p_sys->p_watch, p_item, true ) == 0 )
412 msg_Dbg( p_ml, "couldn't in watch_PlaylistItemCurrent(): (%s:%d)",
413 __FILE__, __LINE__ );
421 * @brief Update informations in the DB for an input item
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()
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 )
434 msg_Dbg( p_ml, "automatically updating media %d", i_media_id );
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 );
443 /* Add the poster to the album */
444 ml_LockMedia( p_media );
445 if( p_media->i_album_id && p_media->psz_cover )
447 SetArtCover( p_ml, p_media->i_album_id, p_media->psz_cover );
449 ml_UnlockMedia( p_media );
455 * @brief Signals the watch system to update all medias
457 void watch_Force_Update( media_library_t* p_ml )
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 );
465 * @brief Loop on the item_list: old objects collector and automatic updater
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
473 * The update of an item is done when its age is >= ITEM_LOOP_UPDATE
474 * (0 could lead to a too early update)
476 * A thread should call this function every N seconds
478 * @param p_ml the media library instance
480 static void watch_loop( media_library_t *p_ml, bool b_force )
482 /* Do the garbage collection */
485 /* Process the append queue */
486 watch_ProcessAppendQueue( p_ml );
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 )
493 if( ( p_elt->i_update && p_elt->i_age >= ITEM_LOOP_UPDATE )
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 */
502 p_elt->i_update = false;
504 else if( p_elt->i_refs == 0 )
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 */
525 vlc_mutex_unlock( &p_ml->p_sys->p_watch->list_mutex );
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
533 static void watch_ProcessAppendQueue( media_library_t* p_ml )
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++ )
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 );
545 if( i_media_id <= 0 )
549 i_ret = AddInputItem( p_ml, p_item );
550 /* FIXME: Need to skip? */
551 if( i_ret != VLC_SUCCESS )
553 i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
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 )
562 vlc_mutex_unlock( &p_wt->list_mutex );
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 )
569 ml_gc_decref( p_media );
570 vlc_mutex_unlock( &p_wt->list_mutex );
574 /* Find the new item and increment its ref */
575 il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
577 if( p_elt->p_item->i_id == p_item->i_id )
583 vlc_mutex_unlock( &p_wt->list_mutex );
584 ml_gc_decref( p_media );
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 );