1 /*****************************************************************************
2 * sql_monitor.c: SQL-based media library: directory scanning and monitoring
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 /** **************************************************************************
29 * MONITORING AND DIRECTORY SCANNING FUNCTIONS
30 *****************************************************************************/
35 #include "sql_media_library.h"
36 #include "vlc_playlist.h"
40 static const char* ppsz_MediaExtensions[] =
41 { EXTENSIONS_AUDIO_CSV, EXTENSIONS_VIDEO_CSV, NULL };
44 /* Monitoring and directory scanning private functions */
45 typedef struct stat_list_t stat_list_t;
46 typedef struct preparsed_item_t preparsed_item_t;
47 static void UpdateLibrary( monitoring_thread_t *p_mon );
48 static void ScanFiles( monitoring_thread_t *, int, bool, stat_list_t *stparent );
49 static int Sort( const char **, const char ** );
51 /* Struct used to verify there are no recursive directory */
58 struct preparsed_item_t
60 monitoring_thread_t *p_mon;
69 * @brief Remove a directory to monitor
70 * @param p_ml A media library object
71 * @param psz_dir the directory to remove
72 * @return VLC_SUCCESS or VLC_EGENERIC
74 int RemoveDirToMonitor( media_library_t *p_ml, const char *psz_dir )
78 char **pp_results = NULL;
79 int i_cols = 0, i_rows = 0, i_ret = VLC_SUCCESS;
82 bool b_recursive = var_CreateGetBool( p_ml, "ml-recursive-scan" );
86 i_ret = Query( p_ml, &pp_results, &i_rows, &i_cols,
87 "SELECT media.id FROM media JOIN directories ON "
88 "(media.directory_id = directories.id) WHERE "
89 "directories.uri LIKE '%q%%'",
91 if( i_ret != VLC_SUCCESS )
93 msg_Err( p_ml, "Error occurred while making a query to the database" );
96 QuerySimple( p_ml, "DELETE FROM directories WHERE uri LIKE '%q%%'",
101 i_ret = Query( p_ml, &pp_results, &i_rows, &i_cols,
102 "SELECT media.id FROM media JOIN directories ON "
103 "(media.directory_id = directories.id) WHERE "
104 "directories.uri = %Q",
106 if( i_ret != VLC_SUCCESS )
108 msg_Err( p_ml, "Error occurred while making a query to the database" );
111 QuerySimple( p_ml, "DELETE FROM directories WHERE uri = %Q",
115 vlc_array_t *p_where = vlc_array_new();
116 for( i = 1; i <= i_rows; i++ )
118 int id = atoi( pp_results[i*i_cols] );
119 ml_element_t* p_find = ( ml_element_t * ) calloc( 1, sizeof( ml_element_t ) );
120 p_find->criteria = ML_ID;
121 p_find->value.i = id;
122 vlc_array_append( p_where, p_find );
124 Delete( p_ml, p_where );
126 FreeSQLResult( p_ml, pp_results );
127 for( i = 0; i < vlc_array_count( p_where ); i++ )
129 free( vlc_array_item_at_index( p_where, i ) );
131 vlc_array_destroy( p_where );
136 * @brief Get the list of the monitored directories
137 * @param p_ml A media library object
138 * @param p_array An initialized array where the list will be put in
139 * @return VLC_SUCCESS or VLC_EGENERIC
141 int ListMonitoredDirs( media_library_t *p_ml, vlc_array_t *p_array )
147 if( Query( p_ml, &pp_results, &i_rows, &i_cols,
148 "SELECT uri AS directory_uri FROM directories WHERE recursive=0" )
152 for( i = 1; i <= i_rows; i++ )
154 vlc_array_append( p_array, strdup( pp_results[i] ) );
156 FreeSQLResult( p_ml, pp_results );
162 * @brief Add a directory to monitor
163 * @param p_ml This media_library_t object
164 * @param psz_dir the directory to add
165 * @return VLC_SUCCESS or VLC_EGENERIC
167 int AddDirToMonitor( media_library_t *p_ml, const char *psz_dir )
171 /* Verify if we can open the directory */
172 DIR *dir = vlc_opendir( psz_dir );
177 msg_Err( p_ml, "%s: %m", psz_dir );
179 msg_Dbg( p_ml, "`%s' is not a directory", psz_dir );
186 msg_Dbg( p_ml, "Adding directory `%s' to be monitored", psz_dir );
187 QuerySimple( p_ml, "INSERT INTO directories ( uri, timestamp, "
188 "recursive ) VALUES( %Q, 0, 0 )", psz_dir );
189 vlc_cond_signal( &p_ml->p_sys->p_mon->wait );
194 static int Sort( const char **a, const char **b )
197 return strcoll( *a, *b );
199 return strcmp( *a, *b );
204 * @brief Directory Monitoring thread loop
206 void *RunMonitoringThread( void *p_this )
208 monitoring_thread_t *p_mon = (monitoring_thread_t*) p_this;
209 vlc_cond_init( &p_mon->wait );
210 vlc_mutex_init( &p_mon->lock );
212 var_Create( p_mon, "ml-recursive-scan", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
214 while( vlc_object_alive( p_mon ) )
216 vlc_mutex_lock( &p_mon->lock );
219 UpdateLibrary( p_mon );
221 /* We wait MONITORING_DELAY seconds or wait that the media library
222 signals us to do something */
223 vlc_cond_timedwait( &p_mon->wait, &p_mon->lock,
224 mdate() + 1000000*MONITORING_DELAY );
226 vlc_mutex_unlock( &p_mon->lock );
228 vlc_cond_destroy( &p_mon->wait );
229 vlc_mutex_destroy( &p_mon->lock );
234 * @brief Update library if new files found or updated
236 static void UpdateLibrary( monitoring_thread_t *p_mon )
238 int i_rows, i_cols, i;
240 media_library_t *p_ml = p_mon->p_ml;
244 bool b_recursive = var_GetBool( p_mon, "ml-recursive-scan" );
246 msg_Dbg( p_mon, "Scanning directories" );
248 Query( p_ml, &pp_results, &i_rows, &i_cols,
249 "SELECT id AS directory_id, uri AS directory_uri, "
250 "timestamp AS directory_ts FROM directories" );
251 msg_Dbg( p_mon, "%d directories to scan", i_rows );
253 for( i = 1; i <= i_rows; i++ )
255 int id = atoi( pp_results[i*i_cols] );
256 char *psz_dir = pp_results[i*i_cols+1];
257 int timestamp = atoi( pp_results[i*i_cols+2] );
259 if( vlc_stat( psz_dir, &s_stat ) == -1 )
262 if( err == ENOTDIR || err == ENOENT )
264 msg_Dbg( p_mon, "Removing `%s'", psz_dir );
265 RemoveDirToMonitor( p_ml, psz_dir );
269 msg_Err( p_mon, "%s: %m", psz_dir );
270 FreeSQLResult( p_ml, pp_results );
276 if( !S_ISDIR( s_stat.st_mode ) )
278 msg_Dbg( p_mon, "Removing `%s'", psz_dir );
279 RemoveDirToMonitor( p_ml, psz_dir );
282 if( timestamp < s_stat.st_mtime )
284 msg_Dbg( p_mon, "Adding `%s'", psz_dir );
285 ScanFiles( p_mon, id, b_recursive, NULL );
288 FreeSQLResult( p_ml, pp_results );
292 * @brief Callback for input item preparser to directory monitor
294 static void PreparseComplete( const vlc_event_t * p_event, void *p_data )
296 int i_ret = VLC_SUCCESS;
297 preparsed_item_t* p_itemobject = (preparsed_item_t*) p_data;
298 monitoring_thread_t *p_mon = p_itemobject->p_mon;
299 media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
300 input_item_t *p_input = (input_item_t*) p_event->p_obj;
302 if( input_item_IsPreparsed( p_input ) )
304 if( p_itemobject->b_update )
306 //TODO: Perhaps we don't have to load everything?
307 ml_media_t* p_media = GetMedia( p_ml, p_itemobject->i_update_id,
308 ML_MEDIA_SPARSE, true );
309 CopyInputItemToMedia( p_media, p_input );
310 i_ret = UpdateMedia( p_ml, p_media );
311 ml_gc_decref( p_media );
314 i_ret = AddInputItem( p_ml, p_input );
317 if( i_ret != VLC_SUCCESS )
318 msg_Dbg( p_mon, "Item could not be correctly added"
319 " or updated during scan: %s", p_input->psz_uri );
320 QuerySimple( p_ml, "UPDATE media SET directory_id=%d, timestamp=%d "
322 p_itemobject->i_dir_id, p_itemobject->i_mtime,
323 GetMediaIdOfURI( p_ml, p_input->psz_uri ) );
324 vlc_event_detach( &p_input->event_manager, vlc_InputItemPreparsedChanged,
325 PreparseComplete, p_itemobject );
326 vlc_gc_decref( p_input );
327 free( p_itemobject->psz_uri );
331 * @brief Scan files in a particular directory
333 static void ScanFiles( monitoring_thread_t *p_mon, int i_dir_id,
334 bool b_recursive, stat_list_t *stparent )
336 int i_rows, i_cols, i_dir_content, i, i_mon_rows, i_mon_cols;
337 char **ppsz_monitored_files;
338 char **pp_results, *psz_dir;
339 char **pp_dir_content;
341 input_item_t *p_input;
343 media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
345 Query( p_ml, &pp_results, &i_rows, &i_cols,
346 "SELECT uri AS directory_uri FROM directories WHERE id = '%d'",
350 msg_Dbg( p_mon, "query returned no directory for dir_id: %d (%s:%d)",
351 i_dir_id, __FILE__, __LINE__ );
354 psz_dir = strdup( pp_results[1] );
355 FreeSQLResult( p_ml, pp_results );
357 struct stat_list_t stself;
359 if( vlc_stat( psz_dir, &stself.st ) == -1 )
361 msg_Err( p_ml, "Cannot stat `%s': %m", psz_dir );
366 for( stat_list_t *stats = stparent; stats != NULL; stats = stats->parent )
368 if( ( stself.st.st_ino == stats->st.st_ino ) &&
369 ( stself.st.st_dev == stats->st.st_dev ) )
371 msg_Warn( p_ml, "Ignoring infinitely recursive directory `%s'",
378 /* Windows has st_dev (driver letter - 'A'), but it zeroes st_ino,
379 * so that the test above will always incorrectly succeed.
380 * Besides, Windows does not have dirfd(). */
382 stself.parent = stparent;
384 QuerySimple( p_ml, "UPDATE directories SET timestamp=%d WHERE id = %d",
385 stself.st.st_mtime, i_dir_id );
386 Query( p_ml, &ppsz_monitored_files, &i_mon_rows, &i_mon_cols,
387 "SELECT id AS media_id, timestamp AS media_ts, uri AS media_uri "
388 "FROM media WHERE directory_id = %d",
390 pb_processed = malloc(sizeof(bool) * i_mon_rows);
391 for( i = 0; i < i_mon_rows ; i++)
392 pb_processed[i] = false;
394 i_dir_content = vlc_scandir( psz_dir, &pp_dir_content, NULL, Sort );
395 if( i_dir_content == -1 )
397 msg_Err( p_mon, "Cannot read `%s': %m", psz_dir );
398 free( pb_processed );
402 else if( i_dir_content == 0 )
404 msg_Dbg( p_mon, "Nothing in directory `%s'", psz_dir );
405 free( pb_processed );
410 for( i = 0; i < i_dir_content; i++ )
412 const char *psz_entry = pp_dir_content[i];
414 if( psz_entry[0] != '.' )
416 /* 7 is the size of "file://" */
417 char psz_uri[strlen(psz_dir) + strlen(psz_entry) + 2 + 7];
418 sprintf( psz_uri, "%s/%s", psz_dir, psz_entry );
420 if( vlc_stat( psz_uri, &s_stat ) == -1 )
422 msg_Err( p_mon, "%s: %m", psz_uri );
423 free( pb_processed );
428 if( S_ISREG( s_stat.st_mode ) )
430 const char *psz_dot = strrchr( psz_uri, '.' );
431 if( psz_dot++ && *psz_dot )
434 for( int a = 0; ppsz_MediaExtensions[a]; a++ )
436 if( !strcasecmp( psz_dot, ppsz_MediaExtensions[a] ) )
444 msg_Dbg( p_mon, "ignoring file %s", psz_uri );
449 char * psz_tmp = encode_URI_component( psz_uri );
450 char * psz_encoded_uri = ( char * )calloc( strlen( psz_tmp ) + 9, 1 );
451 strcpy( psz_encoded_uri, "file:///" );
452 strcat( psz_encoded_uri, psz_tmp );
455 /* Check if given media is already in DB and it has been updated */
457 bool b_update = false;
459 for( j = 1; j <= i_mon_rows; j++ )
461 if( strcasecmp( ppsz_monitored_files[ j * i_mon_cols + 2 ],
462 psz_encoded_uri ) != 0 )
465 pb_processed[ j - 1 ] = true;
466 if( atoi( ppsz_monitored_files[ j * i_mon_cols + 1 ] )
478 msg_Dbg( p_ml , "Checking if %s is in DB. Found: %d", psz_encoded_uri,
483 p_input = input_item_New( psz_encoded_uri, psz_entry );
485 playlist_t* p_pl = pl_Get( p_mon );
486 preparsed_item_t* p_itemobject;
487 p_itemobject = malloc( sizeof( preparsed_item_t ) );
488 p_itemobject->i_dir_id = i_dir_id;
489 p_itemobject->psz_uri = psz_encoded_uri;
490 p_itemobject->i_mtime = s_stat.st_mtime;
491 p_itemobject->p_mon = p_mon;
492 p_itemobject->b_update = b_update;
493 p_itemobject->i_update_id = b_update ?
494 atoi( ppsz_monitored_files[ j * i_mon_cols + 0 ] ) : 0 ;
496 vlc_event_manager_t *p_em = &p_input->event_manager;
497 vlc_event_attach( p_em, vlc_InputItemPreparsedChanged,
498 PreparseComplete, p_itemobject );
499 playlist_PreparseEnqueue( p_pl, p_input );
501 else if( S_ISDIR( s_stat.st_mode ) && b_recursive )
503 Query( p_ml, &pp_results, &i_rows, &i_cols,
504 "SELECT id AS directory_id FROM directories "
505 "WHERE uri=%Q", psz_uri );
506 FreeSQLResult( p_ml, pp_results );
510 msg_Dbg( p_mon, "New directory `%s' in dir of id %d",
513 "INSERT INTO directories (uri, timestamp, "
514 "recursive) VALUES(%Q, 0, 1)", psz_uri );
516 // We get the id of the directory we've just added
517 Query( p_ml, &pp_results, &i_rows, &i_cols,
518 "SELECT id AS directory_id FROM directories WHERE uri=%Q",
522 msg_Err( p_mon, "Directory `%s' was not sucessfully"
523 " added to the database", psz_uri );
524 FreeSQLResult( p_ml, pp_results );
528 ScanFiles( p_mon, atoi( pp_results[1] ), b_recursive,
530 FreeSQLResult( p_ml, pp_results );
536 vlc_array_t* delete_ids = vlc_array_new();
537 for( i = 0; i < i_mon_rows; i++ )
539 if( !pb_processed[i] )
541 /* This file doesn't exist anymore. Let's...urm...delete it. */
542 ml_element_t* find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
543 find->criteria = ML_ID;
544 find->value.i = atoi( ppsz_monitored_files[ (i + 1) * i_mon_cols ] );
545 vlc_array_append( delete_ids, find );
549 /* Delete the unfound media */
550 if( Delete( p_ml, delete_ids ) != VLC_SUCCESS )
551 msg_Dbg( p_ml, "Something went wrong in multi delete" );
553 for( i = 0; i < vlc_array_count( delete_ids ); i++ )
555 free( vlc_array_item_at_index( delete_ids, i ) );
557 vlc_array_destroy( delete_ids );
559 FreeSQLResult( p_ml, ppsz_monitored_files );
560 for( i = 0; i < i_dir_content; i++ )
561 free( pp_dir_content[i] );
562 free( pp_dir_content );
564 free( pb_processed );