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 {
/* Useful text messages shared by interfaces */
#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 \
"*.a52;" \
"*.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