[vlc-devel] [PATCH 03/10] Extensions: core library

Jean-Philippe André jpeg at videolan.org
Thu Nov 12 00:08:03 CET 2009


This contains:
- Extensions manager
- Extensions activation / deactivation functions
- Separate threads for each extension
- Lua specific functions for calling the script
---
 modules/misc/lua/extension.c        |  773 +++++++++++++++++++++++++++++++++++
 modules/misc/lua/extension.h        |  120 ++++++
 modules/misc/lua/extension_thread.c |  387 ++++++++++++++++++
 3 files changed, 1280 insertions(+), 0 deletions(-)
 create mode 100644 modules/misc/lua/extension.c
 create mode 100644 modules/misc/lua/extension.h
 create mode 100644 modules/misc/lua/extension_thread.c

diff --git a/modules/misc/lua/extension.c b/modules/misc/lua/extension.c
new file mode 100644
index 0000000..5238199
--- /dev/null
+++ b/modules/misc/lua/extension.c
@@ -0,0 +1,773 @@
+/*****************************************************************************
+ * extension.c: Lua Extensions (meta data, web information, ...)
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Jean-Philippe André < jpeg # videolan.org >
+ *
+ * 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 "vlc.h"
+#include "libs.h"
+#include "extension.h"
+#include "assert.h"
+
+/* Functions to register */
+static const luaL_Reg p_reg[] =
+{
+    { NULL, NULL }
+};
+
+/*
+ * Extensions capabilities
+ * Note: #define and ppsz_capabilities must be in sync
+ */
+#define EXT_HAS_MENU          (1 << 0)
+#define EXT_TRIGGER_ONLY      (1 << 1)
+
+const char* const ppsz_capabilities[] = {
+    "menu",
+    "trigger",
+    NULL
+};
+
+static int ScanExtensions( extensions_manager_t *p_this );
+static int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
+                            lua_State *L, void *pb_continue );
+static int Control( extensions_manager_t *, int, va_list );
+static int GetMenu( extensions_manager_t *p_mgr, extension_t *p_ext,
+                    char ***pppsz_titles, uint16_t **ppi_ids );
+static lua_State* GetLuaState( extensions_manager_t *p_mgr,
+                               extension_t *p_ext );
+static int TriggerMenu( extension_t *p_ext, int id );
+static int TriggerExtension( extensions_manager_t *p_mgr,
+                             extension_t *p_ext );
+
+int vlclua_extension_deactivate( lua_State *L );
+
+
+/**
+ * Module entry-point
+ **/
+int Open_Extension( vlc_object_t *p_this )
+{
+    msg_Dbg( p_this, "Opening EXPERIMENTAL Lua Extension module" );
+
+    extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
+
+    p_mgr->pf_control = Control;
+
+    extensions_manager_sys_t *p_sys = ( extensions_manager_sys_t* )
+                    calloc( 1, sizeof( extensions_manager_sys_t ) );
+    if( !p_sys ) return VLC_ENOMEM;
+
+    p_mgr->p_sys = p_sys;
+    p_sys->pp_activated_extensions = NULL;
+    p_sys->i_num_activated = 0;
+    p_sys->p_dialog_provider = NULL;
+    vlc_mutex_init( &p_sys->lock );
+
+    p_mgr->i_num_extensions = 0;
+    p_mgr->pp_extensions = NULL;
+
+    /* Initialise Lua state structure */
+    lua_State *L = GetLuaState( p_mgr, NULL );
+    if( !L )
+    {
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+    p_sys->L = L;
+
+    /* Scan available Lua Extensions */
+    if( ScanExtensions( p_mgr ) != VLC_SUCCESS )
+    {
+        msg_Err( p_mgr, "Can't load extensions modules" );
+        return VLC_EGENERIC;
+    }
+
+    lua_close( L );
+    p_sys->L = NULL;
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * Module unload function
+ **/
+void Close_Extension( vlc_object_t *p_this )
+{
+    extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
+    msg_Dbg( p_mgr, "Deactivating all loaded extensions" );
+
+    vlc_mutex_lock( &p_mgr->p_sys->lock );
+    p_mgr->p_sys->b_killed = true;
+
+    /* Reverse walk so that memmove is a no-op */
+    for( int i = p_mgr->p_sys->i_num_activated - 1; i >= 0; i-- )
+    {
+        extension_t *p_ext = p_mgr->p_sys->pp_activated_extensions[i];
+        if( !p_ext ) break;
+        Deactivate( p_mgr, p_ext );
+        WaitForDeactivation( p_ext );
+    }
+
+    /* Release dialog provider, if any */
+    if( p_mgr->p_sys->p_dialog_provider )
+        vlc_object_release( p_mgr->p_sys->p_dialog_provider );
+
+    free( p_mgr->p_sys->pp_activated_extensions );
+    if( p_mgr->p_sys && p_mgr->p_sys->L )
+        lua_close( p_mgr->p_sys->L );
+
+    p_mgr->p_sys->pp_activated_extensions = NULL;
+    vlc_mutex_unlock( &p_mgr->p_sys->lock );
+
+    // FIXME: this looks bad
+    vlc_mutex_destroy( &p_mgr->p_sys->lock );
+    free( p_mgr->p_sys );
+    p_mgr->p_sys = NULL;
+
+    /* Free extensions' memory */
+    for( int j = 0; j < p_mgr->i_num_extensions; ++j )
+    {
+        extension_t *p_ext = p_mgr->pp_extensions[j];
+        if( !p_ext ) break;
+        if( p_ext->p_sys->L )
+            lua_close( p_ext->p_sys->L );
+        free( p_ext->psz_name );
+        free( p_ext->psz_title );
+
+        vlc_mutex_destroy( &p_ext->p_sys->running_lock );
+        vlc_mutex_destroy( &p_ext->p_sys->command_lock );
+        vlc_cond_destroy( &p_ext->p_sys->wait );
+
+        free( p_ext->p_sys );
+        free( p_ext );
+    }
+    free( p_mgr->pp_extensions );
+    p_mgr->pp_extensions = NULL;
+}
+
+/**
+ * Batch scan all Lua files in folder "extensions"
+ * @param p_mgr This extensions_manager_t object
+ **/
+static int ScanExtensions( extensions_manager_t *p_mgr )
+{
+    bool b_true = true;
+    int i_ret =
+        vlclua_scripts_batch_execute( VLC_OBJECT( p_mgr ),
+                                      "extensions",
+                                      &ScanLuaCallback,
+                                      p_mgr->p_sys->L, &b_true );
+
+    if( !i_ret )
+        return VLC_EGENERIC;
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * Batch scan all Lua files in folder "extensions": callback
+ * @param p_this This extensions_manager_t object
+ * @param psz_script Name of the script to run
+ * @param L Lua State, common to all scripts here
+ * @param pb_continue bool* that indicates whether to continue batch or not
+ **/
+int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
+                     lua_State *L, void *pb_continue )
+{
+    extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
+    bool b_ok = false;
+
+    msg_Dbg( p_mgr, "Scanning Lua script %s", psz_script );
+
+    vlc_mutex_lock( &p_mgr->p_sys->lock );
+
+    /* Add new script descriptor */
+    extension_t **tmp = realloc( p_mgr->pp_extensions,
+                sizeof( extension_t* ) * (p_mgr->i_num_extensions + 1) );
+    if( !tmp )
+    {
+        vlc_mutex_unlock( &p_mgr->p_sys->lock );
+        return 0; /* Stop batch */
+    }
+
+    p_mgr->pp_extensions = tmp;
+    p_mgr->i_num_extensions++;
+
+    extension_t *p_ext = tmp[ p_mgr->i_num_extensions - 1 ] =
+            ( extension_t* ) calloc( 1, sizeof( extension_t ) );
+    if( !p_ext )
+    {
+        vlc_mutex_unlock( &p_mgr->p_sys->lock );
+        return 0;
+    }
+
+    p_ext->psz_name = strdup( psz_script );
+    p_ext->p_sys = ( extension_sys_t* ) calloc( 1, sizeof( extension_sys_t ) );
+    if( !p_ext->p_sys )
+    {
+        p_mgr->i_num_extensions--; /* Note: leak */
+        vlc_mutex_unlock( &p_mgr->p_sys->lock );
+        return VLC_ENOMEM;
+    }
+    p_ext->p_sys->p_mgr = p_mgr;
+
+    /* Mutexes and conditions */
+    vlc_mutex_init( &p_ext->p_sys->command_lock );
+    vlc_mutex_init( &p_ext->p_sys->running_lock );
+    vlc_cond_init( &p_ext->p_sys->wait );
+
+    /* Load and run the script(s) */
+    if( luaL_dofile( L, psz_script ) )
+    {
+        msg_Warn( p_mgr, "Error loading script %s: %s", psz_script,
+                  lua_tostring( L, lua_gettop( L ) ) );
+        lua_pop( L, 1 );
+        goto exit;
+    }
+
+    /* Scan script for capabilities */
+    lua_getglobal( L, "descriptor" );
+
+    if( !lua_isfunction( L, -1 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function descriptor() not found", psz_script );
+        goto exit;
+    }
+
+    if( lua_pcall( L, 0, 1, 0 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function descriptor(): %s", psz_script,
+                  lua_tostring( L, lua_gettop( L ) ) );
+        goto exit;
+    }
+
+    if( lua_gettop( L ) )
+    {
+        if( lua_istable( L, -1 ) )
+        {
+            lua_getfield( L, -1, "capabilities" );
+            if( lua_istable( L, -1 ) )
+            {
+                lua_pushnil( L );
+                while( lua_next( L, -2 ) != 0 )
+                {
+                    /* Key is at index -2 and value at index -1. Discard key */
+                    const char *psz_cap = luaL_checkstring( L, -1 );
+                    int i_cap = 0;
+                    bool b_ok = false;
+                    /* Find this capability's flag */
+                    for( const char *iter = *ppsz_capabilities;
+                         iter != NULL;
+                         iter = ppsz_capabilities[ ++i_cap ])
+                    {
+                        if( !strcmp( iter, psz_cap ) )
+                        {
+                            /* Flag it! */
+                            p_ext->p_sys->i_capabilities |= 1 << i_cap;
+                            b_ok = true;
+                            break;
+                        }
+                    }
+                    if( !b_ok )
+                    {
+                        msg_Warn( p_mgr, "Extension capability '%s' unknown in"
+                                  " script %s", psz_cap, psz_script );
+                    }
+                    /* Removes 'value'; keeps 'key' for next iteration */
+                    lua_pop( L, 1 );
+                }
+            }
+            else
+            {
+                msg_Warn( p_mgr, "In script %s, function descriptor() "
+                              "did not return a table of capabilities.",
+                              psz_script );
+            }
+
+            lua_pop( L, 1 );
+            lua_getfield( L, -1, "title" );
+            if( lua_isstring( L, -1 ) )
+            {
+                p_ext->psz_title = strdup( luaL_checkstring( L, -1 ) );
+            }
+            else
+            {
+                msg_Dbg( p_mgr, "In script %s, function descriptor() "
+                                "did not return a string as title.",
+                                psz_script );
+                p_ext->psz_title = strdup( psz_script );
+            }
+        }
+        else
+        {
+            msg_Warn( p_mgr, "In script %s, function descriptor() "
+                      "did not return a table!", psz_script );
+            goto exit;
+        }
+    }
+    else
+    {
+        msg_Err( p_mgr, "Script %s went completely foobar", psz_script );
+        goto exit;
+    }
+
+    msg_Dbg( p_mgr, "Script %s has the following capability flags: 0x%x",
+             psz_script, p_ext->p_sys->i_capabilities );
+
+    b_ok = true;
+exit:
+    if( !b_ok )
+        p_mgr->i_num_extensions--; /* Note: leak */
+
+    vlc_mutex_unlock( &p_mgr->p_sys->lock );
+    /* Continue batch execution */
+    return pb_continue ? ( (* (bool*)pb_continue) ? -1 : 0 ) : -1;
+}
+
+static int Control( extensions_manager_t *p_mgr, int i_control, va_list args )
+{
+    vlc_object_t *p_object = NULL, **pp_object = NULL;
+    extension_t *p_ext = NULL;
+    bool *pb = NULL;
+    uint16_t **ppus = NULL;
+    char ***pppsz = NULL;
+    int i = 0;
+
+    switch( i_control )
+    {
+        case EXTENSION_ACTIVATE:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            return Activate( p_mgr, p_ext );
+
+        case EXTENSION_DEACTIVATE:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            return Deactivate( p_mgr, p_ext );
+
+        case EXTENSION_IS_ACTIVATED:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            pb = ( bool* ) va_arg( args, bool* );
+            *pb = IsActivated( p_mgr, p_ext );
+            break;
+
+        case EXTENSION_HAS_MENU:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            pb = ( bool* ) va_arg( args, bool* );
+            *pb = ( p_ext->p_sys->i_capabilities & EXT_HAS_MENU ) ? 1 : 0;
+            break;
+
+        case EXTENSION_GET_MENU:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            pppsz = ( char*** ) va_arg( args, char*** );
+            ppus = ( uint16_t** ) va_arg( args, uint16_t** );
+            return GetMenu( p_mgr, p_ext, pppsz, ppus );
+
+        case EXTENSION_TRIGGER_ONLY:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            pb = ( bool* ) va_arg( args, bool* );
+            *pb = ( p_ext->p_sys->i_capabilities & EXT_TRIGGER_ONLY ) ? 1 : 0;
+            break;
+
+        case EXTENSION_TRIGGER:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            return TriggerExtension( p_mgr, p_ext );
+
+        case EXTENSION_TRIGGER_MENU:
+            p_ext = ( extension_t* ) va_arg( args, extension_t* );
+            // GCC: 'uint16_t' is promoted to 'int' when passed through '...'
+            i = ( int ) va_arg( args, int );
+            return TriggerMenu( p_ext, i );
+
+        /* Dialogs */
+
+        case EXTENSION_SET_DIALOG_PROVIDER:
+            p_object = ( vlc_object_t* ) va_arg( args, vlc_object_t* );
+            if( p_object )
+            {
+                if( p_mgr->p_sys->p_dialog_provider )
+                    vlc_object_release( p_mgr->p_sys->p_dialog_provider );
+                vlc_object_hold( p_mgr->p_sys->p_dialog_provider = p_object );
+            }
+            else if( p_mgr->p_sys->p_dialog_provider )
+            {
+                vlc_object_release( p_mgr->p_sys->p_dialog_provider );
+                p_mgr->p_sys->p_dialog_provider = NULL;
+            }
+            break;
+
+        case EXTENSION_GET_DIALOG_PROVIDER:
+            pp_object = ( vlc_object_t** ) va_arg( args, vlc_object_t** );
+            if( !pp_object )
+                return VLC_EGENERIC;
+            *pp_object = p_mgr->p_sys->p_dialog_provider;
+            if( p_mgr->p_sys->p_dialog_provider )
+                vlc_object_hold( p_mgr->p_sys->p_dialog_provider );
+            break;
+
+        default:
+            msg_Err( p_mgr, "Control '%d' not yet implemented in Extension",
+                     i_control );
+            return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+int lua_ExtensionActivate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+    assert( p_mgr != NULL && p_ext != NULL );
+    return lua_ExecuteFunction( p_mgr, p_ext, "activate" );
+}
+
+int lua_ExtensionDeactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+    assert( p_mgr != NULL && p_ext != NULL );
+
+    if( !p_ext->p_sys->L )
+        return VLC_SUCCESS;
+
+    int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate" );
+
+    /* Clear Lua State */
+    lua_close( p_ext->p_sys->L );
+    p_ext->p_sys->L = NULL;
+
+    return i_ret;
+}
+
+int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr,
+                              extension_t *p_ext,
+                              extension_widget_t *p_widget )
+{
+    if( !p_ext->p_sys->L )
+        return VLC_SUCCESS;
+
+    widget_sys_t *p_sys = p_widget->p_sys;
+
+    return lua_ExecuteFunction( p_mgr, p_ext, p_sys->psz_function );
+}
+
+
+/**
+ * Get the list of menu entries from an extension script
+ * @param p_mgr
+ * @param p_ext
+ * @param pppsz_titles Pointer to NULL. All strings must be freed by the caller
+ * @param ppi_ids Pointer to NULL. Must be feed by the caller.
+ * @note This function is allowed to run in the UI thread. This means
+ *       that it MUST respond very fast. FIXME: forbid access to libs
+ **/
+static int GetMenu( extensions_manager_t *p_mgr, extension_t *p_ext,
+                    char ***pppsz_titles, uint16_t **ppi_ids )
+{
+    assert( *pppsz_titles == NULL );
+    assert( *ppi_ids == NULL );
+
+    if( !IsActivated( p_mgr, p_ext ) )
+    {
+        msg_Dbg( p_mgr, "Can't get menu before activating the extension!" );
+        return VLC_EGENERIC;
+    }
+
+    if( !LockExtension( p_ext ) )
+    {
+        /* Dying extension, fail. */
+        return VLC_EGENERIC;
+    }
+
+    int i_ret = VLC_EGENERIC;
+    lua_State *L = GetLuaState( p_mgr, p_ext );
+
+    if( ( p_ext->p_sys->i_capabilities & EXT_HAS_MENU ) == 0 )
+    {
+        msg_Dbg( p_mgr, "can't get a menu from an extension without menu!" );
+        goto exit;
+    }
+
+    lua_getglobal( L, "menu" );
+
+    if( !lua_isfunction( L, -1 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function menu() not found", p_ext->psz_name );
+        goto exit;
+    }
+
+    if( lua_pcall( L, 0, 1, 0 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function menu(): %s", p_ext->psz_name,
+                  lua_tostring( L, lua_gettop( L ) ) );
+        goto exit;
+    }
+
+    if( lua_gettop( L ) )
+    {
+        if( lua_istable( L, -1 ) )
+        {
+            /* Get table size */
+            size_t i_size = lua_objlen( L, -1 );
+            *pppsz_titles = ( char** ) calloc( i_size+1, sizeof( char* ) );
+            *ppi_ids = ( uint16_t* ) calloc( i_size+1, sizeof( uint16_t ) );
+
+            /* Walk table */
+            size_t i_idx = 0;
+            lua_pushnil( L );
+            while( lua_next( L, -2 ) != 0 )
+            {
+                assert( i_idx < i_size );
+                if( (!lua_isstring( L, -1 )) || (!lua_isnumber( L, -2 )) )
+                {
+                    msg_Warn( p_mgr, "In script %s, an entry in "
+                              "the menu table is invalid!", p_ext->psz_name );
+                    goto exit;
+                }
+                (*pppsz_titles)[ i_idx ] = strdup( luaL_checkstring( L, -1 ) );
+                (*ppi_ids)[ i_idx ] = (uint16_t) ( luaL_checkinteger( L, -2 ) & 0xFFFF );
+                i_idx++;
+                lua_pop( L, 1 );
+            }
+        }
+        else
+        {
+            msg_Warn( p_mgr, "Function menu() in script %s "
+                      "did not return a table", p_ext->psz_name );
+            goto exit;
+        }
+    }
+    else
+    {
+        msg_Warn( p_mgr, "Script %s went completely foobar", p_ext->psz_name );
+        goto exit;
+    }
+
+    i_ret = VLC_SUCCESS;
+
+exit:
+    UnlockExtension( p_ext );
+    if( i_ret != VLC_SUCCESS )
+    {
+        msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)",
+                 __func__, __FILE__, __LINE__ );
+    }
+    return i_ret;
+}
+
+/* Must be entered with the Lock on Extension */
+static lua_State* GetLuaState( extensions_manager_t *p_mgr,
+                               extension_t *p_ext )
+{
+    lua_State *L = NULL;
+    if( p_ext )
+        L = p_ext->p_sys->L;
+
+    if( !L )
+    {
+        L = luaL_newstate();
+        if( !L )
+        {
+            msg_Err( p_mgr, "Could not create new Lua State" );
+            return NULL;
+        }
+        luaL_openlibs( L );
+        luaL_register( L, "vlc", p_reg );
+        luaopen_msg( L );
+
+        lua_pushlightuserdata( L, p_mgr );
+        lua_setfield( L, -2, "private" );
+
+        lua_pushlightuserdata( L, p_ext );
+        lua_setfield( L, -2, "extension" );
+
+        if( p_ext )
+        {
+            /* Load more libraries */
+            luaopen_acl( L );
+            luaopen_config( L );
+            luaopen_dialog( L );
+            luaopen_input( L );
+            luaopen_msg( L );
+            luaopen_misc( L );
+            luaopen_net( L );
+            luaopen_object( L );
+            luaopen_osd( L );
+            luaopen_playlist( L );
+            luaopen_sd( L );
+            luaopen_stream( L );
+            luaopen_strings( L );
+            luaopen_variables( L );
+            luaopen_video( L );
+            luaopen_vlm( L );
+            luaopen_volume( L );
+
+            /* Register extension specific functions */
+            lua_getglobal( L, "vlc" );
+            lua_pushcfunction( L, vlclua_extension_deactivate );
+            lua_setfield( L, -2, "deactivate" );
+
+            /* Load and run the script(s) */
+            if( luaL_dofile( L, p_ext->psz_name ) != 0 )
+            {
+                msg_Warn( p_mgr, "Error loading script %s: %s", p_ext->psz_name,
+                          lua_tostring( L, lua_gettop( L ) ) );
+                lua_pop( L, 1 );
+                return NULL;
+            }
+
+            p_ext->p_sys->L = L;
+        }
+    }
+#ifndef NDEBUG
+    else
+    {
+        msg_Dbg( p_mgr, "Reusing old Lua state for extension '%s'",
+                 p_ext->psz_name );
+    }
+#endif
+
+    return L;
+}
+
+/**
+ * Execute a function in a Lua script
+ * @return < 0 in case of failure, >= 0 in case of success
+ * @note It's better to call this function from a dedicated thread
+ * (see extension_thread.c)
+ **/
+int lua_ExecuteFunction( extensions_manager_t *p_mgr,
+                         extension_t *p_ext,
+                         const char *psz_function )
+{
+    int i_ret = VLC_EGENERIC;
+    assert( p_mgr != NULL );
+    assert( p_ext != NULL );
+
+    lua_State *L = GetLuaState( p_mgr, p_ext );
+    lua_getglobal( L, psz_function );
+
+    if( !lua_isfunction( L, -1 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function %s() not found", p_ext->psz_name, psz_function );
+        goto exit;
+    }
+
+    if( lua_pcall( L, 0, 1, 0 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function %s(): %s", p_ext->psz_name, psz_function,
+                  lua_tostring( L, lua_gettop( L ) ) );
+        goto exit;
+    }
+
+    i_ret = VLC_SUCCESS;
+exit:
+    return i_ret;
+}
+
+static inline int TriggerMenu( extension_t *p_ext, int i_id )
+{
+    return PushCommand( p_ext, CMD_TRIGGERMENU, i_id );
+}
+
+int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr,
+                              extension_t *p_ext, int id )
+{
+    int i_ret = VLC_EGENERIC;
+    lua_State *L = GetLuaState( p_mgr, p_ext );
+
+    if( !L )
+        return VLC_EGENERIC;
+
+    luaopen_dialog( L );
+
+    lua_getglobal( L, "trigger_menu" );
+    if( !lua_isfunction( L, -1 ) )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function trigger_menu() not found", p_ext->psz_name );
+        return VLC_EGENERIC;
+    }
+
+    /* Pass id as unique argument to the function */
+    lua_pushinteger( L, id );
+
+    if( lua_pcall( L, 1, 1, 0 ) != 0 )
+    {
+        msg_Warn( p_mgr, "Error while runing script %s, "
+                  "function trigger_menu(): %s", p_ext->psz_name,
+                  lua_tostring( L, lua_gettop( L ) ) );
+        return VLC_EGENERIC;
+    }
+
+    if( i_ret < VLC_SUCCESS )
+    {
+        msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)",
+                 __func__, __FILE__, __LINE__ );
+    }
+    return i_ret;
+}
+
+/** Directly trigger an extension, without activating it
+ * This is NOT multithreaded, and this code runs in the UI thread
+ * @param p_mgr
+ * @param p_ext Extension to trigger
+ * @return Value returned by the lua function "trigger"
+ **/
+static int TriggerExtension( extensions_manager_t *p_mgr,
+                             extension_t *p_ext )
+{
+    int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "trigger" );
+
+    /* Close lua state for trigger-only extensions */
+    if( p_ext->p_sys->L )
+        lua_close( p_ext->p_sys->L );
+    p_ext->p_sys->L = NULL;
+
+    return i_ret;
+}
+
+/** Retrieve extension associated to the current script
+ * @param L current lua_State
+ * @return Lua userdata "vlc.extension"
+ **/
+extension_t *vlclua_extension_get( lua_State *L )
+{
+    extension_t *p_ext = NULL;
+    lua_getglobal( L, "vlc" );
+    lua_getfield( L, -1, "extension" );
+    p_ext = (extension_t*) lua_topointer( L, lua_gettop( L ) );
+    lua_pop( L, 2 );
+    return p_ext;
+}
+
+/** Deactivate an extension by order from the extension itself
+ * @param L lua_State
+ * @note This is an asynchronous call. A script calling vlc.deactivate() will
+ * be executed to the end before the last call to deactivate() is done.
+ **/
+int vlclua_extension_deactivate( lua_State *L )
+{
+    extension_t *p_ext = vlclua_extension_get( L );
+    int i_ret = Deactivate( p_ext->p_sys->p_mgr, p_ext );
+    return ( i_ret == VLC_SUCCESS ) ? 1 : 0;
+}
diff --git a/modules/misc/lua/extension.h b/modules/misc/lua/extension.h
new file mode 100644
index 0000000..395aeb7
--- /dev/null
+++ b/modules/misc/lua/extension.h
@@ -0,0 +1,120 @@
+/*****************************************************************************
+ * extension.h: Lua Extensions (meta data, web information, ...)
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Jean-Philippe André < jpeg # videolan.org >
+ *
+ * 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 LUA_EXTENSION_H
+#define LUA_EXTENSION_H
+
+#include <vlc_extensions.h>
+
+///< Array of extension_t
+TYPEDEF_ARRAY( extension_t, array_extension_t );
+
+/* List of available commands */
+#define CMD_ACTIVATE    1
+#define CMD_DEACTIVATE  2
+#define CMD_TRIGGERMENU 3    /* Arg1 = int*, pointing to id to trigger. free */
+#define CMD_CLICK       4    /* Arg1 = extension_widget_t* */
+#define CMD_CLOSE       5
+
+struct extensions_manager_sys_t
+{
+    /* List of activated extensions */
+    int i_num_activated;
+    extension_t **pp_activated_extensions;
+
+    /* Lua specific */
+    lua_State *L;
+
+    /* Communication with the UI */
+    vlc_object_t *p_dialog_provider;
+
+    vlc_mutex_t lock;
+    bool b_killed;
+};
+
+struct extension_sys_t
+{
+    /* Extension general */
+    int i_capabilities;
+
+    /* Lua specific */
+    lua_State *L;
+
+    /* Thread data */
+    vlc_thread_t thread;
+    vlc_mutex_t command_lock;
+    vlc_mutex_t running_lock;
+    vlc_cond_t wait;
+    bool b_exiting;
+    extensions_manager_t *p_mgr;     ///< Parent
+    /* Queue of commands to execute */
+    struct command_t
+    {
+        int i_command;
+        void *data[10];         ///< Optional void* arguments
+        struct command_t *next; ///< Next command
+    } *command;
+};
+
+typedef struct
+{
+    char *psz_function; ///< Function to call in case of click
+} widget_sys_t;
+
+/* Extensions: manager functions */
+int Activate( extensions_manager_t *p_mgr, extension_t * );
+bool IsActivated( extensions_manager_t *p_mgr, extension_t * );
+int Deactivate( extensions_manager_t *p_mgr, extension_t * );
+void WaitForDeactivation( extension_t *p_ext );
+int PushCommand( extension_t *p_ext, int i_command, ... );
+
+/* Lua specific functions */
+extension_t *vlclua_extension_get( lua_State *L );
+int lua_ExtensionActivate( extensions_manager_t *, extension_t * );
+int lua_ExtensionDeactivate( extensions_manager_t *, extension_t * );
+int lua_ExecuteFunction( extensions_manager_t *p_mgr, extension_t *p_ext,
+                         const char *psz_function );
+int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr,
+                              extension_t *p_ext,
+                              extension_widget_t *p_widget );
+int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr,
+                              extension_t *p_ext, int id );
+
+/* Lock this extension. Can fail. */
+static inline bool LockExtension( extension_t *p_ext )
+{
+    vlc_mutex_lock( &p_ext->p_sys->running_lock );
+    if( p_ext->p_sys->b_exiting )
+    {
+        vlc_mutex_unlock( &p_ext->p_sys->running_lock );
+        return false;
+    }
+    return true;
+}
+
+static inline void UnlockExtension( extension_t *p_ext )
+{
+    vlc_mutex_unlock( &p_ext->p_sys->running_lock );
+}
+
+#endif // LUA_EXTENSION_H
diff --git a/modules/misc/lua/extension_thread.c b/modules/misc/lua/extension_thread.c
new file mode 100644
index 0000000..7f04a5f
--- /dev/null
+++ b/modules/misc/lua/extension_thread.c
@@ -0,0 +1,387 @@
+/*****************************************************************************
+ * extension_thread.c: Extensions Manager, Threads manager (no Lua here)
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Jean-Philippe André < jpeg # videolan.org >
+ *
+ * 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.
+ *****************************************************************************/
+
+/* I don't want to include lua headers here */
+typedef struct lua_State lua_State;
+
+#include "extension.h"
+#include "assert.h"
+
+struct thread_sys_t
+{
+    extensions_manager_t *p_mgr;
+    extension_t *p_ext;
+};
+
+/** Thread Run */
+static void* Run( void *data );
+static void FreeCommands( struct command_t *command );
+static int RemoveActivated( extensions_manager_t *p_mgr, extension_t *p_ext );
+
+/**
+ * Activate an extension
+ * @param p_mgr This manager
+ * @param p_ext Extension to activate
+ * @return The usual VLC return codes
+ **/
+int Activate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+    assert( p_ext != NULL );
+
+    struct extension_sys_t *p_sys = p_ext->p_sys;
+    assert( p_sys != NULL );
+
+    msg_Dbg( p_mgr, "Activating extension '%s'", p_ext->psz_title );
+
+    if( IsActivated( p_mgr, p_ext ) )
+    {
+        msg_Warn( p_mgr, "Extension is already activated!" );
+        return VLC_EGENERIC;
+    }
+
+    /* Add this script to the activated extensions list */
+    if( !p_mgr->p_sys->pp_activated_extensions )
+    {
+        p_mgr->p_sys->pp_activated_extensions =
+                malloc( sizeof( extension_t* ) );
+        p_mgr->p_sys->pp_activated_extensions[0] = p_ext;
+        p_mgr->p_sys->i_num_activated = 1;
+    }
+    else
+    {
+        int i_num = p_mgr->p_sys->i_num_activated + 1;
+        extension_t **tmp = ( extension_t** )
+                realloc( p_mgr->p_sys->pp_activated_extensions,
+                         sizeof( extension_t* ) * i_num );
+        if( !tmp )
+            return VLC_ENOMEM;
+        tmp[ p_mgr->p_sys->i_num_activated ] = p_ext;
+        p_mgr->p_sys->pp_activated_extensions = tmp;
+        p_mgr->p_sys->i_num_activated = i_num;
+    }
+
+    /* Prepare first command */
+    p_sys->command = calloc( 1, sizeof( struct command_t ) );
+    if( !p_sys->command )
+        return VLC_ENOMEM;
+    p_sys->command->i_command = CMD_ACTIVATE; /* No params */
+
+    /* Start thread */
+    p_sys->b_exiting = false;
+
+    if( vlc_clone( &p_sys->thread, Run, p_ext, VLC_THREAD_PRIORITY_LOW )
+        != VLC_SUCCESS )
+    {
+        p_sys->b_exiting = true;
+        // Note: Automatically deactivating the extension...
+        Deactivate( p_mgr, p_ext );
+        return VLC_ENOMEM;
+    }
+
+    return VLC_SUCCESS;
+}
+
+/** Look for an extension in the activated extensions list */
+bool IsActivated( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+    vlc_mutex_lock( &p_mgr->p_sys->lock );
+    if( !p_mgr->p_sys->pp_activated_extensions )
+    {
+        vlc_mutex_unlock( &p_mgr->p_sys->lock );
+        return false;
+    }
+    for( int i = 0; i < p_mgr->p_sys->i_num_activated; i++ )
+    {
+        extension_t *p_iter = p_mgr->p_sys->pp_activated_extensions[i];
+        if( !p_iter )
+            break;
+        if( !strcmp( p_iter->psz_name, p_ext->psz_name ) )
+        {
+            vlc_mutex_unlock( &p_mgr->p_sys->lock );
+            return true;
+        }
+    }
+    vlc_mutex_unlock( &p_mgr->p_sys->lock );
+
+    return false;
+}
+
+/** Recursively drop and free commands starting from "command" */
+static void FreeCommands( struct command_t *command )
+{
+    if( !command ) return;
+    struct command_t *next = command->next;
+    switch( command->i_command )
+    {
+        case CMD_ACTIVATE:
+        case CMD_DEACTIVATE:
+        case CMD_CLICK: // Arg1 must not be freed
+            break;
+
+        case CMD_TRIGGERMENU:
+            free( command->data[0] ); // Arg1 is int*, to free
+            break;
+    }
+    free( command );
+    FreeCommands( next );
+}
+
+/** Deactivate this extension: pushes immediate command and drops queued */
+int Deactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+    (void) p_mgr;
+    vlc_mutex_lock( &p_ext->p_sys->command_lock );
+
+    if( p_ext->p_sys->b_exiting )
+    {
+        vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+        return VLC_EGENERIC;
+    }
+
+    /* Free the list of commands */
+    FreeCommands( p_ext->p_sys->command );
+
+    /* Push command */
+    struct command_t *cmd = calloc( 1, sizeof( struct command_t ) );
+    cmd->i_command = CMD_DEACTIVATE;
+    p_ext->p_sys->command = cmd;
+
+    vlc_cond_signal( &p_ext->p_sys->wait );
+    vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+
+    return VLC_SUCCESS;
+}
+
+/** Remove an extension from the activated list */
+static int RemoveActivated( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+    if( p_mgr->p_sys->b_killed )
+        return VLC_SUCCESS;
+    vlc_mutex_lock( &p_mgr->p_sys->lock );
+    int i_idx = -1;
+    for( int i = 0; i < p_mgr->p_sys->i_num_activated; ++i )
+    {
+        extension_t *p_iter = p_mgr->p_sys->pp_activated_extensions[i];
+        if( !p_iter )
+            break;
+        if( !strcmp( p_iter->psz_name, p_ext->psz_name ) )
+        {
+            i_idx = i;
+            break;
+        }
+    }
+    if( i_idx < 0 )
+    {
+        msg_Dbg( p_mgr, "Can't find extension '%s' in the activated list",
+                 p_ext->psz_title );
+        vlc_mutex_unlock( &p_mgr->p_sys->lock );
+        return VLC_EGENERIC;
+    }
+
+    /* Move memory. Do not realloc.
+       Yeah, it leaks a bit... until next extension is loaded. Who cares? */
+    memmove( &( p_mgr->p_sys->pp_activated_extensions[ i_idx ] ),
+             &( p_mgr->p_sys->pp_activated_extensions[ i_idx + 1 ] ),
+             p_mgr->p_sys->i_num_activated - i_idx - 1 );
+    p_mgr->p_sys->i_num_activated--;
+
+    if( !p_mgr->p_sys->i_num_activated )
+    {
+        free( p_mgr->p_sys->pp_activated_extensions );
+        p_mgr->p_sys->pp_activated_extensions = NULL;
+    }
+
+    vlc_mutex_unlock( &p_mgr->p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+/** Wait for an extension to finish */
+void WaitForDeactivation( extension_t *p_ext )
+{
+    void *pointer = NULL;
+    vlc_cond_signal( &p_ext->p_sys->wait );
+    vlc_join( p_ext->p_sys->thread, &pointer );
+}
+
+/** Push a UI command */
+int PushCommand( extension_t *p_ext,
+                 int i_command,
+                 ... )
+{
+    vlc_mutex_lock( &p_ext->p_sys->command_lock );
+
+    va_list args;
+    va_start( args, i_command );
+
+    /* Create command */
+    struct command_t *cmd = calloc( 1, sizeof( struct command_t ) );
+    cmd->i_command = i_command;
+    switch( i_command )
+    {
+        case CMD_CLICK:
+            cmd->data[0] = va_arg( args, void* );
+            break;
+        case CMD_CLOSE:
+            // Nothing to do here
+            break;
+        case CMD_TRIGGERMENU:
+            {
+                int *pi = malloc( sizeof( int ) );
+                if( !pi ) return VLC_ENOMEM;
+                *pi = va_arg( args, int );
+                cmd->data[0] = pi;
+            }
+            break;
+        default:
+            msg_Dbg( p_ext->p_sys->p_mgr,
+                     "Unknown command send to extension: %d", i_command );
+            break;
+    }
+
+    va_end( args );
+
+    /* Push command to the end of the queue */
+    struct command_t *last = p_ext->p_sys->command;
+    if( !last )
+    {
+        p_ext->p_sys->command = cmd;
+    }
+    else
+    {
+        while( last->next != NULL )
+        {
+            last = last->next;
+        }
+        last->next = cmd;
+    }
+
+    vlc_cond_signal( &p_ext->p_sys->wait );
+    vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+    return VLC_SUCCESS;
+}
+
+/* Thread loop */
+static void* Run( void *data )
+{
+    extension_t *p_ext = data;
+    extensions_manager_t *p_mgr = p_ext->p_sys->p_mgr;
+
+    vlc_mutex_lock( &p_ext->p_sys->command_lock );
+
+    while( !p_ext->p_sys->b_exiting )
+    {
+        /* Pop command in front */
+        struct command_t *cmd = p_ext->p_sys->command;
+        if( cmd )
+        {
+            p_ext->p_sys->command = cmd->next;
+        }
+
+        vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+
+        /* Run command */
+        if( cmd )
+        {
+            if( LockExtension( p_ext ) )
+            {
+                switch( cmd->i_command )
+                {
+                    case CMD_ACTIVATE:
+                    {
+                        if( lua_ExecuteFunction( p_mgr, p_ext, "activate" ) < 0 )
+                        {
+                            msg_Dbg( p_mgr, "Could not activate extension!" );
+                            Deactivate( p_mgr, p_ext );
+                        }
+                        break;
+                    }
+
+                    case CMD_DEACTIVATE:
+                    {
+                        msg_Dbg( p_mgr, "Deactivating '%s'", p_ext->psz_title );
+                        if( lua_ExtensionDeactivate( p_mgr, p_ext ) < 0 )
+                        {
+                            msg_Warn( p_mgr, "Extension '%s' did not deactivate properly",
+                                      p_ext->psz_title );
+                        }
+                        p_ext->p_sys->b_exiting = true;
+                        RemoveActivated( p_mgr, p_ext );
+                        break;
+                    }
+
+                    case CMD_CLOSE:
+                    {
+                        lua_ExecuteFunction( p_mgr, p_ext, "close" );
+                        break;
+                    }
+
+                    case CMD_CLICK:
+                    {
+                        extension_widget_t *p_widget = cmd->data[0];
+                        assert( p_widget );
+                        msg_Dbg( p_mgr, "Clicking '%s': '%s'",
+                                 p_ext->psz_name, p_widget->psz_text );
+                        if( !lua_ExtensionWidgetClick( p_mgr, p_ext, p_widget )
+                            < 0 )
+                        {
+                            msg_Warn( p_mgr, "Could not translate click" );
+                        }
+                        break;
+                    }
+
+                    case CMD_TRIGGERMENU:
+                    {
+                        int *pi_id = cmd->data[0];
+                        assert( pi_id );
+                        msg_Dbg( p_mgr, "Trigger menu %d of '%s'",
+                                 *pi_id, p_ext->psz_name );
+                        lua_ExtensionTriggerMenu( p_mgr, p_ext, *pi_id );
+                        free( pi_id );
+                        break;
+                    }
+
+                    default:
+                    {
+                        msg_Dbg( p_mgr, "Unknown command in extension command queue: %d",
+                                 cmd->i_command );
+                        break;
+                    }
+                }
+                UnlockExtension( p_ext );
+            }
+        }
+
+        vlc_mutex_lock( &p_ext->p_sys->command_lock );
+        if( !p_ext->p_sys->b_exiting && !p_ext->p_sys->command )
+        {
+            vlc_cond_wait( &p_ext->p_sys->wait, &p_ext->p_sys->command_lock );
+        }
+    }
+
+    vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+    msg_Dbg( p_mgr, "Extension thread ending..." );
+
+    // Note: At this point, the extension should be deactivated
+    return NULL;
+}
-- 
1.6.5.2




More information about the vlc-devel mailing list