+/*****************************************************************************
+ * 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 */