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_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
,
36 static int watch_PlaylistItemAppend( vlc_object_t
*p_this
, char const *psz_var
,
37 vlc_value_t oldval
, vlc_value_t newval
,
39 static int watch_PlaylistItemDeleted( vlc_object_t
*p_this
, char const *psz_var
,
40 vlc_value_t oldval
, vlc_value_t newval
,
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
);
49 * @brief Watching thread
51 static void* watch_Thread( void *obj
)
53 watch_thread_t
*p_watch
= ( watch_thread_t
* )obj
;
54 media_library_t
*p_ml
= p_watch
->p_ml
;
57 vlc_mutex_lock( &p_watch
->lock
);
58 vlc_cleanup_push( watch_Thread_Cleanup
, p_ml
);
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
);
70 * @brief Callback for thread exit
72 static void watch_Thread_Cleanup( void* p_object
)
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
);
79 * @brief Init watching system
80 * @return Error if the object or the thread could not be created
82 int watch_Init( media_library_t
*p_ml
)
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
)
89 watch_thread_t
* p_wt
= p_ml
->p_sys
->p_watch
;
90 vlc_mutex_init( &p_wt
->list_mutex
);
93 vlc_cond_init( &p_wt
->cond
);
94 vlc_mutex_init( &p_wt
->lock
);
96 if( vlc_clone( &p_wt
->thread
, watch_Thread
, p_wt
, VLC_THREAD_PRIORITY_LOW
) )
98 msg_Dbg( p_ml
, "unable to launch the auto-updating thread" );
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
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
);
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;
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
130 int __watch_add_Item( media_library_t
*p_ml
, input_item_t
*p_item
,
131 ml_media_t
* p_media
, bool locked
)
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
)
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
);
143 Note: vlc_InputItemDurationChanged is disabled because
144 it is triggered too often, even without consequent changes
151 * @brief Detach event manager
152 * @param p_ml The Media Library Object
154 static void detachItemEvents( media_library_t
*p_ml
, input_item_t
*p_item
)
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
);
164 * @brief Close the watching system
165 * @param p_ml The Media Library Object
167 void watch_Close( media_library_t
*p_ml
)
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
);
174 /* Flush item list */
175 il_foreachhashlist( p_ml
->p_sys
->p_watch
->p_hlist
, p_elt
, ixx
)
177 detachItemEvents( p_ml
, p_elt
->p_item
);
178 ml_gc_decref( p_elt
->p_media
);
179 vlc_gc_decref( p_elt
->p_item
);
181 item_list_destroy( p_ml
->p_sys
->p_watch
);
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
);
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
);
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
;
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
204 int __watch_del_Item( media_library_t
* p_ml
, input_item_t
* p_item
, bool locked
)
207 item_list_t
* p_tmp
= item_list_delItem( p_ml
->p_sys
->p_watch
, p_item
, locked
);
210 detachItemEvents( p_ml
, p_tmp
->p_item
);
211 vlc_gc_decref( p_tmp
->p_item
);
212 ml_gc_decref( p_tmp
->p_media
);
218 * @brief Del media from watching by ID
219 * @param p_ml The Media Library Object
220 * @param i_media_id Media ID
222 int watch_del_MediaById( media_library_t
* p_ml
, int i_media_id
)
224 assert( i_media_id
> 0 );
225 item_list_t
* p_elt
= item_list_delMedia( p_ml
->p_sys
->p_watch
, i_media_id
);
228 detachItemEvents( p_ml
, p_elt
->p_item
);
229 vlc_gc_decref( p_elt
->p_item
);
230 ml_gc_decref( p_elt
->p_media
);
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
240 input_item_t
* watch_get_itemOfMediaId( media_library_t
*p_ml
, int i_media_id
)
242 input_item_t
* p_tmp
= item_list_itemOfMediaId( p_ml
->p_sys
->p_watch
, i_media_id
);
245 vlc_gc_incref( p_tmp
);
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
254 ml_media_t
* watch_get_mediaOfMediaId( media_library_t
* p_ml
, int i_media_id
)
256 ml_media_t
* p_tmp
= item_list_mediaOfMediaId( p_ml
->p_sys
->p_watch
, i_media_id
);
259 ml_gc_incref( p_tmp
);
264 * @brief Get mediaid of existing item
265 * @param p_ml The Media Library Object
266 * @param p_item Pointer to input item
268 int watch_get_mediaIdOfItem( media_library_t
*p_ml
, input_item_t
*p_item
)
270 return item_list_mediaIdOfItem( p_ml
->p_sys
->p_watch
, p_item
);
274 * @brief Updates a media each time it is changed (name, info or meta)
276 static void watch_ItemChange( const vlc_event_t
*event
, void *data
)
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 )
285 msg_Dbg( p_ml
, "Couldn't update in watch_ItemChange(): (%s:%d)",
286 __FILE__
, __LINE__
);
291 if( event->type == vlc_InputItemMetaChanged )
293 int id = item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
296 * Tell the world what happened *
297 var_SetInteger( p_ml, "media-meta-change", id );
303 * @brief Callback when item is added to playlist
305 static int watch_PlaylistItemAppend( vlc_object_t
*p_this
, char const *psz_var
,
306 vlc_value_t oldval
, vlc_value_t newval
,
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
;
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
)
324 if( p_elt
->p_item
->i_id
== p_item
->i_id
)
327 vlc_mutex_unlock( &p_wt
->list_mutex
);
328 goto quit_playlistitemappend
;
331 vlc_mutex_unlock( &p_wt
->list_mutex
);
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
:
346 * @brief Callback when item is deleted from playlist
348 static int watch_PlaylistItemDeleted( vlc_object_t
*p_this
, char const *psz_var
,
349 vlc_value_t oldval
, vlc_value_t newval
,
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
);
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
;
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
)
365 if( p_elt
->p_item
->i_id
== p_item
->i_id
)
375 * @brief Callback when watched input item starts playing
376 * @note This will update playcount mainly
377 * TODO: Increment playcount on playing 50%(configurable)
379 static int watch_PlaylistItemCurrent( vlc_object_t
*p_this
, char const *psz_var
,
380 vlc_value_t oldval
, vlc_value_t newval
,
386 media_library_t
*p_ml
= ( media_library_t
* ) data
;
387 input_item_t
*p_item
= NULL
;
389 if( strcmp( psz_var
, "item-current" ) != 0 )
390 /* This case should not happen... */
393 /* Get current input */
394 input_thread_t
*p_input
= pl_CurrentInput( p_ml
);
395 p_item
= p_input
? input_GetItem( p_input
) : NULL
;
398 vlc_object_release( p_input
);
403 if( item_list_updateInput( p_ml
->p_sys
->p_watch
, p_item
, true ) == 0 )
406 msg_Dbg( p_ml
, "couldn't in watch_PlaylistItemCurrent(): (%s:%d)",
407 __FILE__
, __LINE__
);
415 * @brief Update informations in the DB for an input item
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()
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
)
428 msg_Dbg( p_ml
, "automatically updating media %d", i_media_id
);
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
);
437 /* Add the poster to the album */
438 ml_LockMedia( p_media
);
439 if( p_media
->i_album_id
&& p_media
->psz_cover
)
441 SetArtCover( p_ml
, p_media
->i_album_id
, p_media
->psz_cover
);
443 ml_UnlockMedia( p_media
);
449 * @brief Signals the watch system to update all medias
451 void watch_Force_Update( media_library_t
* p_ml
)
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
);
459 * @brief Loop on the item_list: old objects collector and automatic updater
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
467 * The update of an item is done when its age is >= ITEM_LOOP_UPDATE
468 * (0 could lead to a too early update)
470 * A thread should call this function every N seconds
472 * @param p_ml the media library instance
474 static void watch_loop( media_library_t
*p_ml
, bool b_force
)
476 /* Do the garbage collection */
479 /* Process the append queue */
480 watch_ProcessAppendQueue( p_ml
);
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
)
487 if( ( p_elt
->i_update
&& p_elt
->i_age
>= ITEM_LOOP_UPDATE
)
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 */
496 p_elt
->i_update
= false;
498 else if( p_elt
->i_refs
== 0 )
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 */
519 vlc_mutex_unlock( &p_ml
->p_sys
->p_watch
->list_mutex
);
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
527 static void watch_ProcessAppendQueue( media_library_t
* p_ml
)
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
++ )
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
);
539 if( i_media_id
<= 0 )
543 i_ret
= AddInputItem( p_ml
, p_item
);
544 /* FIXME: Need to skip? */
545 if( i_ret
!= VLC_SUCCESS
)
547 i_media_id
= GetMediaIdOfURI( p_ml
, p_item
->psz_uri
);
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
)
556 vlc_mutex_unlock( &p_wt
->list_mutex
);
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
)
563 ml_gc_decref( p_media
);
564 vlc_mutex_unlock( &p_wt
->list_mutex
);
568 /* Find the new item and increment its ref */
569 il_foreachlist( p_wt
->p_hlist
[ item_hash( p_item
) ], p_elt
)
571 if( p_elt
->p_item
->i_id
== p_item
->i_id
)
577 vlc_mutex_unlock( &p_wt
->list_mutex
);
578 ml_gc_decref( p_media
);
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
);