Commit 6bff1cc9 authored by jetru's avatar jetru

ML: Media Library Core

Core functions and singleton media library object
parent 49fd520a
......@@ -77,6 +77,7 @@ pluginsinclude_HEADERS = \
../include/vlc_md5.h \
../include/vlc_messages.h \
../include/vlc_meta.h \
../include/vlc_media_library.h \
../include/vlc_modules.h \
../include/vlc_mouse.h \
../include/vlc_mtime.h \
......@@ -452,6 +453,7 @@ SOURCES_libvlc_common = \
misc/update.c \
misc/update_crypto.c \
misc/xml.c \
misc/media_library.c \
extras/libc.c \
extras/tdestroy.c \
misc/filter.c \
......
......@@ -1259,6 +1259,10 @@ static const char *const ppsz_albumart_descriptions[] =
"The media library is automatically saved and reloaded each time you " \
"start VLC." )
#define LOAD_ML_TEXT N_( "Load Media Library" )
#define LOAD_ML_LONGTEXT N_( \
"Enable this option to load the SQL-based Media Library at VLC startup" )
#define PLTREE_TEXT N_("Display playlist tree")
#define PLTREE_LONGTEXT N_( \
"The playlist can use a tree to categorize some items, like the " \
......@@ -2123,6 +2127,10 @@ vlc_module_begin ()
add_bool( "play-and-pause", 0, NULL, PAP_TEXT, PAP_LONGTEXT, true )
change_safe()
add_bool( "media-library", 1, NULL, ML_TEXT, ML_LONGTEXT, false )
#if defined( MEDIA_LIBRARY )
add_bool( "load-media-library-on-startup", 1, NULL, LOAD_ML_TEXT,
LOAD_ML_LONGTEXT, false )
#endif
add_bool( "playlist-tree", 0, NULL, PLTREE_TEXT, PLTREE_LONGTEXT, false )
add_string( "open", "", NULL, OPEN_TEXT, OPEN_LONGTEXT, false )
......
......@@ -68,6 +68,8 @@
# include <dbus/dbus.h>
#endif
#include <vlc_media_library.h>
#include <vlc_playlist.h>
#include <vlc_interface.h>
......@@ -251,6 +253,7 @@ libvlc_int_t * libvlc_InternalCreate( void )
priv = libvlc_priv (p_libvlc);
priv->p_playlist = NULL;
priv->p_ml = NULL;
priv->p_dialog_provider = NULL;
priv->p_vlm = NULL;
......@@ -819,6 +822,23 @@ int libvlc_InternalInit( libvlc_int_t *p_libvlc, int i_argc,
/* System specific configuration */
system_Configure( p_libvlc, i_argc - vlc_optind, ppsz_argv + vlc_optind );
#if defined(MEDIA_LIBRARY)
/* Get the ML */
if( var_GetBool( p_libvlc, "load-media-library-on-startup" ) == true )
{
priv->p_ml = __ml_Create( VLC_OBJECT( p_libvlc ), NULL );
if( !priv->p_ml )
{
msg_Err( p_libvlc, "ML initialization failed" );
return VLC_EGENERIC;
}
}
else
{
priv->p_ml = NULL;
}
#endif
/* Add service discovery modules */
psz_modules = var_InheritString( p_libvlc, "services-discovery" );
if( psz_modules )
......@@ -1014,6 +1034,22 @@ void libvlc_InternalCleanup( libvlc_int_t *p_libvlc )
/* Free playlist now, all threads are gone */
playlist_Destroy( p_playlist );
/* Free playlist now */
#if defined(MEDIA_LIBRARY)
media_library_t* p_ml = priv->p_ml;
if( p_ml )
{
__ml_Destroy( VLC_OBJECT( p_ml ) );
vlc_object_release( p_ml );
libvlc_priv(p_playlist->p_libvlc)->p_ml = NULL;
}
#endif
/* Free playlist */
/* Any thread still running must not assume pl_Hold() succeeds. */
msg_Dbg( p_libvlc, "removing playlist" );
vlc_object_release( p_playlist );
stats_TimersDumpAll( p_libvlc );
stats_TimersCleanAll( p_libvlc );
......
......@@ -25,6 +25,8 @@
#ifndef LIBVLC_LIBVLC_H
# define LIBVLC_LIBVLC_H 1
#include<vlc_media_library.h>
typedef struct variable_t variable_t;
/* Actions (hot keys) */
......@@ -211,7 +213,8 @@ typedef struct libvlc_priv_t
/* Singleton objects */
module_t *p_memcpy_module; ///< Fast memcpy plugin used
playlist_t *p_playlist; //< the playlist singleton
playlist_t *p_playlist; ///< the playlist singleton
media_library_t *p_ml; ///< the ML singleton
vlm_t *p_vlm; ///< the VLM singleton (or NULL)
vlc_object_t *p_dialog_provider; ///< dialog provider
httpd_t *p_httpd; ///< HTTP daemon (src/network/httpd.c)
......
......@@ -241,6 +241,20 @@ LocaleFree
make_URI
make_path
mdate
__ml_Create
__ml_Destroy
__ml_Hold
__ml_Release
media_Destroy
media_New
ml_OpConnectChilds
__ml_FtreeSpec
__ml_UpdateSimple
ml_GetPersonsFromMedia
ml_DeletePersonTypeFromMedia
ml_PlaySmartPlaylistBasedOn
ml_gc_decref
ml_gc_incref
module_config_free
module_config_get
module_exists
......
/*****************************************************************************
* media_library.c: SQL-based media library: ML creators and destructors
*****************************************************************************
* Copyright (C) 2009-2010 the VideoLAN team and AUTHORS
* $Id$
*
* Authors: 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.
*****************************************************************************/
#if !defined( __LIBVLC__ )
#error You are not libvlc or one of its plugins. You cannot include this file
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(MEDIA_LIBRARY)
#include <assert.h>
#include <vlc_media_library.h>
#include "../libvlc.h"
/**
* @brief Destroy the medialibrary object
* @param Parent object that holds the media library object
*/
void __ml_Destroy( vlc_object_t * p_this )
{
media_library_t* p_ml = ( media_library_t* )p_this;
module_unneed( p_ml, p_ml->p_module );
}
/**
* Atomically set the reference count to 1.
* @param p_gc reference counted object
* @param pf_destruct destruction calback
* @return p_gc.
*/
static void *ml_gc_init (ml_gc_object_t *p_gc, void (*pf_destruct) (ml_gc_object_t *))
{
/* There is no point in using the GC if there is no destructor... */
assert (pf_destruct);
p_gc->pf_destructor = pf_destruct;
p_gc->pool = false;
p_gc->refs = 1;
/* Nobody else can possibly lock the spin - it's there as a barrier */
vlc_spin_init (&p_gc->spin);
vlc_spin_lock (&p_gc->spin);
vlc_spin_unlock (&p_gc->spin);
return p_gc;
}
/**
* @brief Create an instance of the media library
* @param p_this Parent object
* @param psz_name Name which is passed to module_need (not needed)
* @return p_ml created and attached, module loaded. NULL if
* not able to load
*/
media_library_t *__ml_Create( vlc_object_t *p_this, char *psz_name )
{
media_library_t *p_ml = NULL;
p_ml = ( media_library_t * ) vlc_custom_create(
p_this, sizeof( media_library_t ),
VLC_OBJECT_GENERIC, "media-library" );
if( !p_ml )
{
msg_Err( p_this, "unable to create media library object" );
return NULL;
}
vlc_object_attach( p_ml, p_this );
p_ml->p_module = module_need( p_ml, "media-library", psz_name, false );
if( !p_ml->p_module )
{
vlc_object_release( p_ml );
msg_Err( p_this, "Media Library provider not found" );
return NULL;
}
return p_ml;
}
/**
* @brief Acquire a reference to the media library singleton
* @param p_this Object that holds the reference
* @return media_library_t The ml object. NULL if not compiled with
* media library or if unable to load
*/
media_library_t* __ml_Hold( vlc_object_t* p_this )
{
media_library_t* p_ml = NULL;
p_ml = libvlc_priv (p_this->p_libvlc)->p_ml;
assert( VLC_OBJECT( p_ml ) != p_this );
if( p_ml == NULL &&
var_GetBool( p_this->p_libvlc, "load-media-library-on-startup" ) == false )
{
libvlc_priv (p_this->p_libvlc)->p_ml
= __ml_Create( VLC_OBJECT( p_this->p_libvlc ), NULL );
p_ml = libvlc_priv (p_this->p_libvlc)->p_ml;
}
if( p_ml )
vlc_object_hold( p_ml );
return p_ml;
}
/**
* @brief Release a reference to the media library singleton
* @param p_this Object that holds the reference
*/
void __ml_Release( vlc_object_t* p_this )
{
media_library_t* p_ml;
p_ml = libvlc_priv (p_this->p_libvlc)->p_ml;
if( p_ml == NULL )
{
msg_Warn( p_this->p_libvlc , "Spurious release ML called");
return;
}
assert( VLC_OBJECT( p_ml ) != p_this );
vlc_object_release( p_ml );
}
/**
* @brief Destructor for ml_media_t
*/
static void media_Destroy( ml_gc_object_t *p_gc )
{
ml_media_t* p_media = ml_priv( p_gc, ml_media_t );
vlc_mutex_destroy( &p_media->lock );
ml_FreeMediaContent( p_media );
free( p_media );
}
/**
* @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* media_New( media_library_t* p_ml, int id,
ml_select_e select, bool reload )
{
if( id == 0 )
{
ml_media_t* p_media = NULL;
p_media = ( ml_media_t* )calloc( 1, sizeof( ml_media_t ) );
ml_gc_init( &p_media->ml_gc_data, media_Destroy );
vlc_mutex_init( &p_media->lock );
return p_media;
}
else
return p_ml->functions.pf_GetMedia( p_ml, id, select, reload );
}
/**
* @brief Update a given table
* @param p_media_library The media library object
* @param selected_type The table to update
* @param psz_lvalue The role of the person if selected_type = ML_PEOPLE
* @param id The id of the row to update
* @param ... The update data. [SelectType [RoleType] Value] ... ML_END
*/
int __ml_UpdateSimple( media_library_t *p_media_library,
ml_select_e selected_type,
const char* psz_lvalue,
int id, ... )
{
ml_element_t *update = NULL;
vlc_array_t *array = vlc_array_new();
int i_ret = VLC_SUCCESS;
va_list args;
va_start( args, id );
ml_select_e sel;
do {
update = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
sel = ( ml_select_e ) va_arg( args, int );
update->criteria = sel;
if( sel == ML_PEOPLE )
{
update->lvalue.str = va_arg( args, char* );
update->value.str = va_arg( args, char* );
vlc_array_append( array, update );
}
else if( sel == ML_PEOPLE_ID )
{
update->lvalue.str = va_arg( args, char* );
update->value.i = va_arg( args, int );
vlc_array_append( array, update );
}
else if( sel == ML_PEOPLE_ROLE )
{
#ifndef NDEBUG
msg_Dbg( p_media_library,
"this argument is invalid for Update: %d",
(int)sel );
#endif
}
else
{
switch( ml_AttributeIsString( sel ) )
{
case -1:
if( sel != ML_END )
{
#ifndef NDEBUG
msg_Dbg( p_media_library,
"this argument is invalid for Update: %d",
(int)sel );
#endif
i_ret = VLC_EGENERIC;
}
else if( sel == ML_END )
vlc_array_append( array, update );
break;
case 0:
update->value.str = va_arg( args, char* );
vlc_array_append( array, update );
break;
case 1:
update->value.i = va_arg( args, int );
vlc_array_append( array, update );
break;
}
}
} while( sel != ML_END );
va_end( args );
ml_ftree_t* p_where = NULL;
ml_ftree_t* find = ( ml_ftree_t* ) calloc( 1, sizeof( ml_ftree_t ) );
find->criteria = ML_ID;
find->value.i = id ;
find->comp = ML_COMP_EQUAL;
p_where = ml_FtreeFastAnd( p_where, find );
/* Let's update the database ! */
if( i_ret == VLC_SUCCESS )
i_ret = ml_Update( p_media_library, selected_type, psz_lvalue,
p_where, array );
/* Destroying array */
for( int i = 0; i < vlc_array_count( array ); i++ )
{
free( vlc_array_item_at_index( array, i ) );
}
vlc_array_destroy( array );
ml_FreeFindTree( p_where );
return i_ret;
}
/**
* @brief Connect up a find tree
* @param op operator to connect with
* If op = ML_OP_NONE, then you are connecting to a tree consisting of
* only SPECIAL nodes.
* If op = ML_OP_NOT, then right MUST be NULL
* op must not be ML_OP_SPECIAL, @see __ml_FtreeSpec
* @param left part of the tree
* @param right part of the tree
* @return Pointer to new tree
* @note Use the helpers!
*/
ml_ftree_t* ml_OpConnectChilds( ml_op_e op, ml_ftree_t* left,
ml_ftree_t* right )
{
/* Use this Op for fresh trees (with only special nodes/none at all!) */
if( op == ML_OP_NONE )
{
assert( ml_FtreeHasOp( left ) == 0 );
if( left == NULL )
return right;
/* Percolate down tree only for special nodes */
assert( left->op == ML_OP_SPECIAL );
if( left->left == NULL )
{
left->left = right;
return left;
}
else
{
return ml_OpConnectChilds( ML_OP_NONE, left->left, right );
}
}
else if( op == ML_OP_NOT )
{
assert( right == NULL && left != NULL );
assert( ml_FtreeHasOp( left ) > 0 );
}
else if( op == ML_OP_SPECIAL )
{
assert( 0 );
}
else
{
assert( right != NULL && left != NULL );
assert( ml_FtreeHasOp( left ) > 0 );
assert( ml_FtreeHasOp( right ) > 0 );
}
ml_ftree_t* p_parent = (ml_ftree_t *) calloc( 1, sizeof( ml_ftree_t ) );
p_parent->op = op;
p_parent->left = left;
p_parent->right = right;
return p_parent;
}
/**
* @brief Attaches a special node to a tree
* @param tree Tree to attach special node to
* @param crit Criteria may be SORT_ASC, SORT_DESC, LIMIT or DISTINCT
* @param limit Limit used if LIMIT criteria used
* @param Sort string used if SORT criteria is used
* @return Pointer to new tree
* @note Use the helpers
*/
ml_ftree_t* __ml_FtreeSpec( ml_ftree_t* tree,
ml_select_e crit,
int limit,
char* sort )
{
assert( crit == ML_SORT_ASC || crit == ML_LIMIT || crit == ML_SORT_DESC ||
crit == ML_DISTINCT );
ml_ftree_t* right = ( ml_ftree_t* ) calloc( 1, sizeof( ml_ftree_t ) );
right->criteria = crit;
if( crit == ML_LIMIT )
right->value.i = limit;
else if( crit == ML_SORT_ASC || crit == ML_SORT_DESC )
right->value.str = strdup( sort );
right->op = ML_OP_NONE;
ml_ftree_t* p_parent = ( ml_ftree_t* ) calloc( 1, sizeof( ml_ftree_t ) );
p_parent->right = right;
p_parent->op = ML_OP_SPECIAL;
p_parent->left = tree;
return p_parent;
}
/**
* @brief Creates and adds the playlist based on a given find tree
* @param p_ml Media library object
* @param p_tree Find tree to create SELECT
*/
void ml_PlaySmartPlaylistBasedOn( media_library_t* p_ml,
ml_ftree_t* p_tree )
{
assert( p_tree );
vlc_array_t* p_results = vlc_array_new();
ml_FindAdv( p_ml, p_results, ML_ID, NULL, p_tree );
playlist_t* p_pl = pl_Hold( p_ml );
playlist_Lock( p_pl );
playlist_Clear( p_pl, true );
for( int i = 0; i < vlc_array_count( p_results ); i++ )
{
ml_result_t* p_res = ( ml_result_t* ) vlc_array_item_at_index( p_results, i );
input_item_t* p_item;
if( p_res )
{
p_item = ml_CreateInputItem( p_ml, p_res->value.i );
playlist_AddInput( p_pl, p_item, PLAYLIST_APPEND,
PLAYLIST_END, true, true );
}
}
playlist_Unlock( p_pl );
ml_DestroyResultArray( p_results );
vlc_array_destroy( p_results );
}
/**
* @brief Returns a person list of given type
* @param p_ml The ML object
* @param p_media The Media object
* @param i_type The person type
* @note This function is thread safe
*/
ml_person_t* ml_GetPersonsFromMedia( media_library_t* p_ml,
ml_media_t* p_media,
const char *psz_role )
{
VLC_UNUSED( p_ml );
assert( p_media != NULL );
ml_person_t* p_return = NULL;
ml_LockMedia( p_media );
ml_person_t* p_person = p_media->p_people;
while( p_person )
{
if( strcmp( p_person->psz_role, psz_role ) == 0 )
{
int i_ret = ml_CreateAppendPerson( &p_return, p_person );
if( i_ret != VLC_SUCCESS )
{
ml_UnlockMedia( p_media );
ml_FreePeople( p_return );
return NULL;
}
}
p_person = p_person->p_next;
}
ml_UnlockMedia( p_media );
//TODO: Fill up empty names + clean up list
return p_return;
}
/**
* @brief Delete a certain type of people from a media
* @param p_media Media to delete from
* @param i_type Type of person to delete
* @note This function is threadsafe
*/
void ml_DeletePersonTypeFromMedia( ml_media_t* p_media, const char *psz_role )
{
assert( p_media );
ml_LockMedia( p_media );
ml_person_t* p_prev = NULL;
ml_person_t* p_person = p_media->p_people;
while( p_person )
{
if( strcmp( p_person->psz_role, psz_role ) == 0 )
{
if( p_prev == NULL )
{
p_media->p_people = p_person->p_next;
p_person->p_next = NULL;
ml_FreePeople( p_person );
p_person = p_media->p_people;
}
else
{
p_prev->p_next = p_person->p_next;
p_person->p_next = NULL;
ml_FreePeople( p_person );
p_person = p_prev->p_next;
}
}
else
{
p_prev = p_person;
p_person = p_person->p_next;
}
}
ml_UnlockMedia( p_media );
}
#endif /* MEDIA_LIBRARY */
......@@ -395,3 +395,60 @@ vlm_t *vlm_New (vlc_object_t *obj)
return NULL;
}
#endif /* !ENABLE_VLM */
#ifndef MEDIA_LIBRARY
#include<vlc_media_library.h>
media_library_t* __ml_Hold ( vlc_object_t* p_this )
{
return NULL;
}
void __ml_Release ( vlc_object_t* p_this )
{
assert( 0 );
}