Commit db662f04 authored by Srikanth Raju's avatar Srikanth Raju

Base Media Library Module files

parent c83d4a8d
...@@ -168,6 +168,18 @@ typedef enum vlc_dialog { ...@@ -168,6 +168,18 @@ typedef enum vlc_dialog {
/* Useful text messages shared by interfaces */ /* Useful text messages shared by interfaces */
#define INTF_ABOUT_MSG LICENSE_MSG #define INTF_ABOUT_MSG LICENSE_MSG
#define EXTENSIONS_AUDIO_CSV "a52", "aac", "ac3", "ape", "dts", "flac", "it", \
"m4a", "m4p", "mka", "mlp", "mod", "mp1", "mp2", "mp3",\
"oga", "ogg", "oma", "s3m", "spx" \
"wav", "wma", "wv", "xm"
#define EXTENSIONS_VIDEO_CSV "asf", "avi", "divx", "dv", "flv", "gxf", "iso", \
"m1v", "m2v", "m2t", "m2ts", "m4v", "mkv", "mov",\
"mp2", "mp4", "mpeg", "mpeg1", \
"mpeg2", "mpeg4", "mpg", "mts", "mxf", "nuv", \
"ogg", "ogm", "ogv", "ogx", "ps", \
"rec", "rm", "rmvb", "ts", "vob", "wmv"
#define EXTENSIONS_AUDIO \ #define EXTENSIONS_AUDIO \
"*.a52;" \ "*.a52;" \
"*.aac;" \ "*.aac;" \
......
/*****************************************************************************
* sql_media_library.c: SQL-based media library
*****************************************************************************
* 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.
*****************************************************************************/
#include "sql_media_library.h"
static const char* ppsz_AudioExtensions[] = { EXTENSIONS_AUDIO_CSV, NULL };
static const char* ppsz_VideoExtensions[] = { EXTENSIONS_VIDEO_CSV, NULL };
#define MEDIA_LIBRARY_PATH_TEXT N_( "Filename of the SQLite database" )
#define MEDIA_LIBRARY_PATH_LONGTEXT N_( "Path to the file containing " \
"the SQLite database" )
#define IGNORE_TEXT N_( "Ignored extensions in the media library" )
#define IGNORE_LONGTEXT N_( "Files with these extensions will not be added to"\
" the media library when scanning directories." )
#define RECURSIVE_TEXT N_( "Subdirectory recursive scanning" )
#define RECURSIVE_LONGTEXT N_( "When scanning a directory, scan also all its"\
" subdirectories." )
/*****************************************************************************
* Static functions
*****************************************************************************/
/* Module entry point and exit point */
static int load( vlc_object_t* );
static void unload( vlc_object_t* );
static int CreateInputItemFromMedia( media_library_t *p_ml,
input_item_t **pp_item,
ml_media_t *p_media );
struct ml_table_elt
{
int column_id;
char column_name[];
};
static const struct ml_table_elt ml_table_map[]=
{
{ ML_ALBUM_COVER, "album_cover" },
{ ML_ALBUM_ID, "album_id" },
{ ML_ALBUM, "album_title" },
{ ML_COMMENT, "comment" },
{ ML_COVER, "cover" },
{ ML_DIRECTORY, "directory_id" },
{ ML_DIRECTORY, "directory_id" },
{ ML_DISC_NUMBER, "disc" },
{ ML_DURATION, "duration" },
{ ML_EXTRA, "extra" },
{ ML_FILESIZE, "filesize" },
{ ML_FIRST_PLAYED, "first_played" },
{ ML_GENRE, "genre" },
{ ML_ID, "id" },
{ ML_IMPORT_TIME, "import_time" },
{ ML_LANGUAGE, "language" },
{ ML_LAST_PLAYED, "last_played" },
{ ML_LAST_SKIPPED, "last_skipped" },
{ ML_ORIGINAL_TITLE,"original_title" },
{ ML_PEOPLE_ID, "people_id" },
{ ML_PEOPLE, "people_name" },
{ ML_PEOPLE_ROLE, "people_role" },
{ ML_PLAYED_COUNT, "played_count" },
{ ML_PREVIEW, "preview" },
{ ML_SCORE, "score" },
{ ML_SKIPPED_COUNT, "skipped_count" },
{ ML_TITLE, "title" },
{ ML_TRACK_NUMBER, "track" },
{ ML_TYPE, "type" },
{ ML_URI, "uri" },
{ ML_VOTE, "vote" },
{ ML_YEAR, "year" }
};
/*****************************************************************************
* Module description
*****************************************************************************/
vlc_module_begin()
set_shortname( "Media Library" )
set_description( _( "Media Library based on a SQL based database" ) )
set_capability( "media-library", 1 )
set_callbacks( load, unload )
set_category( CAT_ADVANCED )
set_subcategory( SUBCAT_ADVANCED_MISC )
add_string( "ml-filename", "vlc-media-library.db", NULL,
MEDIA_LIBRARY_PATH_TEXT, MEDIA_LIBRARY_PATH_LONGTEXT, false )
add_string( "ml-username", "", NULL, N_( "Username for the database" ),
N_( "Username for the database" ), false )
add_string( "ml-password", "", NULL, N_( "Password for the database" ),
N_( "Password for the database" ), false )
add_integer( "ml-port", 0, NULL,
N_( "Port for the database" ), N_("Port for the database"), false )
add_bool( "ml-recursive-scan", true, NULL, RECURSIVE_TEXT,
RECURSIVE_LONGTEXT, false )
add_bool( "ml-auto-add", true, NULL, N_("Auto add new medias"),
N_( "Automatically add new medias to ML" ), false )
vlc_module_end()
/**
* @brief Load module
* @param obj Parent object
*/
static int load( vlc_object_t *obj )
{
msg_Dbg( obj, "loading media library module" );
media_library_t *p_ml = ( media_library_t * ) obj;
p_ml->p_sys = ( media_library_sys_t* )
calloc( 1, sizeof( media_library_sys_t ) );
if( !p_ml->p_sys )
return VLC_ENOMEM;
p_ml->functions.pf_Find = FindVa;
p_ml->functions.pf_FindAdv = FindAdv;
p_ml->functions.pf_Control = Control;
p_ml->functions.pf_InputItemFromMedia = GetInputItemFromMedia;
p_ml->functions.pf_Update = Update;
p_ml->functions.pf_Delete = Delete;
p_ml->functions.pf_GetMedia = GetMedia;
vlc_mutex_init( &p_ml->p_sys->lock );
/* Initialise Sql module */
InitDatabase( p_ml );
/* Initialise the media pool */
ARRAY_INIT( p_ml->p_sys->mediapool );
vlc_mutex_init( &p_ml->p_sys->pool_mutex );
/* Create variables system */
var_Create( p_ml, "media-added", VLC_VAR_INTEGER );
var_Create( p_ml, "media-deleted", VLC_VAR_INTEGER );
var_Create( p_ml, "media-meta-change", VLC_VAR_INTEGER );
/* Launching the directory monitoring thread */
monitoring_thread_t *p_mon =
vlc_object_create( p_ml, sizeof( monitoring_thread_t ) );
if( !p_mon )
{
vlc_mutex_destroy( &p_ml->p_sys->lock );
sql_Destroy( p_ml->p_sys->p_sql );
free( p_ml->p_sys );
return VLC_ENOMEM;
}
p_ml->p_sys->p_mon = p_mon;
p_mon->p_ml = p_ml;
if( vlc_clone( &p_mon->thread, RunMonitoringThread, p_mon,
VLC_THREAD_PRIORITY_LOW ) )
{
msg_Err( p_ml, "cannot spawn the media library monitoring thread" );
vlc_mutex_destroy( &p_ml->p_sys->lock );
sql_Destroy( p_ml->p_sys->p_sql );
free( p_ml->p_sys );
vlc_object_release( p_mon );
return VLC_EGENERIC;
}
/* Starting the watching system (starts a thread) */
watch_Init( p_ml );
msg_Dbg( p_ml, "Media library module loaded successfully" );
return VLC_SUCCESS;
}
/**
* @brief Unload module
*
* @param obj the media library object
* @return Nothing
*/
static void unload( vlc_object_t *obj )
{
media_library_t *p_ml = ( media_library_t* ) obj;
/* Stopping the watching system */
watch_Close( p_ml );
/* Stop the monitoring thread */
vlc_cancel( p_ml->p_sys->p_mon->thread );
vlc_join( p_ml->p_sys->p_mon->thread, NULL );
vlc_object_release( p_ml->p_sys->p_mon );
/* Destroy the variable */
var_Destroy( p_ml, "media-meta-change" );
var_Destroy( p_ml, "media-deleted" );
var_Destroy( p_ml, "media-added" );
/* Empty the media pool */
ml_media_t* item;
FOREACH_ARRAY( item, p_ml->p_sys->mediapool )
ml_gc_decref( item );
FOREACH_END()
vlc_mutex_destroy( &p_ml->p_sys->pool_mutex );
sql_Destroy( p_ml->p_sys->p_sql );
vlc_mutex_destroy( &p_ml->p_sys->lock );
free( p_ml->p_sys );
}
/**
* @brief Get results of an SQL-Query on the database (please : free the result)
*
* @param p_ml the media library object
* @param ppp_res char *** in which to store the table of results (allocated)
* @param pi_rows resulting row number in table
* @param pi_cols resulting column number in table
* @param psz_fmt query command with printf-like format enabled
* @param va_args format the command
* @return VLC_SUCCESS or a VLC error code
*/
int Query( media_library_t *p_ml,
char ***ppp_res, int *pi_rows, int *pi_cols,
const char *psz_fmt, ... )
{
va_list argp;
va_start( argp, psz_fmt );
int i_ret = QueryVa( p_ml, ppp_res, pi_rows, pi_cols, psz_fmt, argp );
va_end( argp );
return i_ret;
}
/**
* @brief Get results of an SQL-Query on the database (please : free the result)
*
* @param p_ml the media library object
* @param ppp_res char *** in which to store the table of results (allocated)
* @param pi_rows resulting row number in table
* @param pi_cols resulting column number in table
* @param psz_fmt query command with printf-like format enabled
* @param va_args format the command
* @return VLC_SUCCESS or a VLC error code
*/
int QueryVa( media_library_t *p_ml, char ***ppp_res,
int *pi_rows, int *pi_cols, const char *psz_fmt,
va_list argp )
{
assert( p_ml );
if( !ppp_res || !psz_fmt ) return VLC_EGENERIC;
char *psz_query = sql_VPrintf( p_ml->p_sys->p_sql, psz_fmt, argp );
if( !psz_query )
return VLC_ENOMEM;
int i_ret = sql_Query( p_ml->p_sys->p_sql, psz_query,
ppp_res, pi_rows, pi_cols );
free( psz_query );
return i_ret;
}
/**
* @brief Do a SQL-query without any data coming back
*
* @param p_ml the media library object
* @param psz_fmt query command with printf-like format enabled
* @param va_args format the command
* @return VLC_SUCCESS or a VLC error code
*/
int QuerySimple( media_library_t *p_ml,
const char *psz_fmt, ... )
{
va_list argp;
va_start( argp, psz_fmt );
int i_ret = QuerySimpleVa( p_ml, psz_fmt, argp );
va_end( argp );
return i_ret;
}
/**
* @brief Do a SQL-query without any data coming back
*
* @param p_ml the media library object
* @param psz_fmt query command with printf-like format enabled
* @param argp format the command
* @return VLC_SUCCESS or a VLC error code
*/
int QuerySimpleVa( media_library_t *p_ml,
const char *psz_fmt, va_list argp )
{
assert( p_ml );
int i_ret = VLC_SUCCESS;
int i_rows, i_cols;
char **pp_results = NULL;
i_ret = QueryVa( p_ml, &pp_results, &i_rows, &i_cols, psz_fmt, argp );
FreeSQLResult( p_ml, pp_results );
va_end( argp );
return i_ret;
}
/**
* @brief Transforms a string to a ml_result_t, with given type and id (as psz)
*
* @param res the result of the function
* @param psz string to transform into a result
* @param psz_id id as a string
* @param result_type type of the result
* @return ID or a VLC error code
*/
int StringToResult( ml_result_t *p_result, const char *psz,
const char *psz_id, ml_result_type_e result_type )
{
memset( &p_result->value, 0, sizeof( p_result->value ) );
p_result->id = psz_id ? atoi( psz_id ) : 0;
p_result->type = result_type;
switch( result_type )
{
case ML_TYPE_INT:
p_result->value.i = psz ? atoi( psz ) : 0;
break;
case ML_TYPE_TIME:
p_result->value.time = psz ? ( mtime_t ) atoi( psz )
: ( mtime_t ) 0LL;
break;
case ML_TYPE_PSZ:
p_result->value.psz = psz ? strdup( psz ) : NULL;
break;
case ML_TYPE_MEDIA:
default:
/* This is an error */
return VLC_EGENERIC;
}
return p_result->id;
}
/**
* @brief fills an ml_result_array_t with result of an SQL query
*
* @param p_ml the media library object
* @param p_media ml_result_array_t object to fill
* @param pp_results result of sql query
* @param i_rows row number
* @param i_cols column number
* @param result_type type of the result
* @return VLC_SUCCESS or a VLC error code
**/
int SQLToResultArray( media_library_t *p_ml, vlc_array_t *p_result_array,
char **pp_results, int i_rows, int i_cols,
ml_result_type_e result_type )
{
assert( p_ml );
if( !p_result_array )
return VLC_EGENERIC;
if( i_cols == 0 ) /* No result */
return VLC_SUCCESS;
if( i_cols < 0 )
{
msg_Err( p_ml, "negative number of columns in result ?" );
return VLC_EGENERIC;
}
if( i_cols == 1 )
{
for( int i = 1; i <= i_rows; i++ )
{
ml_result_t *res = ( ml_result_t* )
calloc( 1, sizeof( ml_result_t ) );
if( !res )
return VLC_ENOMEM;
StringToResult( res, pp_results[ i ], NULL, result_type );
vlc_array_append( p_result_array, res );
}
}
/* FIXME?: Assuming all double column results are id - result pairs */
else if( ( i_cols == 2 ) )
{
for( int i = 1; i <= i_rows; i++ )
{
ml_result_t *res = ( ml_result_t* )
calloc( 1, sizeof( ml_result_t ) );
if( !res )
return VLC_ENOMEM;
StringToResult( res, pp_results[ i * 2 + 1], pp_results[ i * 2 ],
result_type );
vlc_array_append( p_result_array, res );
}
}
else if( result_type == ML_TYPE_MEDIA )
{
return SQLToMediaArray( p_ml, p_result_array,
pp_results, i_rows, i_cols );
}
else
{
msg_Err( p_ml, "unable to convert SQL result to a ml_result_t array" );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/**
* @brief fills a vlc_array_t with results of an SQL query
* medias in ml_result_t
*
* @param p_ml the media library object
* @param p_array array to fill with ml_media_t elements (might be initialized)
* @param pp_results result of sql query
* @param i_rows row number
* @param i_cols column number
* @return VLC_SUCCESS or a VLC error code
* Warning: this returns VLC_EGENERIC if i_rows == 0 (empty result)
**/
int SQLToMediaArray( media_library_t *p_ml, vlc_array_t *p_result_array,
char **pp_results, int i_rows, int i_cols )
{
int i_ret = VLC_SUCCESS;
assert( p_ml );
#define res( i, j ) ( pp_results[ i * i_cols + j ] )
#define atoinull( a ) ( (a) ? atoi( a ) : 0 )
#define strdupnull( a ) ( (a) ? strdup( a ) : NULL )
if( i_rows == 0 )
return VLC_EGENERIC;
if( !p_result_array || !pp_results || i_rows < 0 || i_cols <= 0 )
{
msg_Warn( p_ml, "bad arguments (%s:%d)", __FILE__, __LINE__ );
return VLC_EGENERIC;
}
vlc_array_t* p_intermediate_array = vlc_array_new();
/* Analyze first row */
int *indexes = ( int* ) calloc( i_cols + 1, sizeof( int ) );
if( !indexes )
return VLC_ENOMEM;
const int count = sizeof( ml_table_map )/ sizeof( struct ml_table_elt );
for( int col = 0; col < i_cols; col++ )
{
//binary search
int low = 0, high = count - 1;
int answer = -1;
while( low <= high ) {
int mid = (low + high ) / 2;
char* mid_val = ml_table_map[mid].column_name;
int cmp = strcmp( mid_val, res( 0, col ) );
if( cmp > 0 )
low = mid + 1;
else if ( cmp < 0 )
high = mid - 1;
else
{
answer = mid; break;
}
}
if( answer == -1 )
msg_Warn( p_ml, "unknown column: %s", res( 0, col ) );
else
indexes[col] = ml_table_map[answer].column_id;
}
/* Read rows 1 to i_rows */
ml_media_t *p_media = NULL;
ml_result_t *p_result = NULL;
for( int row = 1; ( row <= i_rows ) && ( i_ret == VLC_SUCCESS ); row++ )
{
p_media = media_New( p_ml, 0, ML_MEDIA, false );
if( !p_media )
{
free( indexes );
return VLC_ENOMEM;
}
p_result = ( ml_result_t * ) calloc( 1, sizeof( ml_result_t ) );
if( !p_result )
{
ml_gc_decref( p_media );
free( indexes );
return VLC_ENOMEM;
}
char* psz_append_pname = NULL;
char* psz_append_prole = NULL;
int i_append_pid = 0;
#define SWITCH_INT( key, value ) case key: \
p_media-> value = atoinull( res( row, col ) );
#define SWITCH_PSZ( key, value ) case key: \
p_media-> value = strdupnull( res( row, col ) );
ml_LockMedia( p_media );
for( int col = 0; ( col < i_cols ) && ( i_ret == VLC_SUCCESS ); col++ )
{
switch( indexes[ col ] )
{
SWITCH_INT( ML_ALBUM_ID, i_album_id );
SWITCH_PSZ( ML_ALBUM, psz_album );
SWITCH_PSZ( ML_COMMENT, psz_comment );
SWITCH_INT( ML_DISC_NUMBER, i_disc_number );
SWITCH_INT( ML_DURATION, i_duration );
SWITCH_PSZ( ML_EXTRA, psz_extra );
SWITCH_INT( ML_FILESIZE, i_filesize );
SWITCH_INT( ML_FIRST_PLAYED, i_first_played );
SWITCH_PSZ( ML_GENRE, psz_genre);
SWITCH_INT( ML_IMPORT_TIME, i_import_time );
SWITCH_PSZ( ML_LANGUAGE, psz_language );
SWITCH_INT( ML_LAST_PLAYED, i_last_played );
SWITCH_INT( ML_LAST_SKIPPED, i_last_skipped );
SWITCH_PSZ( ML_ORIGINAL_TITLE, psz_orig_title );
SWITCH_INT( ML_PLAYED_COUNT, i_played_count );
SWITCH_PSZ( ML_PREVIEW, psz_preview );
SWITCH_INT( ML_SCORE, i_score );
SWITCH_INT( ML_SKIPPED_COUNT, i_skipped_count );
SWITCH_PSZ( ML_TITLE, psz_title );
SWITCH_INT( ML_TRACK_NUMBER, i_track_number );
SWITCH_INT( ML_TYPE, i_type );
SWITCH_INT( ML_VOTE, i_vote);
SWITCH_INT( ML_YEAR, i_year );
case ML_ALBUM_COVER:
/* See ML_COVER */
// Discard attachment://
if( !p_media->psz_cover || !*p_media->psz_cover
|| !strncmp( p_media->psz_cover, "attachment://", 13 ) )
{
free( p_media->psz_cover );
p_media->psz_cover = strdupnull( res( row, col ) );
}
break;
case ML_PEOPLE:
psz_append_pname = strdupnull( res( row, col ) );
break;
case ML_PEOPLE_ID:
i_append_pid = atoinull( res( row, col ) );
break;
case ML_PEOPLE_ROLE:
psz_append_prole = strdupnull( res( row, col ) );
break;
case ML_COVER:
/* See ML_ALBUM_COVER */
if( !p_media->psz_cover || !*p_media->psz_cover
|| !strncmp( p_media->psz_cover, "attachment://", 13 ) )
{
free( p_media->psz_cover );
p_media->psz_cover = strdupnull( res( row, col ) );
}
break;
case ML_ID:
p_media->i_id = atoinull( res( row, col ) );
if( p_media->i_id <= 0 )
msg_Warn( p_ml, "entry with id null or inferior to zero" );
break;
case ML_URI:
p_media->psz_uri = strdupnull( res( row, col ) );
if( !p_media->psz_uri )
msg_Warn( p_ml, "entry without uri" );
break;
case ML_DIRECTORY:
break; // The column directory_id is'nt part of the media model
default:
msg_Warn( p_ml, "unknown element, row %d column %d (of %d) - %s - %s",
row, col, i_cols, res( 0 , col ), res( row, col ) );
break;
}
}
#undef SWITCH_INT
#undef SWITCH_PSZ
int i_appendrow;
ml_result_t* p_append = NULL;
for( i_appendrow = 0; i_appendrow < vlc_array_count( p_intermediate_array ); i_appendrow++ )
{
p_append = ( ml_result_t* )
vlc_array_item_at_index( p_intermediate_array, i_appendrow );
if( p_append->id == p_media->i_id )
break;
}
if( i_appendrow == vlc_array_count( p_intermediate_array ) )
{
p_result->id = p_media->i_id;
p_result->type = ML_TYPE_MEDIA;
p_result->value.p_media = p_media;
if( psz_append_pname && i_append_pid && psz_append_prole )
ml_CreateAppendPersonAdv( &(p_result->value.p_media->p_people),
psz_append_prole, psz_append_pname, i_append_pid );
vlc_array_append( p_intermediate_array, p_result );
ml_UnlockMedia( p_media );
}
else /* This is a repeat row and the people need to be put together */
{
ml_LockMedia( p_append->value.p_media );
if( psz_append_pname && i_append_pid && psz_append_prole )
ml_CreateAppendPersonAdv( &(p_append->value.p_media->p_people),
psz_append_prole, psz_append_pname, i_append_pid );
ml_UnlockMedia( p_append->value.p_media );
ml_UnlockMedia( p_media );
ml_gc_decref( p_media );
}
FREENULL( psz_append_prole );
FREENULL( psz_append_pname );
i_append_pid = 0;
}
p_media = NULL;
free( indexes );
/* Now check if these medias are already on the pool, and sync */
for( int i = 0; i < vlc_array_count( p_intermediate_array ); i++ )
{
p_result =
( ml_result_t* )vlc_array_item_at_index( p_intermediate_array, i );
p_media = p_result->value.p_media;
ml_media_t* p_poolmedia = pool_GetMedia( p_ml, p_result->id );
/* TODO: Pool_syncMedia might be cleaner? */
p_result = ( ml_result_t* ) calloc( 1, sizeof( ml_result_t * ) );
if( !p_result )
goto quit_sqlmediaarray;
if( p_poolmedia )
{
/* TODO: This might cause some weird stuff to occur w/ GC? */
ml_CopyMedia( p_poolmedia, p_media );
p_result->id = p_poolmedia->i_id;
p_result->type = ML_TYPE_MEDIA;
p_result->value.p_media = p_poolmedia;
vlc_array_append( p_result_array, p_result );
}
else
{
i_ret = pool_InsertMedia( p_ml, p_media, false );
if( i_ret == VLC_SUCCESS )
{
ml_gc_incref( p_media );
p_result->id = p_media->i_id;
p_result->type = ML_TYPE_MEDIA;
p_result->value.p_media = p_media;
vlc_array_append( p_result_array, p_result );
}
}
}
#undef strdupnull
#undef atoinull
#undef res
quit_sqlmediaarray:
for( int k = 0; k < vlc_array_count( p_intermediate_array ); k++ )
{
ml_result_t* temp = ((ml_result_t*)vlc_array_item_at_index( p_intermediate_array, k ));
ml_FreeResult( temp );
}
vlc_array_destroy( p_intermediate_array );
return i_ret;
}
/**
* @brief Returns (unique) ID of media with specified URI
*
* @param p_ml the media library object
* @param psz_uri URI to look for
* @return i_id: (first) ID found, VLC_EGENERIC in case of error
* NOTE: Normally, there should not be more than one ID with one URI
*/
int GetMediaIdOfURI( media_library_t *p_ml, const char *psz_uri )
{
int i_ret = VLC_EGENERIC;
vlc_array_t *p_array = vlc_array_new();
i_ret = Find( p_ml, p_array, ML_ID, ML_URI, psz_uri, ML_LIMIT, 1, ML_END );
if( ( i_ret == VLC_SUCCESS )
&& ( vlc_array_count( p_array ) > 0 )
&& vlc_array_item_at_index( p_array, 0 ) )
{
i_ret = ( (ml_result_t*)vlc_array_item_at_index( p_array, 0 ) )
->value.i;
}
else
{
i_ret = VLC_EGENERIC;
}
vlc_array_destroy( p_array );
return i_ret;
}
/**
* @brief Control function for media library
*
* @param p_ml Media library handle
* @param i_query query type
* @param args query arguments
* @return VLC_SUCCESS if ok
*/
int Control( media_library_t *p_ml, int i_query, va_list args )
{
switch( i_query )
{
case ML_ADD_INPUT_ITEM:
{
input_item_t *p_item = (input_item_t *)va_arg( args, input_item_t * );
return AddInputItem( p_ml, p_item );
}
case ML_ADD_PLAYLIST_ITEM:
{
playlist_item_t *p_item = (playlist_item_t *)va_arg( args, playlist_item_t * );
return AddPlaylistItem( p_ml, p_item );
}
case ML_ADD_MONITORED:
{
char *psz_dir = (char *)va_arg( args, char * );
return AddDirToMonitor( p_ml, psz_dir );
}
case ML_GET_MONITORED:
{
vlc_array_t *p_array = (vlc_array_t *)va_arg( args, vlc_array_t * );
return ListMonitoredDirs( p_ml, p_array );
}
case ML_DEL_MONITORED:
{
char *psz_dir = (char *)va_arg( args, char * );
return RemoveDirToMonitor( p_ml, psz_dir );
}
default:
return VLC_EGENERIC;
}
}
/**
* @brief Create a new (empty) database. The database might be initialized
*
* @param p_ml This ML
* @return VLC_SUCCESS or VLC_EGENERIC
* @note This function is transactional
*/
int CreateEmptyDatabase( media_library_t *p_ml )
{
assert( p_ml );
int i_ret = VLC_SUCCESS;
msg_Dbg( p_ml, "creating a new (empty) database" );
Begin( p_ml );
/* Albums */
i_ret= QuerySimple( p_ml,
"CREATE TABLE album ( "
"id INTEGER PRIMARY KEY,"
"album_artist_id INTEGER,"
"title VARCHAR(1024),"
"cover VARCHAR(1024) )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Add "unknown" entry to albums */
i_ret = QuerySimple( p_ml,
"INSERT INTO album ( id, title, cover, album_artist_id ) "
"VALUES ( 0, 'Unknown', '', 0 )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Main media table */
i_ret= QuerySimple( p_ml,
"CREATE TABLE media ( "
"id INTEGER PRIMARY KEY,"
"timestamp INTEGER," /* File timestamp */
"uri VARCHAR(1024),"
"type INTEGER,"
"title VARCHAR(1024),"
"original_title VARCHAR(1024),"
"album_id INTEGER,"
"cover VARCHAR(1024),"
"preview VARCHAR(1024)," /* Video preview */
"track INTEGER," /* Track number */
"disc INTEGER," /* Disc number */
"year INTEGER,"
"genre VARCHAR(1024),"
"vote INTEGER," /* Rating/Stars */
"score INTEGER," /* ML score/rating */
"comment VARCHAR(1024)," /* Comment */
"filesize INTEGER,"
/* Dates and times */
"duration INTEGER," /* Length of media */
"played_count INTEGER,"
"last_played DATE,"
"first_played DATE,"
"import_time DATE,"
"skipped_count INTEGER,"
"last_skipped DATE,"
"directory_id INTEGER,"
"CONSTRAINT associated_album FOREIGN KEY(album_id) "
"REFERENCES album(id) ON DELETE SET DEFAULT ON UPDATE RESTRICT)" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* People */
i_ret = QuerySimple( p_ml,
"CREATE TABLE people ( "
"id INTEGER PRIMARY KEY,"
"name VARCHAR(1024) ,"
"role VARCHAR(1024) )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Media to people */
i_ret = QuerySimple( p_ml,
"CREATE TABLE media_to_people ( "
"media_id INTEGER, "
"people_id INTEGER, "
"PRIMARY KEY( media_id, people_id ), "
"CONSTRAINT associated_people FOREIGN KEY(people_id) "
"REFERENCES people(id) ON DELETE SET DEFAULT ON UPDATE RESTRICT, "
"CONSTRAINT associated_media FOREIGN KEY(media_id) "
"REFERENCES media(id) ON DELETE CASCADE ON UPDATE RESTRICT )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Add "unknown" entry to people */
i_ret = QuerySimple( p_ml,
"INSERT INTO people ( id, name, role ) "
"VALUES ( 0, 'Unknown', NULL )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* recursive is set to 1 if the directory is added to the database
by recursion and 0 if not */
i_ret = QuerySimple( p_ml,
"CREATE TABLE directories ( "
"id INTEGER PRIMARY KEY,"
"uri VARCHAR(1024),"
"timestamp INTEGER,"
"recursive INTEGER )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Create information table
* This table should have one row and the version number is the version
* of the database
* Other information may be stored here at later stages */
i_ret = QuerySimple( p_ml,
"CREATE TABLE information ( "
"version INTEGER PRIMARY KEY )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Insert current DB version */
i_ret = QuerySimple( p_ml,
"INSERT INTO information ( version ) "
"VALUES ( %d )", ML_DBVERSION );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Text data: song lyrics or subtitles */
i_ret = QuerySimple( p_ml,
"CREATE TABLE extra ( "
"id INTEGER PRIMARY KEY,"
"extra TEXT,"
"language VARCHAR(256),"
"bitrate INTEGER,"
"samplerate INTEGER,"
"bpm INTEGER )" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
/* Emulating foreign keys with triggers */
/* Warning: Lots of SQL */
if( !strcmp( module_get_name( p_ml->p_sys->p_sql->p_module, false ),
"SQLite" ) )
{
i_ret = QuerySimple( p_ml,
"\nCREATE TRIGGER genfkey1_insert_referencing BEFORE INSERT ON \"media\" WHEN\n"
" new.\"album_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"album\" WHERE new.\"album_id\" == \"id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey1_insert_referencing failed. Cannot insert album_id into media. Album did not exist');\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey1_update_referencing BEFORE\n"
" UPDATE OF album_id ON \"media\" WHEN \n"
" new.\"album_id\" IS NOT NULL AND \n"
" NOT EXISTS (SELECT 1 FROM \"album\" WHERE new.\"album_id\" == \"id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey1_update_referencing failed. Cannot update album_id in media. Album did not exist');\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey1_delete_referenced BEFORE DELETE ON \"album\" WHEN\n"
" EXISTS (SELECT 1 FROM \"media\" WHERE old.\"id\" == \"album_id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey1_delete_referenced failed. Cannot delete album, media still exist');\n"
"END;\n"
"\n"
"\n"
"CREATE TRIGGER genfkey1_update_referenced AFTER\n"
" UPDATE OF id ON \"album\" WHEN \n"
" EXISTS (SELECT 1 FROM \"media\" WHERE old.\"id\" == \"album_id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey1_update_referenced failed. Cannot change album id in album, media still exist');\n"
"END;\n"
"\n"
"\n"
"CREATE TRIGGER genfkey2_insert_referencing BEFORE INSERT ON \"media_to_people\" WHEN \n"
" new.\"media_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"media\" WHERE new.\"media_id\" == \"id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey2_insert_referencing failed. Cannot insert into media_to_people, that media does not exist');\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey2_update_referencing BEFORE\n"
" UPDATE OF media_id ON \"media_to_people\" WHEN \n"
" new.\"media_id\" IS NOT NULL AND \n"
" NOT EXISTS (SELECT 1 FROM \"media\" WHERE new.\"media_id\" == \"id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey2_update_referencing failed. Cannot update media_to_people, that media does not exist');\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey2_delete_referenced BEFORE DELETE ON \"media\" WHEN\n"
" EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"media_id\")\n"
"BEGIN\n"
" DELETE FROM \"media_to_people\" WHERE \"media_id\" = old.\"id\";\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey2_update_referenced AFTER\n"
" UPDATE OF id ON \"media\" WHEN \n"
" EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"media_id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey2_update_referenced failed. Cannot update media id, refs still exist in media_to_people');\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey3_insert_referencing BEFORE INSERT ON \"media_to_people\" WHEN \n"
" new.\"people_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"people\" WHERE new.\"people_id\" == \"id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey3_insert_referencing failed. Cannot insert into media_to_people, people does not exist');\n"
"END;\n"
"CREATE TRIGGER genfkey3_update_referencing BEFORE\n"
" UPDATE OF people_id ON \"media_to_people\" WHEN \n"
" new.\"people_id\" IS NOT NULL AND \n"
" NOT EXISTS (SELECT 1 FROM \"people\" WHERE new.\"people_id\" == \"id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey3_update_referencing failed. Cannot update media_to_people, people does not exist');\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey3_delete_referenced BEFORE DELETE ON \"people\" WHEN\n"
" EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"people_id\")\n"
"BEGIN\n"
" UPDATE media_to_people SET people_id = 0 WHERE people_id == old.\"id\";\n"
"END;\n"
"\n"
"CREATE TRIGGER genfkey3_update_referenced AFTER\n"
" UPDATE OF id ON \"people\" WHEN \n"
" EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"people_id\")\n"
"BEGIN\n"
" SELECT RAISE(ABORT, 'constraint genfkey3_update_referenced failed. Cannot update people_id, people does not exist');\n"
"END;\n"
"\n"
"CREATE TRIGGER keep_people_clean AFTER \n"
" DELETE ON \"media_to_people\"\n"
" WHEN NOT EXISTS( SELECT 1 from \"media_to_people\" WHERE old.\"people_id\" == \"people_id\" )\n"
"BEGIN\n"
" DELETE FROM people WHERE people.id = old.\"people_id\" AND people.id != 0;\n"
"END;\n"
"\n"
"CREATE TRIGGER keep_album_clean AFTER\n"
" DELETE ON \"media\"\n"
" WHEN NOT EXISTS( SELECT 1 FROM \"media\" WHERE old.\"album_id\" == \"album_id\" )\n"
"BEGIN\n"
" DELETE FROM album WHERE album.id = old.\"album_id\" AND album.id != 0;\n"
"END;" );
if( i_ret != VLC_SUCCESS )
goto quit_createemptydatabase;
}
quit_createemptydatabase:
if( i_ret == VLC_SUCCESS )
Commit( p_ml );
else
Rollback( p_ml );
return VLC_SUCCESS;
}
/**
* @brief Initiates database (create the database and the tables if needed)
*
* @param p_ml This ML
* @return VLC_SUCCESS or an error code
*/
int InitDatabase( media_library_t *p_ml )
{
assert( p_ml );
msg_Dbg( p_ml, "initializing database" );
/* Select database name */
char *psz_dbhost = NULL, *psz_user = NULL, *psz_pass = NULL;
int i_port = 0;
psz_dbhost = config_GetPsz( p_ml, "ml-filename" );
psz_user = config_GetPsz( p_ml, "ml-username" );
psz_pass = config_GetPsz( p_ml, "ml-password" );
i_port = config_GetInt( p_ml, "ml-port" );
/* Let's consider that a filename with a DIR_SEP is a full URL */
if( strchr( psz_dbhost, DIR_SEP_CHAR ) == NULL )
{
char *psz_datadir = config_GetUserDir( VLC_DATA_DIR );
char *psz_tmp = psz_dbhost;
if( asprintf( &psz_dbhost, "%s" DIR_SEP "%s",
psz_datadir, psz_tmp ) == -1 )
{
free( psz_datadir );
free( psz_tmp );
return VLC_ENOMEM;
}
free( psz_datadir );
free( psz_tmp );
}
p_ml->p_sys->p_sql = sql_Create( p_ml, NULL, psz_dbhost, i_port, psz_user,
psz_pass );
if( !p_ml->p_sys->p_sql )
{
vlc_mutex_destroy( &p_ml->p_sys->lock );
free( p_ml->p_sys );
return VLC_EGENERIC;
}
/* Let's check if tables exist */
int i_version = GetDatabaseVersion( p_ml );
if( i_version <= 0 )
CreateEmptyDatabase( p_ml );
else if( i_version != ML_DBVERSION )
return VLC_EGENERIC;
/**
* The below code ensures that correct code is written
* when database versions are changed
*/
#if ML_DBVERSION != 1
#error "ML versioning code needs to be updated. Is this done correctly?"
#endif
msg_Dbg( p_ml, "ML initialized" );
return VLC_SUCCESS;
}
/**
* @brief Gets the current version number from the database
*
* @param p_ml media library object
* @return version number of the current db. <= 0 on error.
*/
int GetDatabaseVersion( media_library_t *p_ml )
{
int i_rows, i_cols;
char **pp_results;
int i_return;
i_return = Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT version FROM information ORDER BY version DESC LIMIT 1" );
if( i_return != VLC_SUCCESS )
i_return = -1;
else
i_return = atoi( pp_results[ 1 ] );
FreeSQLResult( p_ml, pp_results );
return i_return;
}
/**
* @brief Object constructor for ml_media_t
* @param p_ml The media library object
* @param id If 0, this item isn't in database. If non zero, it is and
* it will be a singleton
* @param select Type of object
* @param reload Whether to reload from database
*/
ml_media_t* GetMedia( media_library_t* p_ml, int id,
ml_select_e select, bool reload )
{
assert( id > 0 );
assert( select == ML_MEDIA || select == ML_MEDIA_SPARSE );
int i_ret = VLC_SUCCESS;
ml_media_t* p_media = NULL;
if( !reload )
{
p_media = pool_GetMedia( p_ml, id );
if( !p_media )
reload = true;
else
{
ml_LockMedia( p_media );
if( p_media->b_sparse == true && select == ML_MEDIA )
reload = true;
/* Utilise ML_MEDIA_EXTRA load? TODO */
ml_UnlockMedia( p_media );
ml_gc_incref( p_media );
}
}
else
{
vlc_array_t *p_array = vlc_array_new();
i_ret = ml_Find( p_ml, p_array, select, ML_ID, id );
assert( vlc_array_count( p_array ) == 1 );
if( ( i_ret == VLC_SUCCESS )
&& ( vlc_array_count( p_array ) > 0 )
&& vlc_array_item_at_index( p_array, 0 ) )
{
p_media = ((ml_result_t*)vlc_array_item_at_index( p_array, 0 ))->value.p_media;
ml_gc_incref( p_media );
ml_FreeResult( vlc_array_item_at_index( p_array, 0 ) );
}
vlc_array_destroy( p_array );
if( select == ML_MEDIA )
p_media->b_sparse = false;
else
p_media->b_sparse = true;
}
return p_media;
}
/**
* @brief Create an input item from media (given its ID)
*
* @param p_ml This media_library_t object
* @param i_media Media ID
* @return input_item_t* created
*
* @note This is a public function (pf_InputItemFromMedia)
* The input_item will have a refcount at 2 (1 for the ML, 1 for you)
*/
input_item_t* GetInputItemFromMedia( media_library_t *p_ml, int i_media )
{
input_item_t *p_item = NULL;
p_item = watch_get_itemOfMediaId( p_ml, i_media );
if( !p_item )
{
ml_media_t* p_media = media_New( p_ml, i_media, ML_MEDIA, true );
if( p_media == NULL )
return NULL;
CreateInputItemFromMedia( p_ml, &p_item, p_media );
watch_add_Item( p_ml, p_item, p_media );
ml_gc_decref( p_media );
}
return p_item;
}
/**
* @brief Copy an input_item_t to a ml_media_t
* @param p_media Destination
* @param p_item Source
* @note Media ID will not be set! This function is threadsafe. Leaves
* unsyncable items alone
*/
void CopyInputItemToMedia( ml_media_t *p_media, input_item_t *p_item )
{
ml_LockMedia( p_media );
#if 0
// unused meta :
input_item_GetCopyright( item )
input_item_GetRating( item ) /* TODO */
input_item_GetGetting( item )
input_item_GetNowPlaying( item )
input_item_GetTrackID( item )
input_item_GetSetting( item )
#endif
p_media->psz_title = input_item_GetTitle ( p_item );
p_media->psz_uri = input_item_GetURL ( p_item );
if( !p_media->psz_uri )
p_media->psz_uri = strdup( p_item->psz_uri );
p_media->psz_album = input_item_GetAlbum ( p_item );
p_media->psz_cover = input_item_GetArtURL ( p_item );
p_media->psz_genre = input_item_GetGenre ( p_item );
p_media->psz_language = input_item_GetLanguage ( p_item );
p_media->psz_comment = input_item_GetDescription ( p_item );
char *psz_track = input_item_GetTrackNum ( p_item );
p_media->i_track_number = psz_track ? atoi( psz_track ) : 0;
free( psz_track );
char *psz_date = input_item_GetDate( p_item );
p_media->i_year = psz_date ? atoi( psz_date ) : 0;
free( psz_date );
p_media->i_duration = p_item->i_duration;
/* People */
char *psz_tmp = input_item_GetArtist( p_item );
if( psz_tmp )
ml_CreateAppendPersonAdv( &p_media->p_people, ML_PERSON_ARTIST,
psz_tmp, 0 );
free( psz_tmp );
psz_tmp = input_item_GetPublisher( p_item );
if( psz_tmp )
ml_CreateAppendPersonAdv( &p_media->p_people, ML_PERSON_PUBLISHER,
psz_tmp, 0 );
free( psz_tmp );
psz_tmp = input_item_GetEncodedBy( p_item );
if( psz_tmp )
ml_CreateAppendPersonAdv( &p_media->p_people, ML_PERSON_ENCODER,
psz_tmp, 0 );
free( psz_tmp );
/* Determine input type: audio, video, stream */
/* First read input type */
switch( p_item->i_type )
{
case ITEM_TYPE_FILE:
p_media->i_type |= 0;
break;
case ITEM_TYPE_DISC:
case ITEM_TYPE_CARD:
p_media->i_type |= ML_REMOVABLE;
break;
case ITEM_TYPE_CDDA:
case ITEM_TYPE_NET:
p_media->i_type |= ML_STREAM;
break;
case ITEM_TYPE_PLAYLIST:
case ITEM_TYPE_NODE:
case ITEM_TYPE_DIRECTORY:
p_media->i_type |= ML_NODE;
break;
case ITEM_TYPE_NUMBER:
case ITEM_TYPE_UNKNOWN:
default:
p_media->i_type |= ML_UNKNOWN;
break;
}
/* Then try to guess if this is a video or not */
/* Check file extension, and guess if this is a video or an audio media
Note: this test is not very good, but it's OK for normal files */
char *psz_ext = strrchr( p_item->psz_uri, '.' );
if( psz_ext && strlen( psz_ext ) < 5 )
{
bool b_ok = false;
psz_ext++;
for( unsigned i = 0; ppsz_AudioExtensions[i]; i++ )
{
if( strcasecmp( psz_ext, ppsz_AudioExtensions[i] ) == 0 )
{
p_media->i_type |= ML_AUDIO;
b_ok = true;
break;
}
}
if( !b_ok )
{
for( unsigned i = 0; ppsz_VideoExtensions[i]; i++ )
{
if( strcasecmp( psz_ext, ppsz_VideoExtensions[i] ) == 0 )
{
p_media->i_type |= ML_VIDEO;
break;
}
}
}
}
ml_UnlockMedia( p_media );
}
/**
* @brief Copy a ml_media_t to an input_item_t
* @param p_item Destination
* @param p_media Source
*/
void CopyMediaToInputItem( input_item_t *p_item, ml_media_t *p_media )
{
ml_LockMedia( p_media );
if( p_media->psz_title && *p_media->psz_title )
input_item_SetTitle( p_item, p_media->psz_title );
if( p_media->psz_uri && *p_media->psz_uri )
input_item_SetURL( p_item, p_media->psz_uri );
if( p_media->psz_album && *p_media->psz_album )
input_item_SetAlbum( p_item, p_media->psz_album );
if( p_media->psz_cover && *p_media->psz_cover )
input_item_SetArtURL( p_item, p_media->psz_cover );
if( p_media->psz_genre && *p_media->psz_genre )
input_item_SetGenre( p_item, p_media->psz_genre );
if( p_media->psz_language && *p_media->psz_language )
input_item_SetLanguage( p_item, p_media->psz_language );
if( p_media->psz_comment && *p_media->psz_comment )
input_item_SetDescription( p_item, p_media->psz_comment );
if( p_media->i_track_number )
{
char *psz_track;
if( asprintf( &psz_track, "%d", p_media->i_track_number ) != -1 )
input_item_SetTrackNum( p_item, psz_track );
free( psz_track );
}
if( p_media->i_year )
{
char *psz_date;
if( asprintf( &psz_date, "%d", p_media->i_year ) != -1 )
input_item_SetDate( p_item, psz_date );
free( psz_date );
}
p_item->i_duration = p_media->i_duration;
ml_person_t *person = p_media->p_people;
while( person )
{
if( !strcmp( person->psz_role, ML_PERSON_ARTIST ) )
input_item_SetArtist( p_item, person->psz_name );
else if( !strcmp( person->psz_role, ML_PERSON_PUBLISHER ) )
input_item_SetPublisher( p_item, person->psz_name );
else if( !strcmp( person->psz_role, ML_PERSON_ENCODER ) )
input_item_SetEncodedBy( p_item, person->psz_name );
person = person->p_next;
}
ml_UnlockMedia( p_media );
}
/**
* @brief Copy a ml_media_t to an input_item_t
* @param p_ml The Media Library object
* @param pp_item A pointer to a new input_item (return value)
* @param p_media The media to copy as an input item
* @note This function is threadsafe
*/
static int CreateInputItemFromMedia( media_library_t *p_ml,
input_item_t **pp_item,
ml_media_t *p_media )
{
playlist_t *p_pl = pl_Get( p_ml );
*pp_item = input_item_New( VLC_OBJECT( p_pl ),
p_media->psz_uri,
p_media->psz_title );
/* ITEM_TYPE_FILE ); */
if( !*pp_item )
return VLC_EGENERIC;
CopyMediaToInputItem( *pp_item, p_media );
return VLC_SUCCESS;
}
/**
* @brief Find the media_id associated to an input item
* @param p_ml This
* @param p_item Input item to look for
* @return Media ID or <= 0 if not found
*/
int GetMediaIdOfInputItem( media_library_t *p_ml, input_item_t *p_item )
{
int i_media_id = watch_get_mediaIdOfItem( p_ml, p_item );
if( i_media_id <= 0 )
{
i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
}
return i_media_id;
}
/*****************************************************************************
* sql_media_library.h : Media Library Interface
*****************************************************************************
* 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.
*****************************************************************************/
#ifndef SQL_MEDIA_LIBRARY_H
#define SQL_MEDIA_LIBRARY_H
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc/vlc.h>
#include <vlc_sql.h>
#include <vlc_media_library.h>
#include <vlc_playlist.h>
#include <vlc_input.h>
#include <vlc_arrays.h>
#include <vlc_charset.h>
#include <vlc_plugin.h>
#include <vlc_interface.h>
#include <vlc_modules.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include "item_list.h"
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
/*****************************************************************************
* Static parameters
*****************************************************************************/
#define THREAD_SLEEP_DELAY 2 /* Time between two calls to item_list_loop */
#define MONITORING_DELAY 30 /* Media library updates interval */
#define ITEM_LOOP_UPDATE 1 /* An item is updated after 1 loop */
#define ITEM_LOOP_MAX_AGE 10 /* An item is deleted after 10 loops */
#define ML_DBVERSION 1 /* The current version of the database */
#define ML_MEDIAPOOL_HASH_LENGTH 100 /* The length of the media pool hash */
/*****************************************************************************
* Structures and types definitions
*****************************************************************************/
typedef struct monitoring_thread_t monitoring_thread_t;
typedef struct ml_poolobject_t ml_poolobject_t;
struct ml_poolobject_t
{
ml_media_t* p_media;
ml_poolobject_t* p_next;
};
struct media_library_sys_t
{
/* Lock on the ML object */
vlc_mutex_t lock;
/* SQL object */
sql_t *p_sql;
/* Monitoring thread */
monitoring_thread_t *p_mon;
/* Watch thread */
watch_thread_t *p_watch;
/* Holds all medias */
DECL_ARRAY( ml_media_t* ) mediapool;
ml_poolobject_t* p_mediapool[ ML_MEDIAPOOL_HASH_LENGTH ];
vlc_mutex_t pool_mutex;
/* Info on update/collection rebuilding */
bool b_updating;
bool b_rebuilding;
};
/* Directory Monitoring thread */
struct monitoring_thread_t
{
VLC_COMMON_MEMBERS;
vlc_cond_t wait;
vlc_mutex_t lock;
vlc_thread_t thread;
media_library_t *p_ml;
};
/* Media status Watching thread */
struct watch_thread_t
{
VLC_COMMON_MEMBERS;
media_library_t *p_ml;
vlc_cond_t cond;
vlc_mutex_t lock;
/* Input items watched */
struct item_list_t* p_hlist[ ML_ITEMLIST_HASH_LENGTH ];
vlc_mutex_t list_mutex;
/* List of items to check */
input_item_t** item_append_queue;
vlc_mutex_t item_append_queue_lock;
int item_append_queue_count;
};
/*****************************************************************************
* Function headers
*****************************************************************************/
/* General functions */
int CreateEmptyDatabase( media_library_t *p_ml );
int InitDatabase( media_library_t *p_ml );
/* Module Control */
int Control( media_library_t *p_ml,
int i_query,
va_list args );
/* Add functions */
int AddMedia( media_library_t *p_ml,
ml_media_t *p_media );
int AddAlbum( media_library_t *p_ml, const char *psz_title,
const char *psz_cover, const int i_album_artist );
int AddPeople( media_library_t *p_ml,
const char *psz_name,
const char *psz_role );
int AddPlaylistItem( media_library_t *p_ml,
playlist_item_t *p_playlist_item );
int AddInputItem( media_library_t *p_ml,
input_item_t *p_input );
/* Create and Copy functions */
ml_media_t* GetMedia( media_library_t* p_ml, int id,
ml_select_e select, bool reload );
input_item_t* GetInputItemFromMedia( media_library_t *p_ml,
int i_media );
void CopyInputItemToMedia( ml_media_t *p_media,
input_item_t *p_item );
void CopyMediaToInputItem( input_item_t *p_item,
ml_media_t *p_media );
/* Get functions */
int GetDatabaseVersion( media_library_t *p_ml );
int GetMediaIdOfInputItem( media_library_t *p_ml,
input_item_t *p_item );
int GetMediaIdOfURI( media_library_t *p_ml,
const char *psz_uri );
/* Search in the database */
int BuildSelectVa( media_library_t *p_ml,
char **ppsz_query,
ml_result_type_e *p_result_type,
va_list criterias );
int BuildSelect( media_library_t *p_ml,
char **ppsz_query,
ml_result_type_e *p_result_type,
const char *psz_selected_type_lvalue,
ml_select_e selected_type,
ml_ftree_t *tree );
int Find( media_library_t *p_ml,
vlc_array_t *results,
... );
int FindVa( media_library_t *p_ml,
vlc_array_t *results,
va_list criterias );
int FindAdv( media_library_t *p_ml,
vlc_array_t *results,
ml_select_e selected_type,
const char* psz_lvalue,
ml_ftree_t *tree );
/* Update the database */
int Update( media_library_t *p_ml,
ml_select_e selected_type,
const char* psz_lvalue,
ml_ftree_t *where,
vlc_array_t *changes );
int BuildUpdate( media_library_t *p_ml,
char **ppsz_query,
char **ppsz_id_query,
const char *psz_lvalue,
ml_select_e selected_type,
ml_ftree_t* where,
vlc_array_t *changes );
int UpdateMedia( media_library_t *p_ml,
ml_media_t *p_media );
int SetArtCover( media_library_t *p_ml,
int i_album_id,
const char *psz_cover );
/* Delete medias in the database */
int Delete( media_library_t *p_ml, vlc_array_t *p_array );
/* Do some query on the database */
int QuerySimple( media_library_t *p_ml,
const char *psz_fmt, ... );
int Query( media_library_t *p_ml,
char ***ppp_res,
int *pi_rows,
int *pi_cols,
const char *psz_fmt,
... );
int QueryVa( media_library_t *p_ml,
char ***ppp_res,
int *pi_rows,
int *pi_cols,
const char *psz_fmt,
va_list args );
int QuerySimpleVa( media_library_t *p_ml,
const char *psz_fmt,
va_list argp );
/* Convert SQL results to ML results */
int StringToResult( ml_result_t *res,
const char *psz,
const char *psz_id,
ml_result_type_e result_type );
int SQLToMediaArray( media_library_t *p_ml,
vlc_array_t *p_result_array,
char **pp_results,
int i_rows,
int i_cols );
int SQLToResultArray( media_library_t *p_ml,
vlc_array_t *p_result_array,
char **pp_results,
int i_rows,
int i_cols,
ml_result_type_e result_type );
/* Database locking functions */
/**
* @brief Begin a transaction
* @param p_ml The Media Library object
* @return VLC_SUCCESS and VLC_EGENERIC
* @note This creates a SHARED lock in SQLITE. All queries made between
* a Begin and Commit/Rollback will be transactional.
*/
static inline int Begin( media_library_t* p_ml )
{
return sql_BeginTransaction( p_ml->p_sys->p_sql );
}
/**
* @brief Commits the transaction
* @param p_ml The Media Library object
*/
static inline void Commit( media_library_t* p_ml )
{
sql_CommitTransaction( p_ml->p_sys->p_sql );
}
/**
* @brief Rollback the transaction
* @param p_ml The Media Library Object
*/
static inline void Rollback( media_library_t* p_ml )
{
sql_RollbackTransaction( p_ml->p_sys->p_sql );
}
/****************************************************************************
* Scanning/monitoring functions
*****************************************************************************/
void *RunMonitoringThread( void *p_mon );
int AddDirToMonitor( media_library_t *p_ml,
const char *psz_dir );
int ListMonitoredDirs( media_library_t *p_ml,
vlc_array_t *p_array );
int RemoveDirToMonitor( media_library_t *p_ml,
const char *psz_dir );
/*****************************************************************************
* Media pool functions
*****************************************************************************/
ml_media_t* pool_GetMedia( media_library_t* p_ml, int media_id );
int pool_InsertMedia( media_library_t* p_ml, ml_media_t* media, bool locked );
void pool_GC( media_library_t* p_ml );
/*****************************************************************************
* Items watching system
*****************************************************************************/
/* Watching thread */
#define watch_add_Item( a, b, c ) __watch_add_Item( a, b, c, false )
int watch_Init( media_library_t *p_ml );
void watch_Close( media_library_t *p_ml );
int __watch_add_Item( media_library_t *p_ml, input_item_t *p_item,
ml_media_t* p_media, bool locked );
#define watch_del_Item( a, b ) __watch_del_Item( a, b, false )
int __watch_del_Item( media_library_t *p_ml, input_item_t *p_item, bool locked );
int watch_del_MediaById( media_library_t* p_ml, int i_media_id );
input_item_t* watch_get_itemOfMediaId( media_library_t *p_ml, int i_media_id );
ml_media_t* watch_get_mediaOfMediaId( media_library_t* p_ml, int i_media_id );
int watch_get_mediaIdOfItem( media_library_t *p_ml, input_item_t *p_item );
void watch_Force_Update( media_library_t* p_ml );
/*****************************************************************************
* Free result of ml_Query
*****************************************************************************/
static inline void FreeSQLResult( media_library_t *p_ml, char **ppsz_result )
{
if( ppsz_result )
{
sql_Free( p_ml->p_sys->p_sql, ppsz_result );
}
}
#endif /* SQL_MEDIA_LIBRARY_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment