]> git.sesse.net Git - vlc/blob - modules/media_library/sql_monitor.c
Missing #include
[vlc] / modules / media_library / sql_monitor.c
1 /*****************************************************************************
2  * sql_monitor.c: SQL-based media library: directory scanning and monitoring
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 /** **************************************************************************
29  * MONITORING AND DIRECTORY SCANNING FUNCTIONS
30  *****************************************************************************/
31 #ifdef HAVE_CONFIG_H
32 #   include "config.h"
33 #endif
34
35 #include "sql_media_library.h"
36 #include "vlc_playlist.h"
37 #include "vlc_url.h"
38 #include "vlc_fs.h"
39
40 static const char* ppsz_MediaExtensions[] =
41                         { EXTENSIONS_AUDIO_CSV, EXTENSIONS_VIDEO_CSV, NULL };
42
43
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 ** );
50
51 /* Struct used to verify there are no recursive directory */
52 struct stat_list_t
53 {
54     stat_list_t *parent;
55     struct stat st;
56 };
57
58 struct preparsed_item_t
59 {
60     monitoring_thread_t *p_mon;
61     char* psz_uri;
62     int i_dir_id;
63     int i_mtime;
64     int i_update_id;
65     bool b_update;
66 };
67
68 /**
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
73  */
74 int RemoveDirToMonitor( media_library_t *p_ml, const char *psz_dir )
75 {
76     assert( p_ml );
77
78     char **pp_results = NULL;
79     int i_cols = 0, i_rows = 0, i_ret = VLC_SUCCESS;
80     int i;
81
82     bool b_recursive = var_CreateGetBool( p_ml, "ml-recursive-scan" );
83
84     if( b_recursive )
85     {
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%%'",
90                           psz_dir );
91         if( i_ret != VLC_SUCCESS )
92         {
93             msg_Err( p_ml, "Error occured while making a query to the database" );
94             return i_ret;
95         }
96         QuerySimple( p_ml, "DELETE FROM directories WHERE uri LIKE '%q%%'",
97                         psz_dir );
98     }
99     else
100     {
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",
105                           psz_dir );
106         if( i_ret != VLC_SUCCESS )
107         {
108             msg_Err( p_ml, "Error occured while making a query to the database" );
109             return i_ret;
110         }
111         QuerySimple( p_ml, "DELETE FROM directories WHERE uri = %Q",
112                         psz_dir );
113     }
114
115     vlc_array_t *p_where = vlc_array_new();
116     for( i = 1; i <= i_rows; i++ )
117     {
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 );
123     }
124     Delete( p_ml, p_where );
125
126     FreeSQLResult( p_ml, pp_results );
127     for( i = 0; i < vlc_array_count( p_where ); i++ )
128     {
129         free( vlc_array_item_at_index( p_where, i ) );
130     }
131     vlc_array_destroy( p_where );
132     return VLC_SUCCESS;
133 }
134
135 /**
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
140  */
141 int ListMonitoredDirs( media_library_t *p_ml, vlc_array_t *p_array )
142 {
143     char **pp_results;
144     int i_cols, i_rows;
145     int i;
146
147     if( Query( p_ml, &pp_results, &i_rows, &i_cols,
148             "SELECT uri AS directory_uri FROM directories WHERE recursive=0" )
149         != VLC_SUCCESS )
150         return VLC_EGENERIC;
151
152     for( i = 1; i <= i_rows; i++ )
153     {
154         vlc_array_append( p_array, strdup( pp_results[i] ) );
155     }
156     FreeSQLResult( p_ml, pp_results );
157
158     return VLC_SUCCESS;
159 }
160
161 /**
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
166  */
167 int AddDirToMonitor( media_library_t *p_ml, const char *psz_dir )
168 {
169     assert( p_ml );
170
171     /* Verify if we can open the directory */
172     DIR *dir = vlc_opendir( psz_dir );
173     if( !dir )
174     {
175         int err = errno;
176         if( err != ENOTDIR )
177             msg_Err( p_ml, "%s: %m", psz_dir );
178         else
179             msg_Dbg( p_ml, "`%s' is not a directory", psz_dir );
180         errno = err;
181         return VLC_EGENERIC;
182     }
183
184     closedir( dir );
185
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 );
190     return VLC_SUCCESS;
191 }
192
193
194 static int Sort( const char **a, const char **b )
195 {
196 #ifdef HAVE_STRCOLL
197     return strcoll( *a, *b );
198 #else
199     return strcmp( *a, *b );
200 #endif
201 }
202
203 /**
204  * @brief Directory Monitoring thread loop
205  */
206 void *RunMonitoringThread( void *p_this )
207 {
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 );
211
212     var_Create( p_mon, "ml-recursive-scan", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
213
214     while( vlc_object_alive( p_mon ) )
215     {
216         vlc_mutex_lock( &p_mon->lock );
217
218         /* Update */
219         UpdateLibrary( p_mon );
220
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 );
225
226         vlc_mutex_unlock( &p_mon->lock );
227     }
228     vlc_cond_destroy( &p_mon->wait );
229     vlc_mutex_destroy( &p_mon->lock );
230     return NULL;
231 }
232
233 /**
234  * @brief Update library if new files found or updated
235  */
236 static void UpdateLibrary( monitoring_thread_t *p_mon )
237 {
238     int i_rows, i_cols, i;
239     char **pp_results;
240     media_library_t *p_ml = p_mon->p_ml;
241
242     struct stat s_stat;
243
244     bool b_recursive = var_GetBool( p_mon, "ml-recursive-scan" );
245
246     msg_Dbg( p_mon, "Scanning directories" );
247
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 );
252
253     for( i = 1; i <= i_rows; i++ )
254     {
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] );
258
259         if( vlc_stat( psz_dir, &s_stat ) == -1 )
260         {
261             int err = errno;
262             if( err == ENOTDIR || err == ENOENT )
263             {
264                 msg_Dbg( p_mon, "Removing `%s'", psz_dir );
265                 RemoveDirToMonitor( p_ml, psz_dir );
266             }
267             else
268             {
269                 msg_Err( p_mon, "%s: %m", psz_dir );
270                 FreeSQLResult( p_ml, pp_results );
271                 return;
272             }
273             errno = err;
274         }
275
276         if( !S_ISDIR( s_stat.st_mode ) )
277         {
278             msg_Dbg( p_mon, "Removing `%s'", psz_dir );
279             RemoveDirToMonitor( p_ml, psz_dir );
280         }
281
282         if( timestamp < s_stat.st_mtime )
283         {
284             msg_Dbg( p_mon, "Adding `%s'", psz_dir );
285             ScanFiles( p_mon, id, b_recursive, NULL );
286         }
287     }
288     FreeSQLResult( p_ml, pp_results );
289 }
290
291 /**
292  * @brief Callback for input item preparser to directory monitor
293  */
294 static void PreparseComplete( const vlc_event_t * p_event, void *p_data )
295 {
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;
301
302     if( input_item_IsPreparsed( p_input ) )
303     {
304         if( p_itemobject->b_update )
305         {
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 );
312         }
313         else
314             i_ret = AddInputItem( p_ml, p_input );
315     }
316
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 "
321                           "WHERE id=%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 );
328 }
329
330 /**
331  * @brief Scan files in a particular directory
332  */
333 static void ScanFiles( monitoring_thread_t *p_mon, int i_dir_id,
334                        bool b_recursive, stat_list_t *stparent )
335 {
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;
340     bool *pb_processed;
341     input_item_t *p_input;
342     struct stat s_stat;
343     media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
344
345     Query( p_ml, &pp_results, &i_rows, &i_cols,
346               "SELECT uri AS directory_uri FROM directories WHERE id = '%d'",
347               i_dir_id );
348     if( i_rows < 1 )
349     {
350         msg_Dbg( p_mon, "query returned no directory for dir_id: %d (%s:%d)",
351                  i_dir_id, __FILE__, __LINE__ );
352         return;
353     }
354     psz_dir = strdup( pp_results[1] );
355     FreeSQLResult( p_ml, pp_results );
356
357     struct stat_list_t stself;
358
359     if( vlc_stat( psz_dir, &stself.st ) == -1 )
360     {
361         msg_Err( p_ml, "Cannot stat `%s': %m", psz_dir );
362         free( psz_dir );
363         return;
364     }
365 #ifndef WIN32
366     for( stat_list_t *stats = stparent; stats != NULL; stats = stats->parent )
367     {
368         if( ( stself.st.st_ino == stats->st.st_ino ) &&
369             ( stself.st.st_dev == stats->st.st_dev ) )
370         {
371             msg_Warn( p_ml, "Ignoring infinitely recursive directory `%s'",
372                       psz_dir );
373             free( psz_dir );
374             return;
375         }
376     }
377 #else
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(). */
381 #endif
382     stself.parent = stparent;
383
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",
389               i_dir_id );
390     pb_processed = malloc(sizeof(bool) * i_mon_rows);
391     for( i = 0; i < i_mon_rows ; i++)
392         pb_processed[i] = false;
393
394     i_dir_content = vlc_scandir( psz_dir, &pp_dir_content, NULL, Sort );
395     if( i_dir_content == -1 )
396     {
397         msg_Err( p_mon, "Cannot read `%s': %m", psz_dir );
398         free( pb_processed );
399         free( psz_dir );
400         return;
401     }
402     else if( i_dir_content == 0 )
403     {
404         msg_Dbg( p_mon, "Nothing in directory `%s'", psz_dir );
405         free( pb_processed );
406         free( psz_dir );
407         return;
408     }
409
410     for( i = 0; i < i_dir_content; i++ )
411     {
412         const char *psz_entry = pp_dir_content[i];
413
414         if( psz_entry[0] != '.' )
415         {
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 );
419
420             if( vlc_stat( psz_uri, &s_stat ) == -1 )
421             {
422                 msg_Err( p_mon, "%s: %m", psz_uri );
423                 free( pb_processed );
424                 free( psz_dir );
425                 return;
426             }
427
428             if( S_ISREG( s_stat.st_mode ) )
429             {
430                 const char *psz_dot = strrchr( psz_uri, '.' );
431                 if( psz_dot++ && *psz_dot )
432                 {
433                     int i_is_media = 0;
434                     for( int a = 0; ppsz_MediaExtensions[a]; a++ )
435                     {
436                         if( !strcasecmp( psz_dot, ppsz_MediaExtensions[a] ) )
437                         {
438                             i_is_media = 1;
439                             break;
440                         }
441                     }
442                     if( !i_is_media )
443                     {
444                         msg_Dbg( p_mon, "ignoring file %s", psz_uri );
445                         continue;
446                     }
447                 }
448
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 );
453                 free( psz_tmp );
454
455                 /* Check if given media is already in DB and it has been updated */
456                 bool b_skip = false;
457                 bool b_update = false;
458                 int j = 1;
459                 for( j = 1; j <= i_mon_rows; j++ )
460                 {
461                     if( strcasecmp( ppsz_monitored_files[ j * i_mon_cols + 2 ],
462                                     psz_encoded_uri ) != 0 )
463                         continue;
464                     b_update = true;
465                     pb_processed[ j - 1 ] = true;
466                     if( atoi( ppsz_monitored_files[ j * i_mon_cols + 1 ] )
467                         < s_stat.st_mtime )
468                     {
469                         b_skip = false;
470                         break;
471                     }
472                     else
473                     {
474                         b_skip = true;
475                         break;
476                     }
477                 }
478                 msg_Dbg( p_ml , "Checking if %s is in DB. Found: %d", psz_encoded_uri,
479                          b_skip? 1 : 0 );
480                 if( b_skip )
481                     continue;
482
483                 p_input = input_item_New( psz_encoded_uri, psz_entry );
484
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 ;
495
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 );
500             }
501             else if( S_ISDIR( s_stat.st_mode ) && b_recursive )
502             {
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 );
507
508                 if( i_rows <= 0 )
509                 {
510                     msg_Dbg( p_mon, "New directory `%s' in dir of id %d",
511                              psz_uri, i_dir_id );
512                     QuerySimple( p_ml,
513                                     "INSERT INTO directories (uri, timestamp, "
514                                     "recursive) VALUES(%Q, 0, 1)", psz_uri );
515
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",
519                               psz_uri );
520                     if( i_rows <= 0 )
521                     {
522                         msg_Err( p_mon, "Directory `%s' was not sucessfully"
523                                 " added to the database", psz_uri );
524                         FreeSQLResult( p_ml, pp_results );
525                         continue;
526                     }
527
528                     ScanFiles( p_mon, atoi( pp_results[1] ), b_recursive,
529                                &stself );
530                     FreeSQLResult( p_ml, pp_results );
531                 }
532             }
533         }
534     }
535
536     vlc_array_t* delete_ids = vlc_array_new();
537     for( i = 0; i < i_mon_rows; i++ )
538     {
539        if( !pb_processed[i] )
540         {
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 );
546        }
547     }
548
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" );
552
553     for( i = 0; i < vlc_array_count( delete_ids ); i++ )
554     {
555        free( vlc_array_item_at_index( delete_ids, i ) );
556     }
557     vlc_array_destroy( delete_ids );
558
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 );
563     free( psz_dir );
564     free( pb_processed );
565 }