You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
565 lines
19 KiB
565 lines
19 KiB
/*****************************************************************************
|
|
* sql_monitor.c: SQL-based media library: directory scanning and monitoring
|
|
*****************************************************************************
|
|
* Copyright (C) 2008-2010 the VideoLAN team and AUTHORS
|
|
* $Id$
|
|
*
|
|
* Authors: Antoine Lejeune <phytos@videolan.org>
|
|
* Jean-Philippe André <jpeg@videolan.org>
|
|
* Rémi Duraffort <ivoire@videolan.org>
|
|
* Adrien Maglo <magsoft@videolan.org>
|
|
* Srikanth Raju <srikiraju at gmail dot com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
/** **************************************************************************
|
|
* MONITORING AND DIRECTORY SCANNING FUNCTIONS
|
|
*****************************************************************************/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "sql_media_library.h"
|
|
#include "vlc_playlist.h"
|
|
#include "vlc_url.h"
|
|
#include "vlc_fs.h"
|
|
|
|
static const char* ppsz_MediaExtensions[] =
|
|
{ EXTENSIONS_AUDIO_CSV, EXTENSIONS_VIDEO_CSV, NULL };
|
|
|
|
|
|
/* Monitoring and directory scanning private functions */
|
|
typedef struct stat_list_t stat_list_t;
|
|
typedef struct preparsed_item_t preparsed_item_t;
|
|
static void UpdateLibrary( monitoring_thread_t *p_mon );
|
|
static void ScanFiles( monitoring_thread_t *, int, bool, stat_list_t *stparent );
|
|
static int Sort( const char **, const char ** );
|
|
|
|
/* Struct used to verify there are no recursive directory */
|
|
struct stat_list_t
|
|
{
|
|
stat_list_t *parent;
|
|
struct stat st;
|
|
};
|
|
|
|
struct preparsed_item_t
|
|
{
|
|
monitoring_thread_t *p_mon;
|
|
char* psz_uri;
|
|
int i_dir_id;
|
|
int i_mtime;
|
|
int i_update_id;
|
|
bool b_update;
|
|
};
|
|
|
|
/**
|
|
* @brief Remove a directory to monitor
|
|
* @param p_ml A media library object
|
|
* @param psz_dir the directory to remove
|
|
* @return VLC_SUCCESS or VLC_EGENERIC
|
|
*/
|
|
int RemoveDirToMonitor( media_library_t *p_ml, const char *psz_dir )
|
|
{
|
|
assert( p_ml );
|
|
|
|
char **pp_results = NULL;
|
|
int i_cols = 0, i_rows = 0, i_ret = VLC_SUCCESS;
|
|
int i;
|
|
|
|
bool b_recursive = var_CreateGetBool( p_ml, "ml-recursive-scan" );
|
|
|
|
if( b_recursive )
|
|
{
|
|
i_ret = Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT media.id FROM media JOIN directories ON "
|
|
"(media.directory_id = directories.id) WHERE "
|
|
"directories.uri LIKE '%q%%'",
|
|
psz_dir );
|
|
if( i_ret != VLC_SUCCESS )
|
|
{
|
|
msg_Err( p_ml, "Error occured while making a query to the database" );
|
|
return i_ret;
|
|
}
|
|
QuerySimple( p_ml, "DELETE FROM directories WHERE uri LIKE '%q%%'",
|
|
psz_dir );
|
|
}
|
|
else
|
|
{
|
|
i_ret = Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT media.id FROM media JOIN directories ON "
|
|
"(media.directory_id = directories.id) WHERE "
|
|
"directories.uri = %Q",
|
|
psz_dir );
|
|
if( i_ret != VLC_SUCCESS )
|
|
{
|
|
msg_Err( p_ml, "Error occured while making a query to the database" );
|
|
return i_ret;
|
|
}
|
|
QuerySimple( p_ml, "DELETE FROM directories WHERE uri = %Q",
|
|
psz_dir );
|
|
}
|
|
|
|
vlc_array_t *p_where = vlc_array_new();
|
|
for( i = 1; i <= i_rows; i++ )
|
|
{
|
|
int id = atoi( pp_results[i*i_cols] );
|
|
ml_element_t* p_find = ( ml_element_t * ) calloc( 1, sizeof( ml_element_t ) );
|
|
p_find->criteria = ML_ID;
|
|
p_find->value.i = id;
|
|
vlc_array_append( p_where, p_find );
|
|
}
|
|
Delete( p_ml, p_where );
|
|
|
|
FreeSQLResult( p_ml, pp_results );
|
|
for( i = 0; i < vlc_array_count( p_where ); i++ )
|
|
{
|
|
free( vlc_array_item_at_index( p_where, i ) );
|
|
}
|
|
vlc_array_destroy( p_where );
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the list of the monitored directories
|
|
* @param p_ml A media library object
|
|
* @param p_array An initialized array where the list will be put in
|
|
* @return VLC_SUCCESS or VLC_EGENERIC
|
|
*/
|
|
int ListMonitoredDirs( media_library_t *p_ml, vlc_array_t *p_array )
|
|
{
|
|
char **pp_results;
|
|
int i_cols, i_rows;
|
|
int i;
|
|
|
|
if( Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT uri AS directory_uri FROM directories WHERE recursive=0" )
|
|
!= VLC_SUCCESS )
|
|
return VLC_EGENERIC;
|
|
|
|
for( i = 1; i <= i_rows; i++ )
|
|
{
|
|
vlc_array_append( p_array, strdup( pp_results[i] ) );
|
|
}
|
|
FreeSQLResult( p_ml, pp_results );
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief Add a directory to monitor
|
|
* @param p_ml This media_library_t object
|
|
* @param psz_dir the directory to add
|
|
* @return VLC_SUCCESS or VLC_EGENERIC
|
|
*/
|
|
int AddDirToMonitor( media_library_t *p_ml, const char *psz_dir )
|
|
{
|
|
assert( p_ml );
|
|
|
|
/* Verify if we can open the directory */
|
|
DIR *dir = vlc_opendir( psz_dir );
|
|
if( !dir )
|
|
{
|
|
int err = errno;
|
|
if( err != ENOTDIR )
|
|
msg_Err( p_ml, "%s: %m", psz_dir );
|
|
else
|
|
msg_Dbg( p_ml, "`%s' is not a directory", psz_dir );
|
|
errno = err;
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
closedir( dir );
|
|
|
|
msg_Dbg( p_ml, "Adding directory `%s' to be monitored", psz_dir );
|
|
QuerySimple( p_ml, "INSERT INTO directories ( uri, timestamp, "
|
|
"recursive ) VALUES( %Q, 0, 0 )", psz_dir );
|
|
vlc_cond_signal( &p_ml->p_sys->p_mon->wait );
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
|
|
static int Sort( const char **a, const char **b )
|
|
{
|
|
#ifdef HAVE_STRCOLL
|
|
return strcoll( *a, *b );
|
|
#else
|
|
return strcmp( *a, *b );
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Directory Monitoring thread loop
|
|
*/
|
|
void *RunMonitoringThread( void *p_this )
|
|
{
|
|
monitoring_thread_t *p_mon = (monitoring_thread_t*) p_this;
|
|
vlc_cond_init( &p_mon->wait );
|
|
vlc_mutex_init( &p_mon->lock );
|
|
|
|
var_Create( p_mon, "ml-recursive-scan", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
|
|
|
|
while( vlc_object_alive( p_mon ) )
|
|
{
|
|
vlc_mutex_lock( &p_mon->lock );
|
|
|
|
/* Update */
|
|
UpdateLibrary( p_mon );
|
|
|
|
/* We wait MONITORING_DELAY seconds or wait that the media library
|
|
signals us to do something */
|
|
vlc_cond_timedwait( &p_mon->wait, &p_mon->lock,
|
|
mdate() + 1000000*MONITORING_DELAY );
|
|
|
|
vlc_mutex_unlock( &p_mon->lock );
|
|
}
|
|
vlc_cond_destroy( &p_mon->wait );
|
|
vlc_mutex_destroy( &p_mon->lock );
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Update library if new files found or updated
|
|
*/
|
|
static void UpdateLibrary( monitoring_thread_t *p_mon )
|
|
{
|
|
int i_rows, i_cols, i;
|
|
char **pp_results;
|
|
media_library_t *p_ml = p_mon->p_ml;
|
|
|
|
struct stat s_stat;
|
|
|
|
bool b_recursive = var_GetBool( p_mon, "ml-recursive-scan" );
|
|
|
|
msg_Dbg( p_mon, "Scanning directories" );
|
|
|
|
Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT id AS directory_id, uri AS directory_uri, "
|
|
"timestamp AS directory_ts FROM directories" );
|
|
msg_Dbg( p_mon, "%d directories to scan", i_rows );
|
|
|
|
for( i = 1; i <= i_rows; i++ )
|
|
{
|
|
int id = atoi( pp_results[i*i_cols] );
|
|
char *psz_dir = pp_results[i*i_cols+1];
|
|
int timestamp = atoi( pp_results[i*i_cols+2] );
|
|
|
|
if( vlc_stat( psz_dir, &s_stat ) == -1 )
|
|
{
|
|
int err = errno;
|
|
if( err == ENOTDIR || err == ENOENT )
|
|
{
|
|
msg_Dbg( p_mon, "Removing `%s'", psz_dir );
|
|
RemoveDirToMonitor( p_ml, psz_dir );
|
|
}
|
|
else
|
|
{
|
|
msg_Err( p_mon, "%s: %m", psz_dir );
|
|
FreeSQLResult( p_ml, pp_results );
|
|
return;
|
|
}
|
|
errno = err;
|
|
}
|
|
|
|
if( !S_ISDIR( s_stat.st_mode ) )
|
|
{
|
|
msg_Dbg( p_mon, "Removing `%s'", psz_dir );
|
|
RemoveDirToMonitor( p_ml, psz_dir );
|
|
}
|
|
|
|
if( timestamp < s_stat.st_mtime )
|
|
{
|
|
msg_Dbg( p_mon, "Adding `%s'", psz_dir );
|
|
ScanFiles( p_mon, id, b_recursive, NULL );
|
|
}
|
|
}
|
|
FreeSQLResult( p_ml, pp_results );
|
|
}
|
|
|
|
/**
|
|
* @brief Callback for input item preparser to directory monitor
|
|
*/
|
|
static void PreparseComplete( const vlc_event_t * p_event, void *p_data )
|
|
{
|
|
int i_ret = VLC_SUCCESS;
|
|
preparsed_item_t* p_itemobject = (preparsed_item_t*) p_data;
|
|
monitoring_thread_t *p_mon = p_itemobject->p_mon;
|
|
media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
|
|
input_item_t *p_input = (input_item_t*) p_event->p_obj;
|
|
|
|
if( input_item_IsPreparsed( p_input ) )
|
|
{
|
|
if( p_itemobject->b_update )
|
|
{
|
|
//TODO: Perhaps we don't have to load everything?
|
|
ml_media_t* p_media = GetMedia( p_ml, p_itemobject->i_update_id,
|
|
ML_MEDIA_SPARSE, true );
|
|
CopyInputItemToMedia( p_media, p_input );
|
|
i_ret = UpdateMedia( p_ml, p_media );
|
|
ml_gc_decref( p_media );
|
|
}
|
|
else
|
|
i_ret = AddInputItem( p_ml, p_input );
|
|
}
|
|
|
|
if( i_ret != VLC_SUCCESS )
|
|
msg_Dbg( p_mon, "Item could not be correctly added"
|
|
" or updated during scan: %s", p_input->psz_uri );
|
|
QuerySimple( p_ml, "UPDATE media SET directory_id=%d, timestamp=%d "
|
|
"WHERE id=%d",
|
|
p_itemobject->i_dir_id, p_itemobject->i_mtime,
|
|
GetMediaIdOfURI( p_ml, p_input->psz_uri ) );
|
|
vlc_event_detach( &p_input->event_manager, vlc_InputItemPreparsedChanged,
|
|
PreparseComplete, p_itemobject );
|
|
vlc_gc_decref( p_input );
|
|
free( p_itemobject->psz_uri );
|
|
}
|
|
|
|
/**
|
|
* @brief Scan files in a particular directory
|
|
*/
|
|
static void ScanFiles( monitoring_thread_t *p_mon, int i_dir_id,
|
|
bool b_recursive, stat_list_t *stparent )
|
|
{
|
|
int i_rows, i_cols, i_dir_content, i, i_mon_rows, i_mon_cols;
|
|
char **ppsz_monitored_files;
|
|
char **pp_results, *psz_dir;
|
|
char **pp_dir_content;
|
|
bool *pb_processed;
|
|
input_item_t *p_input;
|
|
struct stat s_stat;
|
|
media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
|
|
|
|
Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT uri AS directory_uri FROM directories WHERE id = '%d'",
|
|
i_dir_id );
|
|
if( i_rows < 1 )
|
|
{
|
|
msg_Dbg( p_mon, "query returned no directory for dir_id: %d (%s:%d)",
|
|
i_dir_id, __FILE__, __LINE__ );
|
|
return;
|
|
}
|
|
psz_dir = strdup( pp_results[1] );
|
|
FreeSQLResult( p_ml, pp_results );
|
|
|
|
struct stat_list_t stself;
|
|
|
|
if( vlc_stat( psz_dir, &stself.st ) == -1 )
|
|
{
|
|
msg_Err( p_ml, "Cannot stat `%s': %m", psz_dir );
|
|
free( psz_dir );
|
|
return;
|
|
}
|
|
#ifndef WIN32
|
|
for( stat_list_t *stats = stparent; stats != NULL; stats = stats->parent )
|
|
{
|
|
if( ( stself.st.st_ino == stats->st.st_ino ) &&
|
|
( stself.st.st_dev == stats->st.st_dev ) )
|
|
{
|
|
msg_Warn( p_ml, "Ignoring infinitely recursive directory `%s'",
|
|
psz_dir );
|
|
free( psz_dir );
|
|
return;
|
|
}
|
|
}
|
|
#else
|
|
/* Windows has st_dev (driver letter - 'A'), but it zeroes st_ino,
|
|
* so that the test above will always incorrectly succeed.
|
|
* Besides, Windows does not have dirfd(). */
|
|
#endif
|
|
stself.parent = stparent;
|
|
|
|
QuerySimple( p_ml, "UPDATE directories SET timestamp=%d WHERE id = %d",
|
|
stself.st.st_mtime, i_dir_id );
|
|
Query( p_ml, &ppsz_monitored_files, &i_mon_rows, &i_mon_cols,
|
|
"SELECT id AS media_id, timestamp AS media_ts, uri AS media_uri "
|
|
"FROM media WHERE directory_id = %d",
|
|
i_dir_id );
|
|
pb_processed = malloc(sizeof(bool) * i_mon_rows);
|
|
for( i = 0; i < i_mon_rows ; i++)
|
|
pb_processed[i] = false;
|
|
|
|
i_dir_content = vlc_scandir( psz_dir, &pp_dir_content, NULL, Sort );
|
|
if( i_dir_content == -1 )
|
|
{
|
|
msg_Err( p_mon, "Cannot read `%s': %m", psz_dir );
|
|
free( pb_processed );
|
|
free( psz_dir );
|
|
return;
|
|
}
|
|
else if( i_dir_content == 0 )
|
|
{
|
|
msg_Dbg( p_mon, "Nothing in directory `%s'", psz_dir );
|
|
free( pb_processed );
|
|
free( psz_dir );
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < i_dir_content; i++ )
|
|
{
|
|
const char *psz_entry = pp_dir_content[i];
|
|
|
|
if( psz_entry[0] != '.' )
|
|
{
|
|
/* 7 is the size of "file://" */
|
|
char psz_uri[strlen(psz_dir) + strlen(psz_entry) + 2 + 7];
|
|
sprintf( psz_uri, "%s/%s", psz_dir, psz_entry );
|
|
|
|
if( vlc_stat( psz_uri, &s_stat ) == -1 )
|
|
{
|
|
msg_Err( p_mon, "%s: %m", psz_uri );
|
|
free( pb_processed );
|
|
free( psz_dir );
|
|
return;
|
|
}
|
|
|
|
if( S_ISREG( s_stat.st_mode ) )
|
|
{
|
|
const char *psz_dot = strrchr( psz_uri, '.' );
|
|
if( psz_dot++ && *psz_dot )
|
|
{
|
|
int i_is_media = 0;
|
|
for( int a = 0; ppsz_MediaExtensions[a]; a++ )
|
|
{
|
|
if( !strcasecmp( psz_dot, ppsz_MediaExtensions[a] ) )
|
|
{
|
|
i_is_media = 1;
|
|
break;
|
|
}
|
|
}
|
|
if( !i_is_media )
|
|
{
|
|
msg_Dbg( p_mon, "ignoring file %s", psz_uri );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
char * psz_tmp = encode_URI_component( psz_uri );
|
|
char * psz_encoded_uri = ( char * )calloc( strlen( psz_tmp ) + 9, 1 );
|
|
strcpy( psz_encoded_uri, "file:///" );
|
|
strcat( psz_encoded_uri, psz_tmp );
|
|
free( psz_tmp );
|
|
|
|
/* Check if given media is already in DB and it has been updated */
|
|
bool b_skip = false;
|
|
bool b_update = false;
|
|
int j = 1;
|
|
for( j = 1; j <= i_mon_rows; j++ )
|
|
{
|
|
if( strcasecmp( ppsz_monitored_files[ j * i_mon_cols + 2 ],
|
|
psz_encoded_uri ) != 0 )
|
|
continue;
|
|
b_update = true;
|
|
pb_processed[ j - 1 ] = true;
|
|
if( atoi( ppsz_monitored_files[ j * i_mon_cols + 1 ] )
|
|
< s_stat.st_mtime )
|
|
{
|
|
b_skip = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
b_skip = true;
|
|
break;
|
|
}
|
|
}
|
|
msg_Dbg( p_ml , "Checking if %s is in DB. Found: %d", psz_encoded_uri,
|
|
b_skip? 1 : 0 );
|
|
if( b_skip )
|
|
continue;
|
|
|
|
p_input = input_item_New( psz_encoded_uri, psz_entry );
|
|
|
|
playlist_t* p_pl = pl_Get( p_mon );
|
|
preparsed_item_t* p_itemobject;
|
|
p_itemobject = malloc( sizeof( preparsed_item_t ) );
|
|
p_itemobject->i_dir_id = i_dir_id;
|
|
p_itemobject->psz_uri = psz_encoded_uri;
|
|
p_itemobject->i_mtime = s_stat.st_mtime;
|
|
p_itemobject->p_mon = p_mon;
|
|
p_itemobject->b_update = b_update;
|
|
p_itemobject->i_update_id = b_update ?
|
|
atoi( ppsz_monitored_files[ j * i_mon_cols + 0 ] ) : 0 ;
|
|
|
|
vlc_event_manager_t *p_em = &p_input->event_manager;
|
|
vlc_event_attach( p_em, vlc_InputItemPreparsedChanged,
|
|
PreparseComplete, p_itemobject );
|
|
playlist_PreparseEnqueue( p_pl, p_input );
|
|
}
|
|
else if( S_ISDIR( s_stat.st_mode ) && b_recursive )
|
|
{
|
|
Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT id AS directory_id FROM directories "
|
|
"WHERE uri=%Q", psz_uri );
|
|
FreeSQLResult( p_ml, pp_results );
|
|
|
|
if( i_rows <= 0 )
|
|
{
|
|
msg_Dbg( p_mon, "New directory `%s' in dir of id %d",
|
|
psz_uri, i_dir_id );
|
|
QuerySimple( p_ml,
|
|
"INSERT INTO directories (uri, timestamp, "
|
|
"recursive) VALUES(%Q, 0, 1)", psz_uri );
|
|
|
|
// We get the id of the directory we've just added
|
|
Query( p_ml, &pp_results, &i_rows, &i_cols,
|
|
"SELECT id AS directory_id FROM directories WHERE uri=%Q",
|
|
psz_uri );
|
|
if( i_rows <= 0 )
|
|
{
|
|
msg_Err( p_mon, "Directory `%s' was not sucessfully"
|
|
" added to the database", psz_uri );
|
|
FreeSQLResult( p_ml, pp_results );
|
|
continue;
|
|
}
|
|
|
|
ScanFiles( p_mon, atoi( pp_results[1] ), b_recursive,
|
|
&stself );
|
|
FreeSQLResult( p_ml, pp_results );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vlc_array_t* delete_ids = vlc_array_new();
|
|
for( i = 0; i < i_mon_rows; i++ )
|
|
{
|
|
if( !pb_processed[i] )
|
|
{
|
|
/* This file doesn't exist anymore. Let's...urm...delete it. */
|
|
ml_element_t* find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
|
|
find->criteria = ML_ID;
|
|
find->value.i = atoi( ppsz_monitored_files[ (i + 1) * i_mon_cols ] );
|
|
vlc_array_append( delete_ids, find );
|
|
}
|
|
}
|
|
|
|
/* Delete the unfound media */
|
|
if( Delete( p_ml, delete_ids ) != VLC_SUCCESS )
|
|
msg_Dbg( p_ml, "Something went wrong in multi delete" );
|
|
|
|
for( i = 0; i < vlc_array_count( delete_ids ); i++ )
|
|
{
|
|
free( vlc_array_item_at_index( delete_ids, i ) );
|
|
}
|
|
vlc_array_destroy( delete_ids );
|
|
|
|
FreeSQLResult( p_ml, ppsz_monitored_files );
|
|
for( i = 0; i < i_dir_content; i++ )
|
|
free( pp_dir_content[i] );
|
|
free( pp_dir_content );
|
|
free( psz_dir );
|
|
free( pb_processed );
|
|
}
|
|
|