Commit 79990b94 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

media library: good riddance

Big pile of non-sensical and unmaintained code.
parent 8e3288f9
......@@ -3967,7 +3967,6 @@ AC_ARG_ENABLE(media-library,
AS_IF([test "${enable_media_library}" = "yes"], [
AC_DEFINE([MEDIA_LIBRARY], 1, [Define if you want to use the VLC media library])
VLC_ADD_CPPFLAGS([qt4],"-DMEDIA_LIBRARY")
VLC_ADD_PLUGIN([media_library])
dnl
dnl SQLite
......@@ -4135,7 +4134,6 @@ AC_CONFIG_FILES([
modules/lua/Makefile
modules/meta_engine/Makefile
modules/misc/Makefile
modules/media_library/Makefile
modules/mux/Makefile
modules/notify/Makefile
modules/packetizer/Makefile
......
......@@ -192,7 +192,6 @@ $Id$
* magnify: zoom video filter
* marq: Overlays a marquee on the video
* mash: OpenMash based decoder
* media_library: a sql based media library
* mediacodec: Android Jelly Bean MediaCodec decoder module
* mediadirs: Picture/Music/Video user directories as service discoveries
* minimal_macosx: a minimal Mac OS X GUI, using the FrameWork
......
......@@ -10,7 +10,6 @@ BASE_SUBDIRS = \
gui \
meta_engine \
misc \
media_library \
notify \
packetizer \
services_discovery \
......
SOURCES_media_library = sql_media_library.c \
sql_media_library.h \
sql_monitor.c \
sql_search.c \
sql_add.c \
sql_update.c \
sql_delete.c \
item_list.c \
item_list.h \
ml_watch.c \
media_pool.c
/*****************************************************************************
* item_list.c: An input_item_t+media_id couple list for the 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"
#include "item_list.h"
/**
* @short item hash list for the media library monitoring system
*/
/**
* @brief Add an item to the head of the list
* @param p_ml
* @param p_media media object. ID must be non zero and valid
* @param p_item input item to add, MUST NOT be NULL
* @param locked flag set if the list is locked. do not use
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int __item_list_add( watch_thread_t *p_wt, ml_media_t* p_media, input_item_t *p_item,
bool locked )
{
if( !locked )
vlc_mutex_lock( &p_wt->list_mutex );
ml_LockMedia( p_media );
assert( p_media->i_id );
/* Ensure duplication does not occur */
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item->i_id == p_item->i_id )
{
ml_UnlockMedia( p_media );
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return VLC_EGENERIC;
}
}
item_list_t *p_new = ( item_list_t* ) calloc( 1, sizeof( item_list_t ) );
if( !p_new )
{
ml_UnlockMedia( p_media );
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return VLC_ENOMEM;
}
p_new->p_next = p_wt->p_hlist[ item_hash( p_item ) ];
p_new->i_refs = 1;
p_new->i_media_id = p_media->i_id;
p_new->p_media = p_media;
p_new->p_item = p_item;
p_wt->p_hlist[ item_hash( p_item ) ] = p_new;
ml_UnlockMedia( p_media );
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return VLC_SUCCESS;
}
/**
* @brief Delete an item from the list
* @param p_ml this media library
* @param i_media_id media library's media ID
*/
item_list_t* item_list_delMedia( watch_thread_t *p_wt, int i_media_id )
{
vlc_mutex_lock( &p_wt->list_mutex );
item_list_t *p_prev = NULL;
il_foreachhashlist( p_wt->p_hlist, p_elt, ixx )
{
if( p_elt->i_media_id == i_media_id )
{
if( p_prev )
p_prev->p_next = p_elt->p_next;
else
p_wt->p_hlist[ixx] = p_elt->p_next;
p_elt->p_next = NULL;
vlc_mutex_unlock( &p_wt->list_mutex );
return p_elt;
}
else
{
p_prev = p_elt;
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
return NULL;
}
/**
* @brief Delete an item from the list and return the single element
* @param p_ml this media library
* @param p_item item to delete
* @return The element from the list containing p_item
*/
item_list_t* item_list_delItem( watch_thread_t *p_wt, input_item_t *p_item, bool locked )
{
if( !locked )
vlc_mutex_lock( &p_wt->list_mutex );
item_list_t *p_prev = NULL;
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item == p_item )
{
if( p_prev )
p_prev->p_next = p_elt->p_next;
else
p_wt->p_hlist[ item_hash( p_item ) ] = p_elt->p_next;
p_elt->p_next = NULL;
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return p_elt;
}
else
{
p_prev = p_elt;
}
}
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return NULL;
}
/**
* @brief Find an input item
* @param p_ml this media library
* @param i_media_id item to find and gc_incref
* @return input item if found, NULL if not found
*/
input_item_t* item_list_itemOfMediaId( watch_thread_t *p_wt, int i_media_id )
{
item_list_t* p_tmp = item_list_listitemOfMediaId( p_wt, i_media_id );
if( p_tmp )
return p_tmp->p_item;
else
return NULL;
}
/**
* @brief Find an item list item
* @param p_ml this media library
* @param i_media_id item to find and gc_incref
* @return input item if found, NULL if not found
*/
item_list_t* item_list_listitemOfMediaId( watch_thread_t *p_wt, int i_media_id )
{
vlc_mutex_lock( &p_wt->list_mutex );
il_foreachhashlist( p_wt->p_hlist, p_elt, ixx )
{
if( p_elt->i_media_id == i_media_id )
{
p_elt->i_age = 0;
vlc_mutex_unlock( &p_wt->list_mutex );
return p_elt;
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
return NULL;
}
/**
* @brief Find a media
* @param p_ml this media library
* @param i_media_id item to find and gc_incref
* @return media if found. NULL otherwise
*/
ml_media_t* item_list_mediaOfMediaId( watch_thread_t *p_wt, int i_media_id )
{
item_list_t* p_tmp = item_list_listitemOfMediaId( p_wt, i_media_id );
if( p_tmp )
return p_tmp->p_media;
else
return NULL;
}
/**
* @brief Find a media ID by its input_item
* @param p_ml this media library
* @param p_item item to find
* @return media_id found, or VLC_EGENERIC
*/
int item_list_mediaIdOfItem( watch_thread_t *p_wt, input_item_t *p_item )
{
vlc_mutex_lock( &p_wt->list_mutex );
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item == p_item )
{
if( p_elt->i_media_id <= 0 )
/* TODO! */
p_elt->i_age = 0;
vlc_mutex_unlock( &p_wt->list_mutex );
return p_elt->i_media_id;
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
return VLC_EGENERIC;
}
/**
* @brief Find a media by its input_item
* @param p_ml this media library
* @param p_item item to find
* @return media found, or VLC_EGENERIC
*/
ml_media_t* item_list_mediaOfItem( watch_thread_t *p_wt, input_item_t* p_item,
bool locked )
{
if( !locked )
vlc_mutex_lock( &p_wt->list_mutex );
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item == p_item )
{
p_wt->p_hlist[ item_hash( p_item ) ] = p_elt->p_next;
p_elt->p_next = NULL;
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return p_elt->p_media;
}
}
if( !locked )
vlc_mutex_unlock( &p_wt->list_mutex );
return NULL;
}
/**
* @brief Flag an item as updated
* @param p_ml this media library
* @param p_item item to find and flag
* @param b_played raise play count or not, update last play
* @return media_id found, or VLC_EGENERIC
*/
int item_list_updateInput( watch_thread_t *p_wt, input_item_t *p_item,
bool b_played )
{
vlc_mutex_lock( &p_wt->list_mutex );
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item == p_item )
{
/* Item found, flag and return */
p_elt->i_age = 0;
p_elt->i_update |= b_played ? 3 : 1;
vlc_mutex_unlock( &p_wt->list_mutex );
return p_elt->i_media_id;
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
return VLC_EGENERIC;
}
/**
* @brief Free every item in the item list.
* @param p_wt Watch thread
* @note All decref of objects must be handled by watch system
*/
void item_list_destroy( watch_thread_t* p_wt )
{
vlc_mutex_lock( &p_wt->list_mutex );
for( int i = 0; i < ML_ITEMLIST_HASH_LENGTH ; i++ )
{
for( item_list_t* p_elt = p_wt->p_hlist[i] ; p_elt; p_elt = p_wt->p_hlist[i] )
{
p_wt->p_hlist[i] = p_elt->p_next;
free( p_elt );
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
}
/*****************************************************************************
* item_list.h : Item list data structure for Watching system
*****************************************************************************
* Copyright (C) 2008-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.
*****************************************************************************/
#ifndef ML_ITEM_LIST_H
#define ML_ITEM_LIST_H
#include <vlc_input.h>
#include <vlc_arrays.h>
struct watch_thread_t;
typedef struct watch_thread_t watch_thread_t;
typedef struct item_list_t item_list_t;
/**
* Definition of item_list_t
*/
struct item_list_t {
input_item_t *p_item; /**< Input item */
ml_media_t *p_media; /**< Media item */
item_list_t *p_next; /**< Next element in the list */
int i_media_id; /**< Media id */
int i_age; /**< Time spent in this list without activity */
int i_refs; /**< Number of important refs */
int i_update; /**< Flag set when the input item is updated:
0: no update,
1: meta update,
2: increment play count,
3: both */
};
#define ML_ITEMLIST_HASH_LENGTH 40
#define il_foreachhashlist( a, b, c ) \
for( int c = 0 ; c < ML_ITEMLIST_HASH_LENGTH ; c++ ) \
for( item_list_t* b = a[c]; b; b = b->p_next )
#define il_foreachlist( a, b ) for( item_list_t* b = a ; b; b = b->p_next )
#define item_list_add( a, b, c ) __item_list_add( a, b, c, false )
int __item_list_add( watch_thread_t *p_wt, ml_media_t* p_media,
input_item_t *p_item, bool );
item_list_t* item_list_delMedia( watch_thread_t *p_wt, int i_media_id );
item_list_t* item_list_delItem( watch_thread_t *p_wt, input_item_t *p_item, bool );
item_list_t* item_list_listitemOfMediaId( watch_thread_t *p_wt, int i_media_id );
input_item_t* item_list_itemOfMediaId( watch_thread_t *p_wt, int i_media_id );
ml_media_t* item_list_mediaOfMediaId( watch_thread_t *p_wt, int i_media_id );
ml_media_t* item_list_mediaOfItem( watch_thread_t *p_wt, input_item_t* p_item, bool );
int item_list_mediaIdOfItem( watch_thread_t *p_wt, input_item_t *p_item );
int item_list_updateInput( watch_thread_t *p_wt, input_item_t *p_item,
bool b_play_count );
void item_list_destroy( watch_thread_t* p_wt );
/**
* @brief Simple hash function
* @param item_id Hash Key
* @return Hash index
*/
static inline int item_hash( input_item_t* p_item )
{
return DictHash( p_item->psz_uri, ML_ITEMLIST_HASH_LENGTH );
}
#endif /* ML_ITEM_LIST_H */
/*****************************************************************************
* media_pool.c : Media pool for watching system
*****************************************************************************
* 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.
*****************************************************************************/
#include "sql_media_library.h"
#define mp_foreachlist( a, b ) for( ml_poolobject_t* b = a; b; b = b->p_next )
static inline int mediapool_hash( int media_id )
{
return media_id % ML_MEDIAPOOL_HASH_LENGTH;
}
/**
* @brief Get a media from the pool
* @param p_ml ML object
* @param media_id The media id of the object to get
* @return the found media or NULL if not found
*/
ml_media_t* pool_GetMedia( media_library_t* p_ml, int media_id )
{
vlc_mutex_lock( &p_ml->p_sys->pool_mutex );
ml_media_t* p_media = NULL;
mp_foreachlist( p_ml->p_sys->p_mediapool[ mediapool_hash( media_id ) ], p_item )
{
if( p_item->p_media->i_id == media_id )
{
p_media = p_item->p_media;
break;
}
}
if( p_media )
ml_gc_incref( p_media );
vlc_mutex_unlock( &p_ml->p_sys->pool_mutex );
return p_media;
}
/**
* @brief Insert a media into the media pool
* @param p_ml ML object
* @param p_media Media object to insert
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int pool_InsertMedia( media_library_t* p_ml, ml_media_t* p_media, bool locked )
{
if( !locked )
ml_LockMedia( p_media );
assert( p_media );
assert( p_media->i_id > 0 );
if( p_media->ml_gc_data.pool )
{
msg_Dbg( p_ml, "Already in pool! %s %d", p_media->psz_uri, p_media->i_id );
ml_UnlockMedia( p_media );
return VLC_EGENERIC;
}
p_media->ml_gc_data.pool = true;
int i_ret = VLC_SUCCESS;
vlc_mutex_lock( &p_ml->p_sys->pool_mutex );
mp_foreachlist( p_ml->p_sys->p_mediapool[ (mediapool_hash(p_media->i_id)) ], p_item )
{
if( p_media == p_item->p_media )
{
i_ret = VLC_EGENERIC;
break;
}
else if( p_media->i_id == p_item->p_media->i_id )
{
i_ret = VLC_EGENERIC;
msg_Warn( p_ml, "A media of the same id was found, but in different objects!" );
break;
}
}
if( i_ret == VLC_SUCCESS )
{
ml_poolobject_t* p_new = ( ml_poolobject_t * ) calloc( 1, sizeof( ml_poolobject_t* ) );
if( !p_new )
i_ret = VLC_EGENERIC;
else
{
ml_gc_incref( p_media );
p_new->p_media = p_media;
p_new->p_next = p_ml->p_sys->p_mediapool[ ( mediapool_hash( p_media->i_id ) ) ];
p_ml->p_sys->p_mediapool[ ( mediapool_hash( p_media->i_id ) ) ] = p_new;
}
}
vlc_mutex_unlock( &p_ml->p_sys->pool_mutex );
if( !locked )
ml_UnlockMedia( p_media );
return i_ret;
}
/**
* @brief Perform a single garbage collection scan on the media pool
* @param p_ml The ML object
* @note Scans all media and removes any medias not held by any other objects.
*/
void pool_GC( media_library_t* p_ml )
{
vlc_mutex_lock( &p_ml->p_sys->pool_mutex );
ml_poolobject_t* p_prev = NULL;
ml_media_t* p_media = NULL;
for( int i_idx = 0; i_idx < ML_MEDIAPOOL_HASH_LENGTH; i_idx++ )
{
p_prev = NULL;
for( ml_poolobject_t* p_item = p_ml->p_sys->p_mediapool[ i_idx ];
p_item != NULL; p_item = p_item->p_next )
{
p_media = p_item->p_media;
int refs;
refs = p_media->ml_gc_data.refs;
if( refs == 1 )
{
if( p_prev == NULL )
p_ml->p_sys->p_mediapool[i_idx] = p_item->p_next;
else
p_prev->p_next = p_item->p_next;
p_media->ml_gc_data.pool = false;
ml_gc_decref( p_item->p_media );//This should destroy the object
free( p_item );
}
p_prev = p_item;
}
}
vlc_mutex_unlock( &p_ml->p_sys->pool_mutex );
}
/*****************************************************************************
* ml_watch.c: SQL-based media library: Medias watching system
*****************************************************************************
* 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"
#include "item_list.h"
#include <vlc_events.h>
static void watch_ItemChange( const vlc_event_t *, void * );
static int watch_PlaylistItemCurrent( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *data );
static int watch_PlaylistItemAppend( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *data );
static int watch_PlaylistItemDeleted( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *data );
static void watch_loop( media_library_t *p_ml, bool b_force );
static void watch_Thread_Cleanup( void* p_object );
static int watch_update_Item( media_library_t *p_ml, int i_media_id,
input_item_t *p_item, bool b_raise_count, bool locked );
static void watch_ProcessAppendQueue( media_library_t* p_ml );
/**
* @brief Watching thread
*/
static void* watch_Thread( void *obj )
{
watch_thread_t *p_watch = ( watch_thread_t* )obj;
media_library_t *p_ml = p_watch->p_ml;
int i_ret = 0;
vlc_mutex_lock( &p_watch->lock );
vlc_cleanup_push( watch_Thread_Cleanup, p_ml );
for( ;; )
{
watch_loop( p_ml, !i_ret );
i_ret = vlc_cond_timedwait( &p_watch->cond, &p_watch->lock,
mdate() + 1000000 * THREAD_SLEEP_DELAY );
}
vlc_cleanup_run();
return NULL;
}
/**
* @brief Callback for thread exit
*/
static void watch_Thread_Cleanup( void* p_object )
{
media_library_t* p_ml = ( media_library_t* )p_object;
watch_loop( p_ml, true );
vlc_mutex_unlock( &p_ml->p_sys->p_watch->lock );
}
/**
* @brief Init watching system
* @return Error if the object or the thread could not be created
*/
int watch_Init( media_library_t *p_ml )
{
/* init and launch watching thread */
p_ml->p_sys->p_watch = calloc( 1, sizeof(*p_ml->p_sys->p_watch) );
if( !p_ml->p_sys->p_watch )
return VLC_ENOMEM;
watch_thread_t* p_wt = p_ml->p_sys->p_watch;
vlc_mutex_init( &p_wt->list_mutex );
p_wt->p_ml = p_ml;
vlc_cond_init( &p_wt->cond );
vlc_mutex_init( &p_wt->lock );
/* Initialise item append queue */
vlc_mutex_init( &p_wt->item_append_queue_lock );
p_wt->item_append_queue = NULL;
p_wt->item_append_queue_count = 0;
if( vlc_clone( &p_wt->thread, watch_Thread, p_wt, VLC_THREAD_PRIORITY_LOW ) )
{
msg_Dbg( p_ml, "unable to launch the auto-updating thread" );
free( p_wt );
return VLC_EGENERIC;
}
/* Wait on playlist events
* playlist-item-append -> entry to playlist
* activity -> to ensure that we catch played item only!
* playlist-item-deleted -> exit from playlist
* item-change -> Currently not required, as we monitor input_item events
*/
playlist_t *p_pl = pl_Get( p_ml );
var_AddCallback( p_pl, "activity", watch_PlaylistItemCurrent, p_ml );
var_AddCallback( p_pl, "playlist-item-append", watch_PlaylistItemAppend, p_ml );
var_AddCallback( p_pl, "playlist-item-deleted", watch_PlaylistItemDeleted, p_ml );
return VLC_SUCCESS;
}
/**
* @brief Add the input to the watch system
* @param p_ml The Media Library Object
* @param p_item Item to be watched
* @param p_media Corresponding media item to sync with
* @param locked Status of item list lock
* @return VLC_SUCCESS or error code
*/
int __watch_add_Item( media_library_t *p_ml, input_item_t *p_item,
ml_media_t* p_media, bool locked )
{
vlc_gc_incref( p_item );
ml_gc_incref( p_media );
int i_ret = __item_list_add( p_ml->p_sys->p_watch, p_media, p_item, locked );
if( i_ret != VLC_SUCCESS )
return i_ret;
vlc_event_manager_t *p_em = &p_item->event_manager;
vlc_event_attach( p_em, vlc_InputItemMetaChanged, watch_ItemChange, p_ml );
vlc_event_attach( p_em, vlc_InputItemNameChanged, watch_ItemChange, p_ml );
vlc_event_attach( p_em, vlc_InputItemInfoChanged, watch_ItemChange, p_ml );
/*
Note: vlc_InputItemDurationChanged is disabled because
it is triggered too often, even without consequent changes
*/
return VLC_SUCCESS;
}
/**
* @brief Detach event manager
* @param p_ml The Media Library Object
*/
static void detachItemEvents( media_library_t *p_ml, input_item_t *p_item )
{
vlc_event_manager_t *p_em = &p_item->event_manager;
vlc_event_detach( p_em, vlc_InputItemMetaChanged, watch_ItemChange, p_ml );
vlc_event_detach( p_em, vlc_InputItemNameChanged, watch_ItemChange, p_ml );
vlc_event_detach( p_em, vlc_InputItemInfoChanged, watch_ItemChange, p_ml );
}
/**
* @brief Close the watching system
* @param p_ml The Media Library Object
*/
void watch_Close( media_library_t *p_ml )
{
playlist_t *p_pl = pl_Get( p_ml );
var_DelCallback( p_pl, "playlist-item-deleted", watch_PlaylistItemDeleted, p_ml );
var_DelCallback( p_pl, "playlist-item-append", watch_PlaylistItemAppend, p_ml );
var_DelCallback( p_pl, "activity", watch_PlaylistItemCurrent, p_ml );
/* Flush item list */
il_foreachhashlist( p_ml->p_sys->p_watch->p_hlist, p_elt, ixx )
{
detachItemEvents( p_ml, p_elt->p_item );
ml_gc_decref( p_elt->p_media );
vlc_gc_decref( p_elt->p_item );
}
item_list_destroy( p_ml->p_sys->p_watch );
/* Stop the watch thread and join in */
vlc_cancel( p_ml->p_sys->p_watch->thread );
vlc_join( p_ml->p_sys->p_watch->thread, NULL );
/* Clear up other stuff */
vlc_mutex_destroy( &p_ml->p_sys->p_watch->lock );
vlc_cond_destroy( &p_ml->p_sys->p_watch->cond );
vlc_mutex_destroy( &p_ml->p_sys->p_watch->list_mutex );
free( p_ml->p_sys->p_watch );
free( p_ml->p_sys->p_watch->item_append_queue );
vlc_mutex_destroy( &p_ml->p_sys->p_watch->item_append_queue_lock );
p_ml->p_sys->p_watch = NULL;
}
/**
* @brief Del item that is currently being watched
* @param p_ml The Media Library Object
* @param p_item Item to stop watching
* @param locked Lock state of item list
*/
int __watch_del_Item( media_library_t* p_ml, input_item_t* p_item, bool locked )
{
assert( p_item );
item_list_t* p_tmp = item_list_delItem( p_ml->p_sys->p_watch, p_item, locked );
if( p_tmp == NULL )
return VLC_EGENERIC;
detachItemEvents( p_ml, p_tmp->p_item );
vlc_gc_decref( p_tmp->p_item );
ml_gc_decref( p_tmp->p_media );
free( p_tmp );
return VLC_SUCCESS;
}
/**
* @brief Del media from watching by ID
* @param p_ml The Media Library Object
* @param i_media_id Media ID
*/
int watch_del_MediaById( media_library_t* p_ml, int i_media_id )
{
assert( i_media_id > 0 );
item_list_t* p_elt = item_list_delMedia( p_ml->p_sys->p_watch, i_media_id );
if( p_elt == NULL )
return VLC_EGENERIC;
detachItemEvents( p_ml, p_elt->p_item );
vlc_gc_decref( p_elt->p_item );
ml_gc_decref( p_elt->p_media );
free( p_elt );
return VLC_SUCCESS;
}
/**
* @brief Get item using media id, if exists in item list
* @param p_ml The Media Library Object
* @param i_media_id Media ID
*/
input_item_t* watch_get_itemOfMediaId( media_library_t *p_ml, int i_media_id )
{
input_item_t* p_tmp = item_list_itemOfMediaId( p_ml->p_sys->p_watch, i_media_id );
if( p_tmp == NULL )
return NULL;
vlc_gc_incref( p_tmp );
return p_tmp;
}
/**
* @brief Get media using media id, if exists in item list
* @param p_ml The Media Library Object
* @param i_media_id Media ID
*/
ml_media_t* watch_get_mediaOfMediaId( media_library_t* p_ml, int i_media_id )
{
ml_media_t* p_tmp = item_list_mediaOfMediaId( p_ml->p_sys->p_watch, i_media_id );
if( p_tmp == NULL )
return NULL;
ml_gc_incref( p_tmp );
return p_tmp;
}
/**
* @brief Get mediaid of existing item
* @param p_ml The Media Library Object
* @param p_item Pointer to input item
*/
int watch_get_mediaIdOfItem( media_library_t *p_ml, input_item_t *p_item )
{
return item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
}
/**
* @brief Updates a media each time it is changed (name, info or meta)
*/
static void watch_ItemChange( const vlc_event_t *event, void *data )
{
input_item_t *p_item = ( input_item_t* ) event->p_obj;
media_library_t *p_ml = ( media_library_t* ) data;
/* Note: we don't add items to the item_list, but normally there should
not be any item at this point that is not in the list. */
if( item_list_updateInput( p_ml->p_sys->p_watch, p_item, false ) <= 0 )
{
#ifndef NDEBUG
msg_Dbg( p_ml, "Couldn't update in watch_ItemChange(): (%s:%d)",
__FILE__, __LINE__ );
#endif
}
/*
if( event->type == vlc_InputItemMetaChanged )
{
int id = item_list_mediaIdOfItem( p_ml->p_sys->p_watch, p_item );
if( !id ) return;
* Tell the world what happened *
var_SetInteger( p_ml, "media-meta-change", id );
}
*/
}
/**
* @brief Callback when item is added to playlist
*/
static int watch_PlaylistItemAppend( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *data )
{
VLC_UNUSED( oldval );
VLC_UNUSED( p_this );
VLC_UNUSED( psz_var );
media_library_t* p_ml = ( media_library_t* ) data;
playlist_t* p_playlist = pl_Get( p_ml );
playlist_add_t* p_add;
p_add = ( playlist_add_t* ) newval.p_address;
playlist_item_t* p_pitem = playlist_ItemGetById( p_playlist, p_add->i_item );
input_item_t* p_item = p_pitem->p_input;
watch_thread_t* p_wt = p_ml->p_sys->p_watch;
vlc_mutex_lock( &p_wt->list_mutex );
/* Check if we are already watching this item */
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item->i_id == p_item->i_id )
{
p_elt->i_refs++;
vlc_mutex_unlock( &p_wt->list_mutex );
goto quit_playlistitemappend;
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
/* Add the the append queue */
vlc_mutex_lock( &p_wt->item_append_queue_lock );
p_wt->item_append_queue_count++;
p_wt->item_append_queue = realloc( p_wt->item_append_queue,
sizeof( input_item_t* ) * p_wt->item_append_queue_count );
vlc_gc_incref( p_item );
p_wt->item_append_queue[ p_wt->item_append_queue_count - 1 ] = p_item;
vlc_mutex_unlock( &p_wt->item_append_queue_lock );
quit_playlistitemappend:
return VLC_SUCCESS;
}
/**
* @brief Callback when item is deleted from playlist
*/
static int watch_PlaylistItemDeleted( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *data )
{
VLC_UNUSED( oldval );
VLC_UNUSED( p_this );
VLC_UNUSED( psz_var );
media_library_t* p_ml = ( media_library_t* ) data;
playlist_t* p_playlist = pl_Get( p_ml );
/* Luckily this works, because the item isn't deleted from PL, yet */
playlist_item_t* p_pitem = playlist_ItemGetById( p_playlist, newval.i_int );
input_item_t* p_item = p_pitem->p_input;
/* Find the new item and decrement its ref */
il_foreachlist( p_ml->p_sys->p_watch->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item->i_id == p_item->i_id )
{
p_elt->i_refs--;
break;
}
}
return VLC_SUCCESS;
}
/**
* @brief Callback when watched input item starts playing
* @note This will update playcount mainly
* TODO: Increment playcount on playing 50%(configurable)
*/
static int watch_PlaylistItemCurrent( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *data )
{
(void)p_this;
(void)oldval;
(void)newval;
media_library_t *p_ml = ( media_library_t* ) data;
input_item_t *p_item = NULL;
/* Get current input */
input_thread_t *p_input = pl_CurrentInput( p_ml );
p_item = p_input ? input_GetItem( p_input ) : NULL;
if( p_input )
vlc_object_release( p_input );
if( !p_item )
return VLC_EGENERIC;
if( item_list_updateInput( p_ml->p_sys->p_watch, p_item, true ) == 0 )
{
#ifndef NDEBUG
msg_Dbg( p_ml, "couldn't in watch_PlaylistItemCurrent(): (%s:%d)",
__FILE__, __LINE__ );
#endif
}
return VLC_SUCCESS;
}
/**
* @brief Update information in the DB for an input item
*
* @param p_ml this media library instance
* @param i_media_id may be 0 (but not recommended)
* @param p_item input item that was updated
* @param b_raise_count increment the played count
* @return result of UpdateMedia()
*/
static int watch_update_Item( media_library_t *p_ml,
int i_media_id, input_item_t *p_item,
bool b_raise_count, bool locked )
{
#ifndef NDEBUG
msg_Dbg( p_ml, "automatically updating media %d", i_media_id );
#endif
ml_media_t* p_media = item_list_mediaOfItem( p_ml->p_sys->p_watch, p_item, locked );
CopyInputItemToMedia( p_media, p_item );
ml_LockMedia( p_media );
p_media->i_played_count += b_raise_count ? 1 : 0;
ml_UnlockMedia( p_media );
int i_ret = UpdateMedia( p_ml, p_media );
/* Add the poster to the album */
ml_LockMedia( p_media );
if( p_media->i_album_id && p_media->psz_cover )
{
SetArtCover( p_ml, p_media->i_album_id, p_media->psz_cover );
}
ml_UnlockMedia( p_media );
return i_ret;
}
/**
* @brief Signals the watch system to update all medias
*/
void watch_Force_Update( media_library_t* p_ml )
{
vlc_mutex_lock( &p_ml->p_sys->p_watch->lock );
vlc_cond_signal( &p_ml->p_sys->p_watch->cond );
vlc_mutex_unlock( &p_ml->p_sys->p_watch->lock );
}
/**
* @brief Loop on the item_list: old objects collector and automatic updater
*
* This function is *not* a garbage collector. It actually decrefs items
* when they are too old. ITEM_GC_MAX_AGE is the maximum 'time' an item
* can stay in the list. After that, it is gc_decref'ed but not removed
* from this list. If you try to get it after that, either the input item
* is still alive, then you get it, or you'll have
*
* The update of an item is done when its age is >= ITEM_LOOP_UPDATE
* (0 could lead to a too early update)
*
* A thread should call this function every N seconds
*
* @param p_ml the media library instance
*/
static void watch_loop( media_library_t *p_ml, bool b_force )
{
/* Do the garbage collection */
pool_GC( p_ml );
/* Process the append queue */
watch_ProcessAppendQueue( p_ml );
/* Do the item update if necessary */
vlc_mutex_lock( &p_ml->p_sys->p_watch->list_mutex );
item_list_t *p_prev = NULL;
il_foreachhashlist( p_ml->p_sys->p_watch->p_hlist, p_elt, ixx )
{
if( ( p_elt->i_update && p_elt->i_age >= ITEM_LOOP_UPDATE )
|| b_force )
{
/* This is the automatic delayed update */
watch_update_Item( p_ml, p_elt->i_media_id, p_elt->p_item,
( p_elt->i_update & 2 ) ? true : false, true );
/* The item gets older */
p_prev = p_elt;
p_elt->i_age++;
p_elt->i_update = false;
}
else if( p_elt->i_refs == 0 )
{
if( p_elt->i_update )
watch_update_Item( p_ml, p_elt->i_media_id, p_elt->p_item,
( p_elt->i_update & 2 ) ? true : false, true );
__watch_del_Item( p_ml, p_elt->p_item, true );
/* TODO: Do something about below crazy hack */
if( p_prev != NULL )
p_elt = p_prev;
else
{
ixx--;
break;
}
}
else
{
p_prev = p_elt;
p_elt->i_age++;
}
}
vlc_mutex_unlock( &p_ml->p_sys->p_watch->list_mutex );
}
/**
* This function goes through a queue of input_items and checks
* if they are present in ML. All the items we wish to add in the
* watch Queue
*/
static void watch_ProcessAppendQueue( media_library_t* p_ml )
{
watch_thread_t* p_wt = p_ml->p_sys->p_watch;
vlc_mutex_lock( &p_wt->item_append_queue_lock );
bool b_add = var_CreateGetBool( p_ml, "ml-auto-add" );
for( int i = 0; i < p_wt->item_append_queue_count; i++ )
{
input_item_t* p_item = p_wt->item_append_queue[i];
ml_media_t* p_media = NULL;
/* Is this item in ML? */
int i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
int i_ret = 0;
if( i_media_id <= 0 )
{
if( b_add )
{
i_ret = AddInputItem( p_ml, p_item );
/* FIXME: Need to skip? */
if( i_ret != VLC_SUCCESS )
continue;
i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
}
else
continue;
}
vlc_mutex_lock( &p_wt->list_mutex );
p_media = media_New( p_ml, i_media_id, ML_MEDIA, true );
if( p_media == NULL )
{
vlc_mutex_unlock( &p_wt->list_mutex );
continue;
}
/* If duplicate, then it just continues */
i_ret = __watch_add_Item( p_ml, p_item, p_media, true );
if( i_ret != VLC_SUCCESS )
{
ml_gc_decref( p_media );
vlc_mutex_unlock( &p_wt->list_mutex );
continue;
}
/* Find the new item and increment its ref */
il_foreachlist( p_wt->p_hlist[ item_hash( p_item ) ], p_elt )
{
if( p_elt->p_item->i_id == p_item->i_id )
{
p_elt->i_refs++;
break;
}
}
vlc_mutex_unlock( &p_wt->list_mutex );
ml_gc_decref( p_media );
}
p_wt->item_append_queue_count = 0;
FREENULL( p_wt->item_append_queue );
vlc_mutex_unlock( &p_wt->item_append_queue_lock );
}
/*****************************************************************************
* sql_add.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"
/*****************************************************************************
* ADD FUNCTIONS
*****************************************************************************/
/**
* @brief Add element to ML based on a ml_media_t (media ID ignored)
* @param p_ml This media_library_t object
* @param p_media media item to add in the DB. The media_id is ignored
* @return VLC_SUCCESS or VLC_EGENERIC
* @note This function is threadsafe
*/
int AddMedia( media_library_t *p_ml, ml_media_t *p_media )
{
int i_ret = VLC_SUCCESS;
int i_album_artist = 0;
Begin( p_ml );
ml_LockMedia( p_media );
assert( p_media->i_id == 0 );
/* Add any people */
ml_person_t* person = p_media->p_people;
while( person )
{
if( person->i_id <= 0 )
{
if( person->psz_name )
{
person->i_id = ml_GetInt( p_ml, ML_PEOPLE_ID, person->psz_role,
ML_PEOPLE, person->psz_role,
person->psz_name );
if( person->i_id <= 0 )
{
/* Create person */
AddPeople( p_ml, person->psz_name, person->psz_role );
person->i_id = ml_GetInt( p_ml, ML_PEOPLE_ID, person->psz_role,
ML_PEOPLE, person->psz_role,
person->psz_name );
}
}
}
if( strcmp( person->psz_role, ML_PERSON_ALBUM_ARTIST ) == 0 )
i_album_artist = person->i_id;
person = person->p_next;
}
/* Album id */
if( p_media->i_album_id <= 0 )
{
if( p_media->psz_album )
{
/* TODO:Solidly incorporate Album artist */
int i_album_id = ml_GetAlbumId( p_ml, p_media->psz_album );
if( i_album_id <= 0 )
{
/* Create album */
i_ret = AddAlbum( p_ml, p_media->psz_album, p_media->psz_cover,
i_album_artist );
if( i_ret != VLC_SUCCESS )
return i_ret;
i_album_id = ml_GetAlbumId( p_ml, p_media->psz_album );
if( i_album_id <= 0 )
return i_ret;
}
p_media->i_album_id = i_album_id;
}
}
if( !p_media->psz_uri || !*p_media->psz_uri )
{
msg_Dbg( p_ml, "cannot add a media without uri (%s)", __func__ );
return VLC_EGENERIC;
}
i_ret = QuerySimple( p_ml,
"INSERT INTO media ( uri, title, original_title, genre, type, "
"comment, cover, preview, year, track, disc, album_id, vote, score, "
"duration, first_played, played_count, last_played, "
"skipped_count, last_skipped, import_time, filesize ) "
"VALUES ( %Q, %Q, %Q, %Q, '%d',%Q, %Q, %Q, '%d', '%d', '%d', '%d',"
"'%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d' )",
p_media->psz_uri,
p_media->psz_title,
p_media->psz_orig_title,
p_media->psz_genre,
(int)p_media->i_type,
p_media->psz_comment,
p_media->psz_cover,
p_media->psz_preview,
(int)p_media->i_year,
(int)p_media->i_track_number,
(int)p_media->i_disc_number,
(int)p_media->i_album_id,
(int)p_media->i_vote,
(int)p_media->i_score,
(int)p_media->i_duration,
(int)p_media->i_first_played,
(int)p_media->i_played_count,
(int)p_media->i_last_played,
(int)p_media->i_skipped_count,
(int)p_media->i_last_skipped,
(int)p_media->i_import_time,
(int)p_media->i_filesize );
if( i_ret != VLC_SUCCESS )
goto quit_addmedia;
int id = GetMediaIdOfURI( p_ml, p_media->psz_uri );
if( id <= 0 )
{
i_ret = VLC_EGENERIC;
goto quit_addmedia;
}
p_media->i_id = id;
person = p_media->p_people;
if( !person )
{
/* If there is no person, set it to "Unknown", ie. people_id=0 */
i_ret = QuerySimple( p_ml, "INSERT into media_to_people ( media_id, "
"people_id ) VALUES ( %d, %d )",
id, 0 );
if( i_ret != VLC_SUCCESS )
goto quit_addmedia;
} else {
while( person )
{
i_ret = QuerySimple( p_ml, "INSERT into media_to_people ( media_id, "
"people_id ) VALUES ( %d, %d )",
id, person->i_id );
if( i_ret != VLC_SUCCESS )
goto quit_addmedia;
person = person->p_next;
}
}
i_ret = QuerySimple( p_ml, "INSERT into extra ( id, extra, language, bitrate, "
"samplerate, bpm ) VALUES ( '%d', %Q, %Q, '%d', '%d', '%d' )",
id, p_media->psz_extra, p_media->psz_language,
p_media->i_bitrate, p_media->i_samplerate, p_media->i_bpm );
if( i_ret != VLC_SUCCESS )
goto quit_addmedia;
i_ret = pool_InsertMedia( p_ml, p_media, true );
quit_addmedia:
if( i_ret == VLC_SUCCESS )
{
Commit( p_ml );
}
else
Rollback( p_ml );
ml_UnlockMedia( p_media );
if( i_ret == VLC_SUCCESS )
var_SetInteger( p_ml, "media-added", id );
return i_ret;
}
/**
* @brief Add generic album to ML
*
* @param p_ml this Media Library
* @param psz_title album title, cannot be null
* @param psz_cover album cover, can be null
* @return VLC_SUCCESS or a VLC error code
*
* This will add a new in the album table, without checking if album is
* already present (or another album with same title)
*/
int AddAlbum( media_library_t *p_ml, const char *psz_title,
const char *psz_cover, const int i_album_artist )
{
assert( p_ml );
if( !psz_title || !*psz_title )
{
msg_Warn( p_ml, "tried to add an album without title" );
return VLC_EGENERIC;
}
msg_Dbg( p_ml, "New album: '%s'", psz_title );
int i_ret = QuerySimple( p_ml,
"INSERT INTO album ( title, cover, album_artist_id ) "
"VALUES ( %Q, %Q, '%d' )",
psz_title , psz_cover, i_album_artist );
return i_ret;
}
/**
* @brief Add generic people to ML
*
* @param p_ml this Media Library
* @param psz_title name
* @param i_role role: 1 for artist, 2 for publisher
* @return VLC_SUCCESS or a VLC error code
*
* This will add a new in the album table, without checking if album is
* already present (or another album with same title)
*/
int AddPeople( media_library_t *p_ml, const char *psz_name,
const char* psz_role )
{
assert( p_ml );
assert( psz_role && *psz_role );
if( !psz_name || !*psz_name )
{
msg_Warn( p_ml, "tried to add an artist without name" );
return VLC_EGENERIC;
}
msg_Dbg( p_ml, "New people: (%s) '%s'", psz_role, psz_name );
int i_ret = QuerySimple( p_ml,
"INSERT INTO people ( name, role ) "
"VALUES ( %Q, %Q )",
psz_name, psz_role );
return i_ret;
}
/**
* @brief Add element to ML based on an Input Item
* @param p_ml This media_library_t object
* @param p_input input item to add
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int AddInputItem( media_library_t *p_ml, input_item_t *p_input )
{
assert( p_ml );
if( !p_input || !p_input->psz_uri )
return VLC_EGENERIC;
int i_ret = VLC_SUCCESS;
vlc_gc_incref( p_input );
/* Check input item is not already in the ML */
i_ret = GetMediaIdOfInputItem( p_ml, p_input );
if( i_ret > 0 )
{
msg_Dbg( p_ml, "Item already in Media Library (id: %d)", i_ret );
vlc_gc_decref( p_input );
return VLC_SUCCESS;
}
ml_media_t* p_media = media_New( p_ml, 0, ML_MEDIA, false );
/* Add media to the database */
CopyInputItemToMedia( p_media, p_input );
i_ret = AddMedia( p_ml, p_media );
if( i_ret == VLC_SUCCESS )
watch_add_Item( p_ml, p_input, p_media );
ml_gc_decref( p_media );
vlc_gc_decref( p_input );
return i_ret;
}
/**
* @brief Add element to ML based on a Playlist Item
*
* @param p_ml the media library object
* @param p_playlist_item playlist_item to add
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int AddPlaylistItem( media_library_t *p_ml, playlist_item_t *p_playlist_item )
{
if( !p_playlist_item )
return VLC_EGENERIC;
return AddInputItem( p_ml, p_playlist_item->p_input );
}
/*****************************************************************************
* sql_delete.c: SQL-based media library: all database delete functions
*****************************************************************************
* 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sql_media_library.h"
/**
* @brief Generic DELETE function for many medias
* Delete a media and all its referencies which don't point
* an anything else.
*
* @param p_ml This media_library_t object
* @param p_array list of ids to delete
* @return VLC_SUCCESS or VLC_EGENERIC
* TODO: Expand to delete media/artist/album given any params
*/
int Delete( media_library_t *p_ml, vlc_array_t *p_array )
{
char *psz_idlist = NULL, *psz_tmp = NULL;
int i_return = VLC_ENOMEM;
int i_rows = 0, i_cols = 0;
char **pp_results = NULL;
if( vlc_array_count( p_array ) <= 0 )
{
i_return = VLC_SUCCESS;
goto quit_delete_final;
}
for( int i = 0; i < vlc_array_count( p_array ); i++ )
{
ml_element_t* find = ( ml_element_t * )
vlc_array_item_at_index( p_array, i );
assert( find->criteria == ML_ID );
if( !psz_idlist )
{
if( asprintf( &psz_tmp, "( %d", find->value.i ) == -1)
{
goto quit_delete_final;
}
}
else
{
if( asprintf( &psz_tmp, "%s, %d", psz_idlist,
find->value.i ) == -1)
{
goto quit_delete_final;
}
}
free( psz_idlist );
psz_idlist = psz_tmp;
psz_tmp = NULL;
}
free( psz_tmp );
if( asprintf( &psz_tmp, "%s )", psz_idlist ? psz_idlist : "(" ) == -1 )
{
goto quit_delete_final;
}
psz_idlist = psz_tmp;
psz_tmp = NULL;
msg_Dbg( p_ml, "Multi Delete id list: %s", psz_idlist );
/**
* Below ensures you are emitting media-deleted only
* for existant media
*/
Begin( p_ml );
i_return = Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT id FROM media WHERE id IN %s", psz_idlist );
if( i_return != VLC_SUCCESS )
goto quit;
i_return = QuerySimple( p_ml,
"DELETE FROM media WHERE media.id IN %s", psz_idlist );
if( i_return != VLC_SUCCESS )
goto quit;
i_return = QuerySimple( p_ml,
"DELETE FROM extra WHERE extra.id IN %s", psz_idlist );
if( i_return != VLC_SUCCESS )
goto quit;
quit:
if( i_return == VLC_SUCCESS )
{
Commit( p_ml );
/* Emit delete on var media-deleted */
for( int i = 1; i <= i_rows; i++ )
{
var_SetInteger( p_ml, "media-deleted", atoi( pp_results[i*i_cols] ) );
}
}
else
Rollback( p_ml );
quit_delete_final:
FreeSQLResult( p_ml, pp_results );
free( psz_tmp );
free( psz_idlist );
return i_return;
}
/*****************************************************************************
* 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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( input_item_t **pp_item,
ml_media_t *p_media );
struct ml_table_elt
{
int column_id;
const char* column_name;
};
static int compare_ml_elts( const void *a, const void *b )
{
return strcmp( ( (struct ml_table_elt* )a )->column_name,
( ( struct ml_table_elt* )b )->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_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",
MEDIA_LIBRARY_PATH_TEXT, MEDIA_LIBRARY_PATH_LONGTEXT, false )
add_string( "ml-username", "", N_( "Username for the database" ),
N_( "Username for the database" ), false )
add_string( "ml-password", "", N_( "Password for the database" ),
N_( "Password for the database" ), false )
add_integer( "ml-port", 0,
N_( "Port for the database" ), N_("Port for the database"), false )
add_bool( "ml-recursive-scan", true, RECURSIVE_TEXT,
RECURSIVE_LONGTEXT, false )
add_bool( "ml-auto-add", true, N_("Auto add new medias"),
N_( "Automatically add new medias to ML" ), false )
add_bool( "ml-synchronous", true, N_("Use transactions"),
N_( "Disabling transactions saves I/O but can corrupt database in case of crash" ), 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 */
if ( InitDatabase( p_ml ) != VLC_SUCCESS )
{
vlc_mutex_destroy( &p_ml->p_sys->lock );
free( p_ml->p_sys );
return VLC_EGENERIC;
}
/* 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 )
{
vlc_array_destroy( p_intermediate_array );
return VLC_ENOMEM;
}
const int count = sizeof( ml_table_map )/ sizeof( struct ml_table_elt );
for( int col = 0; col < i_cols; col++ )
{
struct ml_table_elt key, *result = NULL;
key.column_name = res( 0, col );
result = bsearch( &key, ml_table_map, count,
sizeof( struct ml_table_elt ), compare_ml_elts );
if( !result )
msg_Warn( p_ml, "unknown column: %s", res( 0, col ) );
else
indexes[col] = result->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 );
i_ret = VLC_ENOMEM;
goto quit_sqlmediaarray;
}
p_result = ( ml_result_t * ) calloc( 1, sizeof( ml_result_t ) );
if( !p_result )
{
ml_gc_decref( p_media );
free( indexes );
i_ret = VLC_ENOMEM;
goto quit_sqlmediaarray;
}
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 */
{
free( p_result );
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;
i_ret = QuerySimple( p_ml, "CREATE INDEX album_title_index ON album (title);" );
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;
i_ret = QuerySimple( p_ml, "CREATE INDEX media_ui_index ON media (uri);" );
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 Journal and synchronous disc and writes
*
* @param p_ml media library object
* @param b_sync boolean
* @return <= 0 on error.
*/
static int SetSynchronous( media_library_t *p_ml, bool b_sync )
{
int i_rows, i_cols;
char **pp_results;
int i_return;
if ( b_sync )
i_return = Query( p_ml, &pp_results, &i_rows, &i_cols,
"PRAGMA synchronous = ON;PRAGMA journal_mode = TRUNCATE" );
else
i_return = Query( p_ml, &pp_results, &i_rows, &i_cols,
"PRAGMA synchronous = OFF;PRAGMA journal_mode = MEMORY" );
if( i_return != VLC_SUCCESS )
i_return = -1;
else
i_return = atoi( pp_results[ 1 ] );
FreeSQLResult( p_ml, pp_results );
return i_return;
}
/**
* @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;
bool b_sync = false;
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" );
b_sync = config_GetInt( p_ml, "ml-synchronous" );
/* 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 )
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
SetSynchronous( p_ml, b_sync );
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 && 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_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 = input_item_GetURI( p_item );
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 && !strncmp( p_media->psz_uri, "http", 4 ) )
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 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( input_item_t **pp_item,
ml_media_t *p_media )
{
*pp_item = input_item_New( 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
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <sys/stat.h>
#include <vlc_common.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 "item_list.h"
/*****************************************************************************
* 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
{
media_library_t *p_ml;
vlc_thread_t thread;
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 */
/*****************************************************************************
* sql_monitor.c: SQL-based media library: directory scanning and monitoring
*****************************************************************************
* Copyright (C) 2008-2010 the VideoLAN team and AUTHORS
* $Id$
*
* Authors: Antoine Lejeune <phytos@videolan.org>
* Jean-Philippe André <jpeg@videolan.org>
* Rémi Duraffort <ivoire@videolan.org>
* Adrien Maglo <magsoft@videolan.org>
* Srikanth Raju <srikiraju at gmail dot com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/** **************************************************************************
* MONITORING AND DIRECTORY SCANNING FUNCTIONS
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sql_media_library.h"
#include "vlc_playlist.h"
#include "vlc_url.h"
#include "vlc_fs.h"
static const char* ppsz_MediaExtensions[] =
{ EXTENSIONS_AUDIO_CSV, EXTENSIONS_VIDEO_CSV, NULL };
/* Monitoring and directory scanning private functions */
typedef struct stat_list_t stat_list_t;
typedef struct preparsed_item_t preparsed_item_t;
static void UpdateLibrary( monitoring_thread_t *p_mon );
static void ScanFiles( monitoring_thread_t *, int, bool, stat_list_t *stparent );
static int Sort( const char **, const char ** );
/* Struct used to verify there are no recursive directory */
struct stat_list_t
{
stat_list_t *parent;
struct stat st;
};
struct preparsed_item_t
{
monitoring_thread_t *p_mon;
char* psz_uri;
int i_dir_id;
int i_mtime;
int i_update_id;
bool b_update;
};
/**
* @brief Remove a directory to monitor
* @param p_ml A media library object
* @param psz_dir the directory to remove
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int RemoveDirToMonitor( media_library_t *p_ml, const char *psz_dir )
{
assert( p_ml );
char **pp_results = NULL;
int i_cols = 0, i_rows = 0, i_ret = VLC_SUCCESS;
int i;
bool b_recursive = var_CreateGetBool( p_ml, "ml-recursive-scan" );
if( b_recursive )
{
i_ret = Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT media.id FROM media JOIN directories ON "
"(media.directory_id = directories.id) WHERE "
"directories.uri LIKE '%q%%'",
psz_dir );
if( i_ret != VLC_SUCCESS )
{
msg_Err( p_ml, "Error occurred while making a query to the database" );
return i_ret;
}
QuerySimple( p_ml, "DELETE FROM directories WHERE uri LIKE '%q%%'",
psz_dir );
}
else
{
i_ret = Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT media.id FROM media JOIN directories ON "
"(media.directory_id = directories.id) WHERE "
"directories.uri = %Q",
psz_dir );
if( i_ret != VLC_SUCCESS )
{
msg_Err( p_ml, "Error occurred while making a query to the database" );
return i_ret;
}
QuerySimple( p_ml, "DELETE FROM directories WHERE uri = %Q",
psz_dir );
}
vlc_array_t *p_where = vlc_array_new();
for( i = 1; i <= i_rows; i++ )
{
int id = atoi( pp_results[i*i_cols] );
ml_element_t* p_find = ( ml_element_t * ) calloc( 1, sizeof( ml_element_t ) );
p_find->criteria = ML_ID;
p_find->value.i = id;
vlc_array_append( p_where, p_find );
}
Delete( p_ml, p_where );
FreeSQLResult( p_ml, pp_results );
for( i = 0; i < vlc_array_count( p_where ); i++ )
{
free( vlc_array_item_at_index( p_where, i ) );
}
vlc_array_destroy( p_where );
return VLC_SUCCESS;
}
/**
* @brief Get the list of the monitored directories
* @param p_ml A media library object
* @param p_array An initialized array where the list will be put in
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int ListMonitoredDirs( media_library_t *p_ml, vlc_array_t *p_array )
{
char **pp_results;
int i_cols, i_rows;
int i;
if( Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT uri AS directory_uri FROM directories WHERE recursive=0" )
!= VLC_SUCCESS )
return VLC_EGENERIC;
for( i = 1; i <= i_rows; i++ )
{
vlc_array_append( p_array, strdup( pp_results[i] ) );
}
FreeSQLResult( p_ml, pp_results );
return VLC_SUCCESS;
}
/**
* @brief Add a directory to monitor
* @param p_ml This media_library_t object
* @param psz_dir the directory to add
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int AddDirToMonitor( media_library_t *p_ml, const char *psz_dir )
{
assert( p_ml );
/* Verify if we can open the directory */
DIR *dir = vlc_opendir( psz_dir );
if( !dir )
{
int err = errno;
if( err != ENOTDIR )
msg_Err( p_ml, "%s: %m", psz_dir );
else
msg_Dbg( p_ml, "`%s' is not a directory", psz_dir );
errno = err;
return VLC_EGENERIC;
}
closedir( dir );
msg_Dbg( p_ml, "Adding directory `%s' to be monitored", psz_dir );
QuerySimple( p_ml, "INSERT INTO directories ( uri, timestamp, "
"recursive ) VALUES( %Q, 0, 0 )", psz_dir );
vlc_cond_signal( &p_ml->p_sys->p_mon->wait );
return VLC_SUCCESS;
}
static int Sort( const char **a, const char **b )
{
#ifdef HAVE_STRCOLL
return strcoll( *a, *b );
#else
return strcmp( *a, *b );
#endif
}
/**
* @brief Directory Monitoring thread loop
*/
void *RunMonitoringThread( void *p_this )
{
monitoring_thread_t *p_mon = (monitoring_thread_t*) p_this;
vlc_cond_init( &p_mon->wait );
vlc_mutex_init( &p_mon->lock );
var_Create( p_mon, "ml-recursive-scan", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
while( vlc_object_alive( p_mon ) )
{
vlc_mutex_lock( &p_mon->lock );
/* Update */
UpdateLibrary( p_mon );
/* We wait MONITORING_DELAY seconds or wait that the media library
signals us to do something */
vlc_cond_timedwait( &p_mon->wait, &p_mon->lock,
mdate() + 1000000*MONITORING_DELAY );
vlc_mutex_unlock( &p_mon->lock );
}
vlc_cond_destroy( &p_mon->wait );
vlc_mutex_destroy( &p_mon->lock );
return NULL;
}
/**
* @brief Update library if new files found or updated
*/
static void UpdateLibrary( monitoring_thread_t *p_mon )
{
int i_rows, i_cols, i;
char **pp_results;
media_library_t *p_ml = p_mon->p_ml;
struct stat s_stat;
bool b_recursive = var_GetBool( p_mon, "ml-recursive-scan" );
msg_Dbg( p_mon, "Scanning directories" );
Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT id AS directory_id, uri AS directory_uri, "
"timestamp AS directory_ts FROM directories" );
msg_Dbg( p_mon, "%d directories to scan", i_rows );
for( i = 1; i <= i_rows; i++ )
{
int id = atoi( pp_results[i*i_cols] );
char *psz_dir = pp_results[i*i_cols+1];
int timestamp = atoi( pp_results[i*i_cols+2] );
if( vlc_stat( psz_dir, &s_stat ) == -1 )
{
int err = errno;
if( err == ENOTDIR || err == ENOENT )
{
msg_Dbg( p_mon, "Removing `%s'", psz_dir );
RemoveDirToMonitor( p_ml, psz_dir );
}
else
{
msg_Err( p_mon, "%s: %m", psz_dir );
FreeSQLResult( p_ml, pp_results );
return;
}
errno = err;
}
if( !S_ISDIR( s_stat.st_mode ) )
{
msg_Dbg( p_mon, "Removing `%s'", psz_dir );
RemoveDirToMonitor( p_ml, psz_dir );
}
if( timestamp < s_stat.st_mtime )
{
msg_Dbg( p_mon, "Adding `%s'", psz_dir );
ScanFiles( p_mon, id, b_recursive, NULL );
}
}
FreeSQLResult( p_ml, pp_results );
}
/**
* @brief Callback for input item preparser to directory monitor
*/
static void PreparseComplete( const vlc_event_t * p_event, void *p_data )
{
int i_ret = VLC_SUCCESS;
preparsed_item_t* p_itemobject = (preparsed_item_t*) p_data;
monitoring_thread_t *p_mon = p_itemobject->p_mon;
media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
input_item_t *p_input = (input_item_t*) p_event->p_obj;
if( input_item_IsPreparsed( p_input ) )
{
if( p_itemobject->b_update )
{
//TODO: Perhaps we don't have to load everything?
ml_media_t* p_media = GetMedia( p_ml, p_itemobject->i_update_id,
ML_MEDIA_SPARSE, true );
CopyInputItemToMedia( p_media, p_input );
i_ret = UpdateMedia( p_ml, p_media );
ml_gc_decref( p_media );
}
else
i_ret = AddInputItem( p_ml, p_input );
}
if( i_ret != VLC_SUCCESS )
msg_Dbg( p_mon, "Item could not be correctly added"
" or updated during scan: %s", p_input->psz_uri );
QuerySimple( p_ml, "UPDATE media SET directory_id=%d, timestamp=%d "
"WHERE id=%d",
p_itemobject->i_dir_id, p_itemobject->i_mtime,
GetMediaIdOfURI( p_ml, p_input->psz_uri ) );
vlc_event_detach( &p_input->event_manager, vlc_InputItemPreparsedChanged,
PreparseComplete, p_itemobject );
vlc_gc_decref( p_input );
free( p_itemobject->psz_uri );
}
/**
* @brief Scan files in a particular directory
*/
static void ScanFiles( monitoring_thread_t *p_mon, int i_dir_id,
bool b_recursive, stat_list_t *stparent )
{
int i_rows, i_cols, i_dir_content, i, i_mon_rows, i_mon_cols;
char **ppsz_monitored_files;
char **pp_results, *psz_dir;
char **pp_dir_content;
bool *pb_processed;
input_item_t *p_input;
struct stat s_stat;
media_library_t *p_ml = (media_library_t *)p_mon->p_ml;
Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT uri AS directory_uri FROM directories WHERE id = '%d'",
i_dir_id );
if( i_rows < 1 )
{
msg_Dbg( p_mon, "query returned no directory for dir_id: %d (%s:%d)",
i_dir_id, __FILE__, __LINE__ );
return;
}
psz_dir = strdup( pp_results[1] );
FreeSQLResult( p_ml, pp_results );
struct stat_list_t stself;
if( vlc_stat( psz_dir, &stself.st ) == -1 )
{
msg_Err( p_ml, "Cannot stat `%s': %m", psz_dir );
free( psz_dir );
return;
}
#ifndef WIN32
for( stat_list_t *stats = stparent; stats != NULL; stats = stats->parent )
{
if( ( stself.st.st_ino == stats->st.st_ino ) &&
( stself.st.st_dev == stats->st.st_dev ) )
{
msg_Warn( p_ml, "Ignoring infinitely recursive directory `%s'",
psz_dir );
free( psz_dir );
return;
}
}
#else
/* Windows has st_dev (driver letter - 'A'), but it zeroes st_ino,
* so that the test above will always incorrectly succeed.
* Besides, Windows does not have dirfd(). */
#endif
stself.parent = stparent;
QuerySimple( p_ml, "UPDATE directories SET timestamp=%d WHERE id = %d",
stself.st.st_mtime, i_dir_id );
Query( p_ml, &ppsz_monitored_files, &i_mon_rows, &i_mon_cols,
"SELECT id AS media_id, timestamp AS media_ts, uri AS media_uri "
"FROM media WHERE directory_id = %d",
i_dir_id );
pb_processed = malloc(sizeof(bool) * i_mon_rows);
for( i = 0; i < i_mon_rows ; i++)
pb_processed[i] = false;
i_dir_content = vlc_scandir( psz_dir, &pp_dir_content, NULL, Sort );
if( i_dir_content == -1 )
{
msg_Err( p_mon, "Cannot read `%s': %m", psz_dir );
free( pb_processed );
free( psz_dir );
return;
}
else if( i_dir_content == 0 )
{
msg_Dbg( p_mon, "Nothing in directory `%s'", psz_dir );
free( pb_processed );
free( psz_dir );
return;
}
for( i = 0; i < i_dir_content; i++ )
{
const char *psz_entry = pp_dir_content[i];
if( psz_entry[0] != '.' )
{
/* 7 is the size of "file://" */
char psz_uri[strlen(psz_dir) + strlen(psz_entry) + 2 + 7];
sprintf( psz_uri, "%s/%s", psz_dir, psz_entry );
if( vlc_stat( psz_uri, &s_stat ) == -1 )
{
msg_Err( p_mon, "%s: %m", psz_uri );
free( pb_processed );
free( psz_dir );
return;
}
if( S_ISREG( s_stat.st_mode ) )
{
const char *psz_dot = strrchr( psz_uri, '.' );
if( psz_dot++ && *psz_dot )
{
int i_is_media = 0;
for( int a = 0; ppsz_MediaExtensions[a]; a++ )
{
if( !strcasecmp( psz_dot, ppsz_MediaExtensions[a] ) )
{
i_is_media = 1;
break;
}
}
if( !i_is_media )
{
msg_Dbg( p_mon, "ignoring file %s", psz_uri );
continue;
}
}
char * psz_tmp = encode_URI_component( psz_uri );
char * psz_encoded_uri = ( char * )calloc( strlen( psz_tmp ) + 9, 1 );
strcpy( psz_encoded_uri, "file:///" );
strcat( psz_encoded_uri, psz_tmp );
free( psz_tmp );
/* Check if given media is already in DB and it has been updated */
bool b_skip = false;
bool b_update = false;
int j = 1;
for( j = 1; j <= i_mon_rows; j++ )
{
if( strcasecmp( ppsz_monitored_files[ j * i_mon_cols + 2 ],
psz_encoded_uri ) != 0 )
continue;
b_update = true;
pb_processed[ j - 1 ] = true;
if( atoi( ppsz_monitored_files[ j * i_mon_cols + 1 ] )
< s_stat.st_mtime )
{
b_skip = false;
break;
}
else
{
b_skip = true;
break;
}
}
msg_Dbg( p_ml , "Checking if %s is in DB. Found: %d", psz_encoded_uri,
b_skip? 1 : 0 );
if( b_skip )
continue;
p_input = input_item_New( psz_encoded_uri, psz_entry );
playlist_t* p_pl = pl_Get( p_mon );
preparsed_item_t* p_itemobject;
p_itemobject = malloc( sizeof( preparsed_item_t ) );
p_itemobject->i_dir_id = i_dir_id;
p_itemobject->psz_uri = psz_encoded_uri;
p_itemobject->i_mtime = s_stat.st_mtime;
p_itemobject->p_mon = p_mon;
p_itemobject->b_update = b_update;
p_itemobject->i_update_id = b_update ?
atoi( ppsz_monitored_files[ j * i_mon_cols + 0 ] ) : 0 ;
vlc_event_manager_t *p_em = &p_input->event_manager;
vlc_event_attach( p_em, vlc_InputItemPreparsedChanged,
PreparseComplete, p_itemobject );
playlist_PreparseEnqueue( p_pl, p_input );
}
else if( S_ISDIR( s_stat.st_mode ) && b_recursive )
{
Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT id AS directory_id FROM directories "
"WHERE uri=%Q", psz_uri );
FreeSQLResult( p_ml, pp_results );
if( i_rows <= 0 )
{
msg_Dbg( p_mon, "New directory `%s' in dir of id %d",
psz_uri, i_dir_id );
QuerySimple( p_ml,
"INSERT INTO directories (uri, timestamp, "
"recursive) VALUES(%Q, 0, 1)", psz_uri );
// We get the id of the directory we've just added
Query( p_ml, &pp_results, &i_rows, &i_cols,
"SELECT id AS directory_id FROM directories WHERE uri=%Q",
psz_uri );
if( i_rows <= 0 )
{
msg_Err( p_mon, "Directory `%s' was not sucessfully"
" added to the database", psz_uri );
FreeSQLResult( p_ml, pp_results );
continue;
}
ScanFiles( p_mon, atoi( pp_results[1] ), b_recursive,
&stself );
FreeSQLResult( p_ml, pp_results );
}
}
}
}
vlc_array_t* delete_ids = vlc_array_new();
for( i = 0; i < i_mon_rows; i++ )
{
if( !pb_processed[i] )
{
/* This file doesn't exist anymore. Let's...urm...delete it. */
ml_element_t* find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
find->criteria = ML_ID;
find->value.i = atoi( ppsz_monitored_files[ (i + 1) * i_mon_cols ] );
vlc_array_append( delete_ids, find );
}
}
/* Delete the unfound media */
if( Delete( p_ml, delete_ids ) != VLC_SUCCESS )
msg_Dbg( p_ml, "Something went wrong in multi delete" );
for( i = 0; i < vlc_array_count( delete_ids ); i++ )
{
free( vlc_array_item_at_index( delete_ids, i ) );
}
vlc_array_destroy( delete_ids );
FreeSQLResult( p_ml, ppsz_monitored_files );
for( i = 0; i < i_dir_content; i++ )
free( pp_dir_content[i] );
free( pp_dir_content );
free( psz_dir );
free( pb_processed );
}
/*****************************************************************************
* sql_search.c: SQL-based media library: all find/get functions
*****************************************************************************
* 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sql_media_library.h"
int Find( media_library_t *p_ml, vlc_array_t *p_result_array, ... )
{
va_list args;
int returned;
va_start( args, p_result_array );
returned = FindVa( p_ml, p_result_array, args );
va_end( args );
return returned;
}
/**
* @brief Generic find in Media Library, returns arrays of psz or int
*
* @param p_ml the media library object
* @param result A pointer to a result array
* @param criterias list of criterias used in SELECT
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int FindVa( media_library_t *p_ml,
vlc_array_t *p_result_array, va_list criterias )
{
int i_ret = VLC_SUCCESS;
char *psz_query;
ml_result_type_e result_type;
char **pp_results = NULL;
int i_cols, i_rows;
if( !p_result_array )
return VLC_EGENERIC;
i_ret = BuildSelectVa( p_ml, &psz_query, &result_type, criterias );
if( i_ret != VLC_SUCCESS )
return i_ret;
if( Query( p_ml, &pp_results, &i_rows, &i_cols, "%s", psz_query )
!= VLC_SUCCESS )
{
msg_Err( p_ml, "Error occurred while making the query to the database" );
return VLC_EGENERIC;
}
i_ret = SQLToResultArray( p_ml, p_result_array, pp_results, i_rows, i_cols,
result_type );
free( psz_query);
FreeSQLResult( p_ml, pp_results );
return i_ret;
}
/**
* @brief Generic find in Media Library, returns arrays of psz or int
*
* @param p_ml the media library object
* @param result a pointer to a result array
* @param selected_type the type of the element we're selecting
* @param criterias list of criterias used in SELECT
* @return VLC_SUCCESS or VLC_EGENERIC
*/
int FindAdv( media_library_t *p_ml, vlc_array_t *p_result_array,
ml_select_e selected_type, const char* psz_lvalue, ml_ftree_t *tree )
{
int i_ret = VLC_SUCCESS;
char *psz_query;
ml_result_type_e result_type;
char **pp_results = NULL;
int i_cols, i_rows;
if( !p_result_array )
return VLC_EGENERIC;
i_ret = BuildSelect( p_ml, &psz_query, &result_type, psz_lvalue,
selected_type, tree );
if( i_ret != VLC_SUCCESS )
return i_ret;
if( Query( p_ml, &pp_results, &i_rows, &i_cols, "%s", psz_query )
!= VLC_SUCCESS )
{
msg_Err( p_ml, "Error occurred while making the query to the database" );
return VLC_EGENERIC;
}
i_ret = SQLToResultArray( p_ml, p_result_array, pp_results, i_rows, i_cols,
result_type );
free( psz_query);
FreeSQLResult( p_ml, pp_results );
return i_ret;
}
/**
* @brief Generic SELECT query builder with va_list parameter
*
* @param p_ml This media_library_t object
* @param ppsz_query *ppsz_query will contain query
* @param p_result_type see enum ml_result_type_e
* @param criterias list of criterias used in SELECT
* @return VLC_SUCCESS or a VLC error code
* NOTE va_list criterias must end with ML_END or this will fail (segfault)
*
* This function handles results of only one column (or two if ID is included),
* of 'normal' types: int and strings
*/
int BuildSelectVa( media_library_t *p_ml, char **ppsz_query,
ml_result_type_e *p_result_type, va_list criterias )
{
int i_continue = 1;
ml_ftree_t* p_ftree = NULL;
char* psz_lvalue = NULL;
/* Get the name of the data we want */
ml_select_e selected_type = va_arg( criterias, int );
if( selected_type == ML_PEOPLE || selected_type == ML_PEOPLE_ID ||
selected_type == ML_PEOPLE_ROLE )
psz_lvalue = va_arg( criterias, char * );
/* Loop on every arguments */
while( i_continue )
{
ml_ftree_t *p_find = ( ml_ftree_t* ) calloc( 1, sizeof( ml_ftree_t ) );
if( !p_find )
return VLC_ENOMEM;
p_find->criteria = va_arg( criterias, int );
p_find->comp = ML_COMP_EQUAL;
switch( p_find->criteria )
{
case ML_SORT_ASC:
p_ftree = ml_FtreeSpecAsc( p_ftree, va_arg( criterias, char* ) ); break;
case ML_SORT_DESC:
p_ftree = ml_FtreeSpecDesc( p_ftree, va_arg( criterias, char* ) ); break;
case ML_DISTINCT:
p_ftree = ml_FtreeSpecDistinct( p_ftree ); break;
case ML_LIMIT:
p_ftree = ml_FtreeSpecLimit( p_ftree, va_arg( criterias, int ) );
break;
case ML_ARTIST:
/* This is OK because of a shallow free find */
p_find->lvalue.str = (char *)ML_PERSON_ARTIST;
p_find->value.str = va_arg( criterias, char* );
p_ftree = ml_FtreeFastAnd( p_ftree, p_find );
break;
case ML_PEOPLE:
p_find->lvalue.str = va_arg( criterias, char* );
p_find->value.str = va_arg( criterias, char* );
p_ftree = ml_FtreeFastAnd( p_ftree, p_find );
break;
case ML_PEOPLE_ID:
p_find->lvalue.str = va_arg( criterias, char* );
p_find->value.i = va_arg( criterias, int );
p_ftree = ml_FtreeFastAnd( p_ftree, p_find );
break;
case ML_END:
i_continue = 0;
break;
default:
switch( ml_AttributeIsString( p_find->criteria ) )
{
case 0:
p_find->value.i = va_arg( criterias, int );
break;
case 1:
p_find->value.str = va_arg( criterias, char* );
break;
}
p_ftree = ml_FtreeFastAnd( p_ftree, p_find );
break;
}
}
int i_ret = BuildSelect( p_ml, ppsz_query, p_result_type, psz_lvalue,
selected_type, p_ftree );
ml_ShallowFreeFindTree( p_ftree );
return i_ret;
}
/**
* @brief Append a string and format it using SQL vmprintf
**/
static int AppendStringFmtVa( media_library_t *p_ml,
char **ppsz_dst, const char *psz_fmt,
va_list args )
{
char *psz_tmp = NULL, *psz_fullexp = NULL;
assert( ppsz_dst != NULL );
if( !( *ppsz_dst ) )
{
/* New expression */
*ppsz_dst = sql_VPrintf( p_ml->p_sys->p_sql, psz_fmt, args );
if( !( *ppsz_dst ) )
return VLC_ENOMEM;
}
else
{
/* Create new expression B */
psz_tmp = sql_VPrintf( p_ml->p_sys->p_sql, psz_fmt, args );
if( !psz_tmp )
return VLC_ENOMEM;
if( asprintf( &psz_fullexp, "%s%s", *ppsz_dst, psz_tmp ) == -1 )
{
free( psz_tmp );
return VLC_ENOMEM;
}
free( *ppsz_dst );
*ppsz_dst = psz_fullexp;
}
return VLC_SUCCESS;
}
static int AppendStringFmt( media_library_t *p_ml,
char **ppsz_dst, const char *psz_fmt, ... )
{
va_list args;
va_start( args, psz_fmt );
int i_ret = AppendStringFmtVa( p_ml, ppsz_dst, psz_fmt, args );
va_end( args );
return i_ret;
}
/* Early Declaration of Where String Generator */
static int BuildWhere( media_library_t* p_ml, char **ppsz_where, ml_ftree_t* tree,
char** sort, int* limit, const char** distinct, char*** pppsz_frompersons,
int* i_frompersons, int* join );
# define table_media (1 << 0)
# define table_album (1 << 1)
# define table_people (1 << 2)
# define table_extra (1 << 3)
static void PackFromPersons( char*** pppsz_frompersons, int i_num_frompersons )
{
for( int i = 0; i < i_num_frompersons; i++ )
{
if( *pppsz_frompersons[i] == NULL )
continue;
for( int j = i+1; j < i_num_frompersons; j++ )
{
if( strcmp( *pppsz_frompersons[i], *pppsz_frompersons[j] ) == 0 )
{
*pppsz_frompersons[j] = NULL;
}
}
}
}
/**
* @brief Generic SELECT query builder
*
* @param p_ml This media_library_t object
* @param ppsz_query *ppsz_query will contain query
* @param p_result_type see enum ml_result_type_e
* @param selected_type the type of the element we're selecting
* @param tree the find tree
* @return VLC_SUCCESS or VLC_EGENERIC
*/
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 )
{
/* Basic verification */
if( !ppsz_query )
return VLC_EGENERIC;
int i_ret = VLC_SUCCESS;
char *psz_query = NULL;
/* Building psz_query :
psz_query = "SELECT psz_distinct psz_select
FROM psz_from [JOIN psz_join ON psz_on]
[JOIN psz_join2 ON psz_on2]
[WHERE psz_where[i] [AND psz_where[j] ...]]
[LIMIT psz_limit] [ORDER BY psz_select psz_sort] ;"
*/
char *psz_select = NULL;
const char *psz_distinct = ""; /* "DISTINCT" or "" */
/* FROM */
char *psz_from = NULL;
int i_from = 0; /* Main select table */
char **ppsz_frompersons = NULL;
int i_num_frompersons = 0;
char *psz_peoplerole = NULL; /* Person to get selected */
/* JOIN ... ON ... */
char *psz_join = NULL;
char *psz_join2 = NULL;
char *psz_on = NULL;
char *psz_on2 = NULL;
int i_join = 0; /* Tables that need to be joined */
/* String buffers */
char *psz_where = NULL;
char *psz_sort = NULL; /* ASC or DESC or NULL */
char *psz_tmp = NULL;
int i_limit = 0;
/* Build the WHERE condition */
BuildWhere( p_ml, &psz_where, tree, &psz_sort, &i_limit,
&psz_distinct, &ppsz_frompersons, &i_num_frompersons, &i_join );
PackFromPersons( &ppsz_frompersons, i_num_frompersons );
/* What is the result type? */
ml_result_type_e res_type = ML_TYPE_PSZ;
/* SELECT, FROM */
/* Note that a DISTINCT select makes id of result non sense */
switch( selected_type )
{
case ML_ALBUM:
psz_select = ( !*psz_distinct ) ?
strdup( "album.id, album.title AS album_title" )
: strdup( "album.title AS album_title" );
i_from = table_album;
break;
case ML_ALBUM_COVER:
psz_select = ( !*psz_distinct ) ?
strdup( "album.id, album.cover" ) : strdup( "album.cover" );
i_from = table_album;
break;
case ML_ALBUM_ID:
psz_select = strdup( "album.id" );
psz_distinct = "DISTINCT";
i_from = table_album;
res_type = ML_TYPE_INT;
break;
case ML_ARTIST:
psz_select = ( !*psz_distinct ) ?
strdup( "people_Artist.id, people_Artist.name" )
: strdup( "people_Artist.name" );
i_from = table_people;
psz_peoplerole = strdup( ML_PERSON_ARTIST );
break;
case ML_ARTIST_ID:
psz_select = strdup( "people_Artist.id" );
psz_distinct = "DISTINCT";
i_from = table_people;
res_type = ML_TYPE_INT;
psz_peoplerole = strdup( ML_PERSON_ARTIST );
break;
case ML_COVER:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.cover" ) : strdup( "media.cover" );
i_from = table_media;
break;
case ML_COMMENT:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, extra.comment" ) : strdup( "extra.comment" );
i_from = table_extra;
break;
case ML_GENRE:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.genre" ) : strdup( "media.genre" );
i_from = table_media;
break;
case ML_COUNT_MEDIA:
psz_select = ( !*psz_distinct ) ?
strdup( "COUNT()" ) : strdup( "COUNT( DISTINCT media.id )" );
i_from = table_media;
res_type = ML_TYPE_INT;
break;
case ML_COUNT_ALBUM:
psz_select = ( !*psz_distinct ) ?
strdup( "COUNT()" ) : strdup( "COUNT( DISTINCT album.id )" );
i_from = table_album;
res_type = ML_TYPE_INT;
break;
case ML_COUNT_PEOPLE:
psz_select = ( !*psz_distinct ) ?
strdup( "COUNT()" ) : strdup( "COUNT( DISTINCT people.id )" );
i_from = table_people;
res_type = ML_TYPE_INT;
break;
case ML_FILESIZE:
psz_select = strdup( "media.filesize" );
i_from = table_media;
res_type = ML_TYPE_INT;
break;
case ML_ID:
psz_select = strdup( "media.id" ); /* ID: must be distinct */
psz_distinct = "DISTINCT";
i_from = table_media;
res_type = ML_TYPE_INT;
break;
case ML_LANGUAGE:
psz_select = strdup( "extra.language" );
psz_distinct = "DISTINCT";
i_from = table_extra;
break;
case ML_MEDIA_SPARSE:
i_ret = AppendStringFmt( p_ml, &psz_select, "media.id AS id,"
"media.uri AS uri,"
"media.type AS type,"
"media.title AS title,"
"media.duration AS duration,"
"media.original_title AS original_title,"
"media.album_id AS album_id,"
"media.cover AS cover,"
"media.preview AS preview,"
"media.disc AS disc,"
"media.track AS track,"
"media.year AS year,"
"media.genre AS genre,"
"media.played_count AS played_count,"
"media.last_played AS last_played,"
"media.first_played AS first_played,"
"media.import_time AS import_time,"
"media.skipped_count AS skipped_count,"
"media.last_skipped AS last_skipped,"
"media.vote AS vote,"
"media.score AS score,"
"media.comment AS comment,"
"media.filesize AS filesize,"
"album.title AS album_title,"
"album.cover AS album_cover,"
"(SELECT name FROM media_to_people JOIN people "
"ON (people_id = id) WHERE media_id = media.id AND role = %Q LIMIT 1) AS people_%s",
ML_PERSON_ARTIST, ML_PERSON_ARTIST );
if( i_ret != VLC_SUCCESS )
goto exit;
i_from = table_media;
i_join |= ( table_album | table_people );
psz_distinct = "DISTINCT";
res_type = ML_TYPE_MEDIA;
break;
case ML_MEDIA:
/* Who said this was over-complicated ?? */
/* Yea right. */
psz_select = strdup( "media.id AS id,"
"media.uri AS uri,"
"media.type AS type,"
"media.title AS title,"
"media.duration AS duration,"
"media.original_title AS original_title,"
"media.album_id AS album_id,"
"media.cover AS cover,"
"media.preview AS preview,"
"media.disc as disc,"
"media.track AS track,"
"media.year AS year,"
"media.genre AS genre,"
"media.played_count AS played_count,"
"media.last_played AS last_played,"
"media.first_played AS first_played,"
"media.import_time AS import_time,"
"media.last_skipped AS last_skipped,"
"media.skipped_count AS skipped_count,"
"media.vote AS vote,"
"media.score AS score,"
"media.comment AS comment,"
"media.filesize AS filesize,"
"album.title AS album_title,"
"album.cover AS album_cover,"
"people.id AS people_id,"
"people.name AS people_name,"
"people.role AS people_role,"
"extra.language AS language,"
"extra.extra AS extra" );
i_from = table_media;
i_join |= ( table_album | table_people | table_extra );
psz_distinct = "DISTINCT";
res_type = ML_TYPE_MEDIA;
break;
case ML_MEDIA_EXTRA:
psz_select = strdup( "media.id AS id,"
"people.id AS people_id,"
"people.name AS people_name,"
"people.role AS people_role,"
"extra.extra AS extra,"
"extra.language AS language" );
i_from = table_media;
i_join |= ( table_album | table_people | table_extra );
psz_distinct = "DISTINCT";
res_type = ML_TYPE_MEDIA;
break;
case ML_ORIGINAL_TITLE:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.original_title" ) : strdup( "media.original_title" );
i_from = table_media;
break;
/* For people, if lvalue = "", then we want ANY people. */
case ML_PEOPLE:
assert( psz_selected_type_lvalue );
i_ret = AppendStringFmt( p_ml, &psz_select, "people%s%s.name",
*psz_selected_type_lvalue ? "_" : "",
*psz_selected_type_lvalue ? psz_selected_type_lvalue : "" );
if( i_ret != VLC_SUCCESS )
goto exit;
if( *psz_distinct )
{
i_ret = AppendStringFmt( p_ml, &psz_select, ", people%s%s.name",
*psz_selected_type_lvalue ? "_" : "",
*psz_selected_type_lvalue ? psz_selected_type_lvalue : "" );
if( i_ret != VLC_SUCCESS )
goto exit;
}
i_from = table_people;
psz_peoplerole = strdup( psz_selected_type_lvalue );
break;
case ML_PEOPLE_ID:
assert( psz_selected_type_lvalue );
i_ret = AppendStringFmt( p_ml, &psz_select, "people%s%s.id",
*psz_selected_type_lvalue ? "_" : "",
*psz_selected_type_lvalue ? psz_selected_type_lvalue : "" );
if( i_ret != VLC_SUCCESS )
goto exit;
if( *psz_distinct )
{
i_ret = AppendStringFmt( p_ml, &psz_select, ", people%s%s.id",
*psz_selected_type_lvalue ? "_" : "",
*psz_selected_type_lvalue ? psz_selected_type_lvalue : "" );
if( i_ret != VLC_SUCCESS )
goto exit;
}
psz_distinct = "DISTINCT";
i_from = table_people;
psz_peoplerole = strdup( psz_selected_type_lvalue );
res_type = ML_TYPE_INT;
break;
case ML_PEOPLE_ROLE:
psz_select = strdup( "people.role" );
psz_distinct = "DISTINCT";
i_from = table_people;
break;
case ML_TITLE:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.title" ) : strdup( "media.title" );
i_from = table_media;
break;
case ML_TYPE:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.type" ): strdup( "media.type" );
i_from = table_media;
res_type = ML_TYPE_INT;
break;
case ML_URI:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.uri" ) : strdup( "media.uri" );
i_from = table_media;
break;
case ML_VOTE:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.vote" ) : strdup( "media.vote" );
i_from = table_media;
res_type = ML_TYPE_INT;
break;
case ML_YEAR:
psz_select = ( !*psz_distinct ) ?
strdup( "media.id, media.year" ) : strdup( "media.year" );
i_from = table_media;
res_type = ML_TYPE_INT;
break;
case ML_LIMIT:
case ML_SORT_DESC:
case ML_SORT_ASC:
case ML_END:
default:
msg_Dbg( p_ml, "unknown select (%d) in BuildSelect", selected_type );
return VLC_EGENERIC;
}
/* Let's build full psz_query ! */
i_ret = VLC_SUCCESS;
/* Figure out select and join tables */
switch( i_from )
{
case table_media:
break;
case table_album:
switch( i_join )
{
case 0: break;
case 2: i_join = 0; break;
case 1:
case 3: i_from = table_media; i_join = table_album; break;
case 4:
case 5:
case 6:
case 7: i_from = table_media; i_join = table_album | table_people; break;
case 8:
case 9:
case 10:
case 11: i_from = table_media; i_join = table_extra | table_album; break;
case 12:
case 13:
case 14:
case 15: i_from = table_media; i_join = table_extra | table_album | table_people; break;
default: break;
}
break;
case table_people:
switch( i_join )
{
case 0: break;
case 1: i_from = table_media; i_join = table_people; break;
case 2:
case 3: i_from = table_media; i_join = table_album | table_people; break;
case 4:
/* Determine if a join from media is required */
if( i_num_frompersons > 1 )
i_from = table_media;
else
i_join = 0;
break;
case 5: i_from = table_media; i_join = table_people; break;
case 6:
case 7: i_from = table_media; i_join = table_album | table_people; break;
case 8:
case 9: i_from = table_media; i_join = table_people | table_extra; break;
case 10:
case 11: i_from = table_media; i_join = table_people | table_album | table_extra; break;
case 12:
case 13: i_from = table_media; i_join = table_people | table_extra; break;
case 14:
case 15: i_from = table_media; i_join = table_people | table_album | table_extra; break;
default: break;
}
break;
case table_extra:
switch( i_join )
{
case 0: break;
case 1: i_from = table_media; i_join = table_extra; break;
case 2:
case 3: i_from = table_media; i_join = table_extra | table_album; break;
case 4:
case 5: i_from = table_media; i_join = table_extra | table_people; break;
case 6:
case 7: i_from = table_media; i_join = table_extra | table_people | table_album; break;
case 8: i_from = table_extra; i_join = 0; break;
case 9: i_from = table_media; i_join = table_extra; break;
case 10:
case 11: i_from = table_media; i_join = table_extra | table_album; break;
case 12:
case 13: i_from = table_media; i_join = table_extra | table_people; break;
case 14:
case 15: i_from = table_media; i_join = table_extra | table_people | table_album; break;
default: break;
}
break;
default: msg_Warn( p_ml, "You can't be selecting from this table!!" );
i_ret = VLC_EGENERIC;
goto exit;
}
assert( !( i_from & table_album && i_join & table_album ) );
assert( !( i_from & table_people && i_join & table_people ) );
assert( !( i_from & table_extra && i_join & table_extra ) );
/* Generate FROM - psz_from */
if( i_from == table_media )
i_ret = AppendStringFmt( p_ml, &psz_from, "media" );
else if( i_from == table_album )
i_ret = AppendStringFmt( p_ml, &psz_from, "album" );
else if( i_from == table_extra )
i_ret = AppendStringFmt( p_ml, &psz_from, "extra" );
else if( i_from == table_people )
{
i_ret = AppendStringFmt( p_ml, &psz_from, "people AS people%s%s",
psz_peoplerole ? "_" : "", psz_peoplerole );
if( i_ret < 0 ) goto exit;
/* The ugly next statement is only required if persons are being
* selected. Otherwise the joins will handle this */
if( psz_peoplerole && *psz_peoplerole )
{
i_ret = AppendStringFmt( p_ml, &psz_where, "%s people_%s.role = %Q ",
( psz_where && *psz_where ) ? " AND" : "",
psz_peoplerole, psz_peoplerole );
if( i_ret < 0 ) goto exit;
}
}
if( i_ret < 0 ) goto exit;
i_ret = AppendStringFmt( p_ml, &psz_query,
"SELECT %s %s ", psz_distinct, psz_select );
if( i_ret < 0 ) goto exit;
i_ret = AppendStringFmt( p_ml, &psz_query, "FROM %s ", psz_from );
if( i_ret < 0 ) goto exit;
/* Create join conditions */
if( i_join & table_people )
{
/* we can join psz_peoplerole safely because
* if i_join = people, then i_from != people */
bool join = true;
for( int i = 0; i < i_num_frompersons ; i++ )
{
/* We assume ppsz_frompersons has unique entries and
* if ppsz_frompersons[i] is empty(but not NULL), then it
* means we accept any role */
if( ppsz_frompersons[i] && *ppsz_frompersons[i] )
{
if( strcmp( psz_peoplerole, ppsz_frompersons[i] ) == 0 )
join = false;
AppendStringFmt( p_ml, &psz_join, "%smedia_to_people AS people_%sx ",
psz_join == NULL ? "" : ",", ppsz_frompersons[i] );
/* This is possible because from is usually the media table */
AppendStringFmt( p_ml, &psz_on, "%speople_%sx.media_id = media.id ",
psz_on == NULL ? "" : " AND ", ppsz_frompersons[i] );
AppendStringFmt( p_ml, &psz_join2, "%speople AS people_%s ",
psz_join2 == NULL ? "" : ",", ppsz_frompersons[i] );
AppendStringFmt( p_ml, &psz_on2, "%s ( people_%sx.people_id = people_%s.id AND "
"people_%s.role = %Q )", psz_on2 == NULL ? "" : " AND ",
ppsz_frompersons[i], ppsz_frompersons[i],
ppsz_frompersons[i], ppsz_frompersons[i] );
}
else if( ppsz_frompersons[i] )
{
if( strcmp( psz_peoplerole, ppsz_frompersons[i] ) == 0 )
join = false;
AppendStringFmt( p_ml, &psz_join, "%smedia_to_people AS peoplex ",
psz_join == NULL ? "" : "," );
/* This is possible because from is usually the media table */
AppendStringFmt( p_ml, &psz_on, "%speoplex.media_id = media.id ",
psz_on == NULL ? "" : " AND " );
AppendStringFmt( p_ml, &psz_join2, "%speople AS people ",
psz_join2 == NULL ? "" : "," );
AppendStringFmt( p_ml, &psz_on2, "%s peoplex.people_id = people.id",
psz_on2 == NULL ? "" : " AND " );
}
}
if( join )
{
if( psz_peoplerole && *psz_peoplerole )
{
AppendStringFmt( p_ml, &psz_join, "%smedia_to_people AS people_%sx ",
psz_join == NULL ? "" : ",", psz_peoplerole );
/* This is possible because from is always the media table */
AppendStringFmt( p_ml, &psz_on, "%speople_%sx.media_id = media.id ",
psz_on == NULL ? "" : " AND ", psz_peoplerole );
AppendStringFmt( p_ml, &psz_join2, "%speople AS people_%s ",
psz_join2 == NULL ? "" : ",", psz_peoplerole );
AppendStringFmt( p_ml, &psz_on2, "%s ( people_%sx.people_id = people_%s.id AND "
"people_%s.role = %Q )", psz_on2 == NULL ? "" : " AND ",
psz_peoplerole, psz_peoplerole,
psz_peoplerole, psz_peoplerole );
}
else
{
AppendStringFmt( p_ml, &psz_join, "%smedia_to_people AS peoplex ",
psz_join == NULL ? "" : "," );
/* This is possible because from is usually the media table */
AppendStringFmt( p_ml, &psz_on, "%speoplex.media_id = media.id ",
psz_on == NULL ? "" : " AND " );
AppendStringFmt( p_ml, &psz_join2, "%speople ",
psz_join2 == NULL ? "" : "," );
AppendStringFmt( p_ml, &psz_on2, "%s peoplex.people_id = people.id",
psz_on2 == NULL ? "" : " AND " );
}
}
}
if( i_join & table_album )
{
AppendStringFmt( p_ml, &psz_join, "%salbum", psz_join == NULL ? "" : "," );
AppendStringFmt( p_ml, &psz_on, "%s album.id = media.album_id ",
psz_on == NULL ? "" : " AND " );
}
if( i_join & table_extra )
{
AppendStringFmt( p_ml, &psz_join, "%sextra", psz_join == NULL ? "" : "," );
AppendStringFmt( p_ml, &psz_on, "%s extra.id = media.id ",
psz_on == NULL ? "" : " AND " );
}
/* Complete the join clauses */
if( psz_join )
{
AppendStringFmt( p_ml, &psz_query,
"JOIN %s ON %s ", psz_join, psz_on );
}
if( psz_join2 )
{
AppendStringFmt( p_ml, &psz_query,
"JOIN %s ON %s ", psz_join2, psz_on2 );
}
if( psz_where && *psz_where )
{
AppendStringFmt( p_ml, &psz_query,
"WHERE %s ", psz_where );
}
/* TODO: FIXME: Limit on media objects doesn't work! */
if( i_limit )
{
AppendStringFmt( p_ml, &psz_query,
"LIMIT %d ", i_limit );
}
if( psz_sort )
{
AppendStringFmt( p_ml, &psz_query,
"ORDER BY %s %s", psz_select, psz_sort );
}
if( i_ret > 0 ) i_ret = VLC_SUCCESS;
if( p_result_type ) *p_result_type = res_type;
if( !psz_query ) i_ret = VLC_EGENERIC;
else *ppsz_query = strdup( psz_query );
exit:
free( psz_query );
free( psz_where );
free( psz_tmp );
free( psz_from );
free( psz_join );
free( psz_select );
free( psz_join2 );
free( psz_on );
free( psz_on2 );
free( psz_peoplerole );
free( ppsz_frompersons );
if( i_ret != VLC_SUCCESS )
msg_Warn( p_ml, "an unknown error occurred (%d)", i_ret );
return i_ret;
}
#undef CASE_INT
#define CASE_INT( casestr, fmt, table ) \
case casestr: \
assert( tree->comp != ML_COMP_HAS && tree->comp != ML_COMP_STARTS_WITH \
&& tree->comp != ML_COMP_ENDS_WITH ); \
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql, "%s %s %d", fmt, \
tree->comp == ML_COMP_LESSER ? "<" : \
tree->comp == ML_COMP_LESSER_OR_EQUAL ? "<=" : \
tree->comp == ML_COMP_GREATER ? ">" : \
tree->comp == ML_COMP_GREATER_OR_EQUAL ? ">=" : "=", tree->value.i ); \
if( *ppsz_where == NULL ) \
goto parsefail; \
*join |= table; \
break
#undef CASE_PSZ
#define CASE_PSZ( casestr, fmt, table ) \
case casestr: \
assert( tree->comp == ML_COMP_HAS || tree->comp == ML_COMP_EQUAL \
|| tree->comp == ML_COMP_STARTS_WITH \
|| tree->comp == ML_COMP_ENDS_WITH ); \
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql, "%s %s '%s%q%s'", fmt, \
(ML_COMP_EQUAL)?"=":"LIKE", \
tree->comp == ML_COMP_HAS \
|| tree->comp == ML_COMP_STARTS_WITH? "%%" : "", \
tree->value.str, \
tree->comp == ML_COMP_HAS \
|| tree->comp == ML_COMP_ENDS_WITH? "%%" : "" ); \
if( *ppsz_where == NULL ) \
goto parsefail; \
*join |= table; \
break
#define SLDPJ sort, limit, distinct, pppsz_frompersons, i_frompersons, join
static int BuildWhere( media_library_t* p_ml, char **ppsz_where, ml_ftree_t* tree,
char** sort, int* limit, const char** distinct,
char*** pppsz_frompersons, int* i_frompersons, int* join )
{
assert( ppsz_where && sort && distinct );
if( !tree ) /* Base case */
{
return VLC_SUCCESS;
}
int i_ret = VLC_EGENERIC;
char* psz_left = NULL;
char* psz_right = NULL;
switch( tree->op )
{
case ML_OP_AND:
case ML_OP_OR:
i_ret = BuildWhere( p_ml, &psz_left, tree->left, SLDPJ );
if( i_ret != VLC_SUCCESS )
goto parsefail;
i_ret = BuildWhere( p_ml, &psz_right, tree->right, SLDPJ );
if( i_ret != VLC_SUCCESS )
goto parsefail;
if( psz_left == NULL || psz_right == NULL )
{
msg_Err( p_ml, "Parsing failed for AND/OR" );
i_ret = VLC_EGENERIC;
goto parsefail;
}
if( asprintf( ppsz_where, "( %s %s %s )", psz_left,
( tree->op == ML_OP_AND ? "AND" : "OR" ), psz_right ) == -1 )
{
i_ret = VLC_ENOMEM;
goto parsefail;
}
break;
case ML_OP_NOT:
i_ret = BuildWhere( p_ml, &psz_left, tree->left, SLDPJ );
if( i_ret != VLC_SUCCESS )
goto parsefail;
if( psz_left == NULL )
{
msg_Err( p_ml, "Parsing failed at NOT" );
i_ret = VLC_EGENERIC;
goto parsefail;
}
if( asprintf( ppsz_where, "( NOT %s )", psz_left ) == -1 )
{
i_ret = VLC_ENOMEM;
goto parsefail;
}
break;
case ML_OP_SPECIAL:
i_ret = BuildWhere( p_ml, &psz_right, tree->right, SLDPJ );
if( i_ret != VLC_SUCCESS )
goto parsefail;
i_ret = BuildWhere( p_ml, &psz_left, tree->left, SLDPJ );
if( i_ret != VLC_SUCCESS )
goto parsefail;
/* Ignore right parse tree as this is a special node */
*ppsz_where = strdup( psz_left ? psz_left : "" );
if( !*ppsz_where )
{
i_ret = VLC_ENOMEM;
goto parsefail;
}
break;
case ML_OP_NONE:
switch( tree->criteria )
{
case ML_PEOPLE:
assert( tree->comp == ML_COMP_HAS
|| tree->comp == ML_COMP_EQUAL
|| tree->comp == ML_COMP_STARTS_WITH
|| tree->comp == ML_COMP_ENDS_WITH );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql,
"people%s%s.name LIKE '%s%q%s'",
tree->lvalue.str ? "_" : "",
tree->lvalue.str ? tree->lvalue.str : "",
tree->comp == ML_COMP_HAS
|| tree->comp == ML_COMP_STARTS_WITH ? "%%" : "",
tree->value.str,
tree->comp == ML_COMP_HAS
|| tree->comp == ML_COMP_ENDS_WITH ? "%%" : "" );
if( *ppsz_where == NULL )
goto parsefail;
*pppsz_frompersons = realloc( *pppsz_frompersons,
++*i_frompersons * sizeof( char* ) );
*pppsz_frompersons[ *i_frompersons - 1 ] = tree->lvalue.str;
*join |= table_people;
break;
case ML_PEOPLE_ID:
assert( tree->comp == ML_COMP_EQUAL );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql,
"( people%s%s.id = %d )", tree->lvalue.str ? "_":"",
tree->lvalue.str ? tree->lvalue.str:"",
tree->value.i );
if( *ppsz_where == NULL )
goto parsefail;
*pppsz_frompersons = realloc( *pppsz_frompersons,
++*i_frompersons * sizeof( char* ) );
*pppsz_frompersons[ *i_frompersons - 1 ] = tree->lvalue.str;
*join |= table_people;
break;
case ML_PEOPLE_ROLE:
assert( tree->comp == ML_COMP_HAS
|| tree->comp == ML_COMP_EQUAL
|| tree->comp == ML_COMP_STARTS_WITH
|| tree->comp == ML_COMP_ENDS_WITH );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql,
"people%s%s.role LIKE '%s%q%s'",
tree->lvalue.str ? "_" : "",
tree->lvalue.str ? tree->lvalue.str : "",
tree->comp == ML_COMP_HAS
|| tree->comp == ML_COMP_STARTS_WITH ? "%%" : "",
tree->value.str,
tree->comp == ML_COMP_HAS
|| tree->comp == ML_COMP_ENDS_WITH ? "%%" : "" );
if( *ppsz_where == NULL )
goto parsefail;
*pppsz_frompersons = realloc( *pppsz_frompersons,
++*i_frompersons * sizeof( char* ) );
*pppsz_frompersons[ *i_frompersons - 1 ] = tree->lvalue.str;
*join |= table_people;
break;
CASE_PSZ( ML_ALBUM, "album.title", table_album );
CASE_PSZ( ML_ALBUM_COVER, "album.cover", table_album );
case ML_ALBUM_ID:
assert( tree->comp == ML_COMP_EQUAL );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql,
"album.id = %d", tree->value.i );
if( *ppsz_where == NULL )
goto parsefail;
*join |= table_album;
break;
CASE_PSZ( ML_COMMENT, "media.comment", table_media );
CASE_PSZ( ML_COVER, "media.cover", table_media );
CASE_INT( ML_DURATION, "media.duration", table_media );
CASE_PSZ( ML_EXTRA, "extra.extra", table_extra );
CASE_INT( ML_FILESIZE, "media.filesize", table_media );
CASE_PSZ( ML_GENRE, "media.genre", table_media );
case ML_ID:
assert( tree->comp == ML_COMP_EQUAL );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql,
"media.id = %d", tree->value.i );
if( *ppsz_where == NULL )
goto parsefail;
*join |= table_media;
break;
CASE_PSZ( ML_LANGUAGE, "extra.language", table_extra );
CASE_INT( ML_LAST_PLAYED, "media.last_played", table_media );
CASE_PSZ( ML_ORIGINAL_TITLE, "media.original_title", table_media );
msg_Warn( p_ml, "Deprecated Played Count tags" );
CASE_INT( ML_PLAYED_COUNT, "media.played_count", table_media );
CASE_INT( ML_SCORE, "media.score", table_media );
CASE_PSZ( ML_TITLE, "media.title", table_media );
CASE_INT( ML_TRACK_NUMBER, "media.track", table_media);
CASE_INT( ML_TYPE, "media.type", table_media );
CASE_PSZ( ML_URI, "media.uri", table_media );
CASE_INT( ML_VOTE, "media.vote", table_media );
CASE_INT( ML_YEAR, "media.year", table_media );
case ML_LIMIT:
if( !*limit )
*limit = tree->value.i;
else
msg_Warn( p_ml, "Double LIMIT found" );
break;
case ML_SORT_DESC:
*sort = sql_Printf( p_ml->p_sys->p_sql, "%s%s%s DESC ",
sort ? *sort : "", sort ? ", " : "",
tree->value.str );
if( *sort == NULL )
goto parsefail;
break;
case ML_SORT_ASC:
*sort = sql_Printf( p_ml->p_sys->p_sql, "%s%s%s ASC ",
sort ? *sort : "", sort ? ", " : "",
tree->value.str );
if( *sort == NULL )
goto parsefail;
break;
case ML_DISTINCT:
if( !**distinct )
*distinct = "DISTINCT";
else
msg_Warn( p_ml, "Double DISTINCT found!" );
break;
default:
msg_Err( p_ml, "Invalid select type or unsupported: %d", tree->criteria );
}
break;
default:
msg_Err( p_ml, "Broken find tree!" );
i_ret = VLC_EGENERIC;
goto parsefail;
}
i_ret = VLC_SUCCESS;
parsefail:
free( psz_left );
free( psz_right );
return i_ret;
}
# undef CASE_INT
# undef CASE_PSZ
# undef table_media
# undef table_album
# undef table_people
# undef table_extra
/*****************************************************************************
* sql_update.c: SQL-based media library: all database update functions
*****************************************************************************
* 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sql_media_library.h"
/**
* @brief Generic update in Media Library database
*
* @param p_ml the media library object
* @param selected_type the type of the element we're selecting
* @param where the list of ids or uri to change
* @param changes list of changes to make in the entries
* @return VLC_SUCCESS or VLC_EGENERIC
* @note This function is transactional
*/
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 i_ret = VLC_EGENERIC;
char *psz_query = NULL;
char *psz_id_query = NULL;
char **pp_results = NULL;
int i_rows = 0, i_cols = 0;
i_ret = BuildUpdate( p_ml, &psz_query, &psz_id_query,
psz_lvalue, selected_type, where, changes );
if( i_ret != VLC_SUCCESS )
{
msg_Err(p_ml,"Failed to generate update query" );
return i_ret;
}
i_ret = VLC_EGENERIC;
Begin( p_ml );
if( QuerySimple( p_ml, "%s", psz_query ) != VLC_SUCCESS )
{
msg_Err( p_ml, "Couldn't run the generated update query successfully" );
goto quitdelete;
}
/* Get the updated IDs to send events! */
if( Query( p_ml, &pp_results, &i_rows, &i_cols, psz_id_query )
!= VLC_SUCCESS )
goto quitdelete;
i_ret = VLC_SUCCESS;
quitdelete:
if( i_ret != VLC_SUCCESS )
Rollback( p_ml );
else
{
Commit( p_ml );
if( i_rows > 0 )
{
for( int i = 0; i < i_rows; i++ )
{
var_SetInteger( p_ml, "media-meta-change",
atoi( pp_results[i*i_cols] ) );
}
}
}
FreeSQLResult( p_ml, pp_results );
free( psz_id_query );
free( psz_query );
return i_ret;
}
#define SET_STR( a ) \
if( !psz_set[i_type] ) \
{ \
psz_set[i_type] = sql_Printf( p_ml->p_sys->p_sql, a, find->value.str ); \
if( !psz_set[i_type] ) \
goto quit_buildupdate; \
} \
break;
#define SET_INT( a ) \
if( !psz_set[i_type] ) \
{ \
psz_set[i_type] = sql_Printf( p_ml->p_sys->p_sql, a, find->value.i ); \
if( !psz_set[i_type] ) \
goto quit_buildupdate; \
} \
break;
/* TODO: Build smarter updates by using IN () */
static int BuildWhere( media_library_t* p_ml, char **ppsz_where, ml_ftree_t *tree )
{
assert( ppsz_where );
char* psz_left = NULL;
char* psz_right = NULL;
int i_ret = VLC_SUCCESS;
switch( tree->op )
{
case ML_OP_AND:
case ML_OP_OR:
i_ret = BuildWhere( p_ml, &psz_left, tree->left );
if( i_ret != VLC_SUCCESS )
goto quit_buildwhere;
i_ret = BuildWhere( p_ml, &psz_right, tree->right );
if( i_ret != VLC_SUCCESS )
goto quit_buildwhere;
if( psz_left == NULL || psz_right == NULL )
{
msg_Err( p_ml, "Couldn't build AND/OR for Update statement" );
i_ret = VLC_EGENERIC;
goto quit_buildwhere;
}
if( asprintf( ppsz_where, "( %s %s %s )", psz_left,
( tree->op == ML_OP_AND ? "AND" : "OR" ), psz_right ) == -1 )
{
i_ret = VLC_ENOMEM;
goto quit_buildwhere;
}
break;
case ML_OP_NOT:
i_ret = BuildWhere( p_ml, &psz_left, tree->left );
if( i_ret != VLC_SUCCESS )
goto quit_buildwhere;
if( psz_left == NULL )
{
msg_Err( p_ml, "Couldn't build NOT for Update statement" );
i_ret = VLC_EGENERIC;
goto quit_buildwhere;
}
if( asprintf( ppsz_where, "( NOT %s )", psz_left ) == -1 )
{
i_ret = VLC_ENOMEM;
goto quit_buildwhere;
}
break;
case ML_OP_SPECIAL:
msg_Err( p_ml, "Couldn't build special for Update statement" );
break;
case ML_OP_NONE:
switch( tree->criteria )
{
case ML_ID:
assert( tree->comp == ML_COMP_EQUAL );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql, "media.id = %d",
tree->value.i );
if( *ppsz_where == NULL )
goto quit_buildwhere;
break;
case ML_URI:
assert( tree->comp == ML_COMP_EQUAL );
*ppsz_where = sql_Printf( p_ml->p_sys->p_sql, "media.uri = %q",
tree->value.str );
if( *ppsz_where == NULL )
goto quit_buildwhere;
break;
default:
msg_Err( p_ml, "Trying to update with unsupported condition" );
break;
}
}
quit_buildwhere:
return i_ret;
}
/**
* @brief Generic UPDATE query builder
*
* @param p_ml This media_library_t object
* @param ppsz_query *ppsz_query will contain query for update
* @param ppsz_id_query will contain query to get the ids of updated files
* @param selected_type the type of the element we're selecting
* @param where parse tree of where condition
* @param changes list of changes to make in the entries
* @return VLC_SUCCESS or VLC_EGENERIC
*/
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 )
{
assert( ppsz_query );
assert( ppsz_id_query );
*ppsz_query = NULL;
int i_type;
int i_index;
int i_ret = VLC_ENOMEM;
char *psz_table = NULL;
/* TODO: Hack? */
char *psz_set[ ML_DIRECTORY + 1 ] = { NULL };
char *psz_fullset = NULL;
char *psz_extra = NULL; /*<< For an update to extra table */
char *psz_where = NULL;
char *psz_tmp = NULL;
int *pi_padd_ids = NULL;
int i_people_add = 0;
int i_album_id = 0;
char *psz_album = NULL;
char *psz_cover = NULL;
if( !where )
{
msg_Warn( p_ml, "You're trying to update no rows."
"Trying to guess update based on uri" );
}
/* Create the id/uri lists for WHERE part of the query */
i_ret = BuildWhere( p_ml, &psz_where, where );
if( i_ret != VLC_SUCCESS )
goto quit_buildupdate;
i_ret = VLC_ENOMEM;
/** Firstly, choose the right table */
switch( selected_type )
{
case ML_ALBUM:
psz_table = strdup( "album" );
break;
case ML_PEOPLE:
psz_table = strdup( "people" );
break;
case ML_MEDIA:
psz_table = strdup( "media" );
break;
default:
msg_Err( p_ml, "Not a valid element to Update!" );
i_ret = VLC_EGENERIC;
goto quit_buildupdate;
break;
}
if( !psz_table )
return VLC_ENOMEM;
/** Secondly, build the SET part of the query */
for( i_index = 0; i_index < vlc_array_count( changes ); i_index++ )
{
ml_element_t *find = ( ml_element_t * )
vlc_array_item_at_index( changes, i_index );
i_type = find->criteria;
switch( i_type )
{
case ML_ALBUM:
if( selected_type == ML_ALBUM )
{
if( !psz_set[i_type] )
{
psz_set[i_type] = sql_Printf( p_ml->p_sys->p_sql,
"title = %Q",
find->value.str );
if( !psz_set[i_type] )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
}
}
else if( selected_type == ML_MEDIA )
{
if( !psz_album )
psz_album = find->value.str;
}
else
assert( 0 );
break;
case ML_ALBUM_ID:
assert( selected_type != ML_ALBUM );
if( selected_type == ML_MEDIA )
{
if( i_album_id <= 0 )
{
i_album_id = find->value.i;
if( !psz_set[i_type] )
{
psz_set[i_type] = sql_Printf( p_ml->p_sys->p_sql,
"album_id = '%d'",
find->value.i );
if( !psz_set[i_type] )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
}
}
}
break;
case ML_PEOPLE:
if( selected_type == ML_MEDIA )
{
pi_padd_ids = (int*) realloc( pi_padd_ids , ( ++i_people_add * sizeof(int) ) );
pi_padd_ids[ i_people_add - 1 ] = ml_GetInt( p_ml, ML_PEOPLE_ID,
find->lvalue.str, ML_PEOPLE, find->lvalue.str,
find->value.str );
if( pi_padd_ids[ i_people_add - 1 ] <= 0 )
{
AddPeople( p_ml, find->value.str, find->lvalue.str );
pi_padd_ids[ i_people_add - 1 ] = ml_GetInt( p_ml, ML_PEOPLE_ID,
find->lvalue.str, ML_PEOPLE, find->lvalue.str,
find->value.str );
}
}
else if( strcmp( psz_lvalue, find->lvalue.str ) )
{
msg_Err( p_ml, "Trying to update a different person type" );
return VLC_EGENERIC;
}
else
{
if( !psz_set[i_type] ) psz_set[i_type] =
sql_Printf( p_ml->p_sys->p_sql, "name = %Q", find->value.str );
}
break;
case ML_PEOPLE_ID:
/* TODO: Implement smarter updates for this case? */
assert( selected_type == ML_MEDIA );
if( selected_type == ML_MEDIA )
{
bool b_update = true;
for( int i = 0; i < i_people_add; i++ )
{
if( pi_padd_ids[ i ] == find->value.i )
{
b_update = false;
break;
}
}
if( b_update )
{
pi_padd_ids = (int *)realloc( pi_padd_ids, ( ++i_people_add * sizeof(int) ) );
pi_padd_ids[ i_people_add - 1 ] = find->value.i;
}
}
break;
case ML_PEOPLE_ROLE:
msg_Dbg( p_ml, "Can't update role" );
break;
case ML_COMMENT:
assert( selected_type == ML_MEDIA );
SET_STR( "comment = %Q" );
case ML_COVER:
assert( selected_type == ML_ALBUM || selected_type == ML_MEDIA );
psz_cover = find->value.str;
SET_STR( "cover = %Q" );
case ML_DISC_NUMBER:
assert( selected_type == ML_MEDIA );
SET_INT( "disc = '%d'" );
case ML_DURATION:
assert( selected_type == ML_MEDIA );
SET_INT( "duration = '%d'" );
case ML_EXTRA:
assert( selected_type == ML_MEDIA );
SET_STR( "extra = %Q" );
case ML_FIRST_PLAYED:
assert( selected_type == ML_MEDIA );
SET_INT( "first_played =='%d'" );
case ML_GENRE:
assert( selected_type == ML_MEDIA );
SET_STR( "genre = %Q" );
/* ID cannot be updated */
/* Import time can't be updated */
case ML_LAST_PLAYED:
assert( selected_type == ML_MEDIA );
SET_INT( "last_played = '%d'" );
case ML_ORIGINAL_TITLE:
assert( selected_type == ML_MEDIA );
SET_STR( "original_title = %Q" );
case ML_PLAYED_COUNT:
assert( selected_type == ML_MEDIA );
SET_INT( "played_count = '%d'" );
case ML_PREVIEW:
assert( selected_type == ML_MEDIA );
SET_STR( "preview = %Q" );
case ML_SKIPPED_COUNT:
assert( selected_type == ML_MEDIA );
SET_INT( "skipped_count = '%d'" );
case ML_SCORE:
assert( selected_type == ML_MEDIA );
SET_INT( "score = '%d'" );
case ML_TITLE:
assert( selected_type == ML_MEDIA );
SET_STR( "title = %Q" );
case ML_TRACK_NUMBER:
assert( selected_type == ML_MEDIA );
SET_INT( "track = '%d'" );
case ML_TYPE:
assert( selected_type == ML_MEDIA );
if( !psz_set[i_type] ) psz_set[i_type] =
sql_Printf( p_ml->p_sys->p_sql, "type = '%d'", find->value.i );
break;
case ML_URI:
assert( selected_type == ML_MEDIA );
if( !psz_set[i_type] )
{
psz_set[i_type] = sql_Printf( p_ml->p_sys->p_sql,
"uri = %Q",
find->value.str );
}
break;
case ML_VOTE:
assert( selected_type == ML_MEDIA );
SET_INT( "vote = '%d'" );
case ML_YEAR:
assert( selected_type == ML_MEDIA );
SET_INT( "year = '%d'" );
case ML_END:
goto exitfor;
default:
msg_Warn( p_ml, "Invalid type for update : %d", i_type );
}
}
exitfor:
/* TODO: Album artist. Verify albumart */
if( i_album_id <= 0 && psz_album )
{
i_album_id = ml_GetAlbumId( p_ml, psz_album );
if( i_album_id < 0 ) //0 is Unknown
{
i_ret = AddAlbum( p_ml, psz_album, psz_cover, 0 );
if( i_ret != VLC_SUCCESS )
{
msg_Err( p_ml, "Couldn't AddAlbum at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
i_album_id = ml_GetAlbumId( p_ml, psz_album );
if( i_album_id < 0 )
goto quit_buildupdate;
}
psz_set[ML_ALBUM_ID] = sql_Printf( p_ml->p_sys->p_sql,
"album_id = '%d'", i_album_id );
if( !psz_set[ML_ALBUM_ID] )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
}
for( unsigned i = 0; i <= ML_DIRECTORY; i++ )
{
if( psz_set[i] )
{
if( i == ML_EXTRA || i == ML_LANGUAGE )
{
free( psz_tmp );
if( asprintf( &psz_tmp, "%s%s%s", psz_extra ? psz_extra : "",
psz_extra ? ", ": "", psz_set[i] ) == -1 )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
free( psz_extra );
psz_extra = strdup( psz_tmp );
}
else
{
free( psz_tmp );
if( asprintf( &psz_tmp, "%s%s%s", psz_fullset ? psz_fullset : "",
psz_fullset ? ", ": "", psz_set[i] ) == -1 )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
free( psz_fullset );
psz_fullset = strdup( psz_tmp );
}
}
}
i_ret = VLC_SUCCESS;
/** Now build the right WHERE condition */
assert( psz_where && *psz_where );
/** Finally build the full query */
/** Pass if we have some people to add - Indirect update */
if( !psz_fullset && i_people_add == 0 )
{
i_ret = VLC_EGENERIC;
msg_Err( p_ml, "Nothing found to create update at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
if( psz_fullset ){
if( asprintf( ppsz_query, "UPDATE %s SET %s WHERE %s", psz_table,
psz_fullset, psz_where ) == -1 )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
}
if( selected_type == ML_MEDIA )
{
if( psz_extra )
{
if( asprintf( &psz_tmp, "%s; UPDATE extra SET %s WHERE %s",
*ppsz_query, psz_extra, psz_where ) == -1 )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
free( *ppsz_query );
*ppsz_query = psz_tmp;
psz_tmp = NULL;
}
char* psz_idstring = NULL;
if( i_people_add > 0 )
{
for( int i = 0; i < i_people_add; i++ )
{
if( asprintf( &psz_tmp, "%s%s%d", psz_idstring == NULL? "" : psz_idstring,
psz_idstring == NULL ? "" : ",", pi_padd_ids[i] ) == -1 )
{
free( psz_tmp );
free( psz_idstring );
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
free( psz_idstring );
psz_idstring = psz_tmp;
psz_tmp = NULL;
}
/* Delete all connections with people whom we will update now! */
if( asprintf( &psz_tmp, "%s;DELETE FROM media_to_people WHERE EXISTS "
"(SELECT media.id, people.id FROM media JOIN media_to_people "
"AS temp ON media.id = temp.media_id "
"JOIN people ON temp.people_id = people.id "
"WHERE %s AND people.role IN "
"(SELECT people.role FROM people WHERE people.id IN (%s)) "
"AND people.id NOT IN (%s) "
"AND temp.media_id = media_to_people.media_id AND "
"temp.people_id = media_to_people.people_id )",
*ppsz_query == NULL ? "": *ppsz_query, psz_where,
psz_idstring, psz_idstring ) == -1 )
{
free( psz_idstring );
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
free( *ppsz_query );
*ppsz_query = psz_tmp;
psz_tmp = NULL;
free( psz_idstring );
}
for( int i = 0; i < i_people_add ; i++ )
{
if( pi_padd_ids[i] > 0 )
{
/* OR IGNORE will avoid errors from collisions from old media
* Perhaps this hack can be fixed...FIXME */
if( asprintf( &psz_tmp, "%s;INSERT OR IGNORE into media_to_people "
"(media_id,people_id) SELECT media.id, %d FROM media WHERE %s",
*ppsz_query == NULL ? "" : *ppsz_query, pi_padd_ids[i],
psz_where ) == -1 )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
FREENULL( *ppsz_query );
*ppsz_query = psz_tmp;
psz_tmp = NULL;
}
}
}
if( asprintf( ppsz_id_query, "SELECT id AS %s_id FROM %s WHERE %s",
psz_table, psz_table, psz_where ) == -1 )
{
msg_Err( p_ml, "Couldn't create string at BuildUpdate():(%s, %d)",
__FILE__, __LINE__ );
goto quit_buildupdate;
}
#ifndef NDEBUG
msg_Dbg( p_ml, "updated media where %s", psz_where );
#endif
goto quit_buildupdate_success;
quit_buildupdate:
msg_Warn( p_ml, "BuildUpdate() could not generate update sql query" );
quit_buildupdate_success:
free( psz_tmp );
free( psz_table );
free( psz_fullset );
free( psz_extra );
free( pi_padd_ids );
for( int i = 0; i <= ML_DIRECTORY; i++ )
free( psz_set[ i ] );
return i_ret;
}
#undef SET_STR
#undef SET_INT
/**
* @brief Update a ml_media_t
*
* @param p_ml the media library object
* @param p_media media to synchronise in the database
* @return VLC_SUCCESS or VLC_EGENERIC
* @note: the media id may be 0, in this case, the update is based
* on the url (less powerful). This function is threadsafe
*
* This synchronises all non NULL and non zero fields of p_media
* Synchronization of album and people is TODO
*/
int UpdateMedia( media_library_t *p_ml, ml_media_t *p_media )
{
assert( p_media->i_id || ( p_media->psz_uri && *p_media->psz_uri ) );
vlc_array_t *changes = vlc_array_new();
ml_element_t *find = NULL;
int i_ret = VLC_EGENERIC;
ml_LockMedia( p_media );
#define APPEND_ICHANGES( cond, crit ) \
if( cond ) { \
find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) ); \
find->criteria = crit; \
find->value.i = cond; \
vlc_array_append( changes, find ); \
}
#define APPEND_SCHANGES( cond, crit ) \
if( cond ) { \
find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) ); \
find->criteria = crit; \
find->value.str = cond; \
vlc_array_append( changes, find ); \
}
APPEND_SCHANGES( p_media->psz_title, ML_TITLE );
APPEND_ICHANGES( p_media->i_type, ML_TYPE );
APPEND_ICHANGES( p_media->i_duration, ML_DURATION );
APPEND_SCHANGES( p_media->psz_preview, ML_PREVIEW );
APPEND_SCHANGES( p_media->psz_cover, ML_COVER );
APPEND_ICHANGES( p_media->i_disc_number, ML_DISC_NUMBER );
APPEND_ICHANGES( p_media->i_track_number, ML_TRACK_NUMBER );
APPEND_ICHANGES( p_media->i_year, ML_YEAR);
APPEND_SCHANGES( p_media->psz_genre, ML_GENRE );
APPEND_ICHANGES( p_media->i_album_id, ML_ALBUM_ID );
APPEND_SCHANGES( p_media->psz_album, ML_ALBUM );
APPEND_ICHANGES( p_media->i_skipped_count, ML_SKIPPED_COUNT );
APPEND_ICHANGES( p_media->i_last_skipped, ML_LAST_SKIPPED );
APPEND_ICHANGES( p_media->i_played_count, ML_PLAYED_COUNT );
APPEND_ICHANGES( p_media->i_last_played, ML_LAST_PLAYED );
APPEND_ICHANGES( p_media->i_first_played, ML_FIRST_PLAYED );
APPEND_ICHANGES( p_media->i_vote, ML_VOTE );
APPEND_ICHANGES( p_media->i_score, ML_SCORE );
APPEND_SCHANGES( p_media->psz_comment, ML_COMMENT );
APPEND_SCHANGES( p_media->psz_extra, ML_EXTRA );
APPEND_SCHANGES( p_media->psz_language, ML_LANGUAGE );
if( p_media->psz_uri && p_media->i_id )
{
find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
find->criteria = ML_URI;
find->value.str = p_media->psz_uri;
vlc_array_append( changes, find );
}
/*TODO: implement extended meta */
/* We're not taking import time! Good */
#undef APPEND_ICHANGES
#undef APPEND_SCHANGES
ml_person_t* person = p_media->p_people;
while( person )
{
if( person->i_id > 0 )
{
find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
find->criteria = ML_PEOPLE_ID;
find->lvalue.str = person->psz_role;
find->value.i = person->i_id;
vlc_array_append( changes, find );
}
else if( person->psz_name && *person->psz_name )
{
find = ( ml_element_t* ) calloc( 1, sizeof( ml_element_t ) );
find->criteria = ML_PEOPLE;
find->lvalue.str = person->psz_role;
find->value.str = person->psz_name;
vlc_array_append( changes, find );
}
person = person->p_next;
}
ml_ftree_t* p_where = NULL;
ml_ftree_t* p_where_elt = ( ml_ftree_t* ) calloc( 1, sizeof( ml_ftree_t ) );
if( p_media->i_id )
{
p_where_elt->criteria = ML_ID;
p_where_elt->value.i = p_media->i_id ;
p_where_elt->comp = ML_COMP_EQUAL;
p_where = ml_FtreeFastAnd( p_where, p_where_elt );
}
else if( p_media->psz_uri )
{
p_where_elt->criteria = ML_URI;
p_where_elt->value.str = p_media->psz_uri;
p_where_elt->comp = ML_COMP_EQUAL;
p_where = ml_FtreeFastAnd( p_where, p_where_elt );
}
else
{
goto quit1;
}
i_ret = Update( p_ml, ML_MEDIA, NULL, p_where, changes );
quit1:
ml_FreeFindTree( p_where );
for( int i = 0; i < vlc_array_count( changes ); i++ )
/* Note: DO NOT free the strings because
* they belong to the ml_media_t object */
free( vlc_array_item_at_index( changes, i ) );
vlc_array_destroy( changes );
ml_UnlockMedia( p_media );
return i_ret;
}
/**
* @brief Update an album's cover art
* @param p_ml The Media Library
* @param i_album_id Album's ID
* @param psz_cover New cover art
* @return VLC success/error code
**/
int SetArtCover( media_library_t *p_ml,
int i_album_id, const char *psz_cover )
{
assert( i_album_id != 0 );
assert( psz_cover != NULL );
char *psz_query = sql_Printf( p_ml->p_sys->p_sql,
"UPDATE album SET cover = %Q WHERE id = '%d'",
psz_cover, i_album_id );
if( !psz_query )
return VLC_ENOMEM;
if( QuerySimple( p_ml, "%s", psz_query ) != VLC_SUCCESS )
{
msg_Warn( p_ml, "Could not update the album's cover art "
"(Database error)" );
free( psz_query );
return VLC_EGENERIC;
}
free( psz_query );
return VLC_SUCCESS;
}
......@@ -935,7 +935,6 @@ modules/lua/libs/volume.c
modules/lua/meta.c
modules/lua/vlc.c
modules/lua/vlc.h
modules/media_library/sql_media_library.c
modules/meta_engine/folder.c
modules/meta_engine/taglib.cpp
modules/misc/audioscrobbler.c
......
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