]> git.sesse.net Git - vlc/blob - modules/misc/lua/extension_thread.c
Extensions: fix a leak (and a potential crash)
[vlc] / modules / misc / lua / extension_thread.c
1 /*****************************************************************************
2  * extension_thread.c: Extensions Manager, Threads manager (no Lua here)
3  *****************************************************************************
4  * Copyright (C) 2009-2010 VideoLAN and authors
5  * $Id$
6  *
7  * Authors: Jean-Philippe AndrĂ© < jpeg # videolan.org >
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /* I don't want to include lua headers here */
25 typedef struct lua_State lua_State;
26
27 #include "extension.h"
28 #include "assert.h"
29
30 struct thread_sys_t
31 {
32     extensions_manager_t *p_mgr;
33     extension_t *p_ext;
34 };
35
36 /** Thread Run */
37 static void* Run( void *data );
38 static void FreeCommands( struct command_t *command );
39 static int RemoveActivated( extensions_manager_t *p_mgr, extension_t *p_ext );
40
41 /**
42  * Activate an extension
43  * @param p_mgr This manager
44  * @param p_ext Extension to activate
45  * @return The usual VLC return codes
46  **/
47 int Activate( extensions_manager_t *p_mgr, extension_t *p_ext )
48 {
49     assert( p_ext != NULL );
50
51     struct extension_sys_t *p_sys = p_ext->p_sys;
52     assert( p_sys != NULL );
53
54     msg_Dbg( p_mgr, "Activating extension '%s'", p_ext->psz_title );
55
56     if( IsActivated( p_mgr, p_ext ) )
57     {
58         msg_Warn( p_mgr, "Extension is already activated!" );
59         return VLC_EGENERIC;
60     }
61
62     /* Add this script to the activated extensions list */
63     vlc_mutex_lock( &p_mgr->p_sys->lock );
64     ARRAY_APPEND( p_mgr->p_sys->activated_extensions, p_ext );
65     vlc_mutex_unlock( &p_mgr->p_sys->lock );
66
67     /* Prepare first command */
68     p_sys->command = calloc( 1, sizeof( struct command_t ) );
69     if( !p_sys->command )
70         return VLC_ENOMEM;
71     p_sys->command->i_command = CMD_ACTIVATE; /* No params */
72
73     /* Start thread */
74     p_sys->b_exiting = false;
75
76     if( vlc_clone( &p_sys->thread, Run, p_ext, VLC_THREAD_PRIORITY_LOW )
77         != VLC_SUCCESS )
78     {
79         p_sys->b_exiting = true;
80         // Note: Automatically deactivating the extension...
81         Deactivate( p_mgr, p_ext );
82         return VLC_ENOMEM;
83     }
84
85     return VLC_SUCCESS;
86 }
87
88 /** Look for an extension in the activated extensions list */
89 bool IsActivated( extensions_manager_t *p_mgr, extension_t *p_ext )
90 {
91     assert( p_ext != NULL );
92     vlc_mutex_lock( &p_mgr->p_sys->lock );
93
94     extension_t *p_iter;
95     FOREACH_ARRAY( p_iter, p_mgr->p_sys->activated_extensions )
96     {
97         if( !p_iter )
98             break;
99         assert( p_iter->psz_name != NULL );
100         if( !strcmp( p_iter->psz_name, p_ext->psz_name ) )
101         {
102             vlc_mutex_unlock( &p_mgr->p_sys->lock );
103             return true;
104         }
105     }
106     FOREACH_END()
107
108     vlc_mutex_unlock( &p_mgr->p_sys->lock );
109     return false;
110 }
111
112 /** Recursively drop and free commands starting from "command" */
113 static void FreeCommands( struct command_t *command )
114 {
115     if( !command ) return;
116     struct command_t *next = command->next;
117     switch( command->i_command )
118     {
119         case CMD_ACTIVATE:
120         case CMD_DEACTIVATE:
121         case CMD_CLICK: // Arg1 must not be freed
122             break;
123
124         case CMD_TRIGGERMENU:
125         case CMD_PLAYING_CHANGED:
126             free( command->data[0] ); // Arg1 is int*, to free
127             break;
128
129         default:
130             break;
131     }
132     free( command );
133     FreeCommands( next );
134 }
135
136 /** Deactivate this extension: pushes immediate command and drops queued */
137 int Deactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
138 {
139     (void) p_mgr;
140     vlc_mutex_lock( &p_ext->p_sys->command_lock );
141
142     if( p_ext->p_sys->b_exiting )
143     {
144         vlc_mutex_unlock( &p_ext->p_sys->command_lock );
145         return VLC_EGENERIC;
146     }
147
148     /* Free the list of commands */
149     FreeCommands( p_ext->p_sys->command );
150
151     /* Push command */
152     struct command_t *cmd = calloc( 1, sizeof( struct command_t ) );
153     cmd->i_command = CMD_DEACTIVATE;
154     p_ext->p_sys->command = cmd;
155
156     vlc_cond_signal( &p_ext->p_sys->wait );
157     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
158
159     return VLC_SUCCESS;
160 }
161
162 /** Remove an extension from the activated list */
163 static int RemoveActivated( extensions_manager_t *p_mgr, extension_t *p_ext )
164 {
165     if( p_mgr->p_sys->b_killed )
166         return VLC_SUCCESS;
167     vlc_mutex_lock( &p_mgr->p_sys->lock );
168
169     int i_idx = -1;
170     extension_t *p_iter;
171     FOREACH_ARRAY( p_iter, p_mgr->p_sys->activated_extensions )
172     {
173         i_idx++;
174         if( !p_iter )
175         {
176             i_idx = -1;
177             break;
178         }
179         assert( p_iter->psz_name != NULL );
180         if( !strcmp( p_iter->psz_name, p_ext->psz_name ) )
181             break;
182     }
183     FOREACH_END()
184
185     if( i_idx >= 0 )
186     {
187         ARRAY_REMOVE( p_mgr->p_sys->activated_extensions, i_idx );
188     }
189     else
190     {
191         msg_Dbg( p_mgr, "Can't find extension '%s' in the activated list",
192                  p_ext->psz_title );
193     }
194
195     vlc_mutex_unlock( &p_mgr->p_sys->lock );
196     return (i_idx >= 0) ? VLC_SUCCESS : VLC_EGENERIC;
197 }
198
199 /** Wait for an extension to finish */
200 void WaitForDeactivation( extension_t *p_ext )
201 {
202     vlc_join( p_ext->p_sys->thread, NULL );
203 }
204
205 /** Push a UI command */
206 int __PushCommand( extension_t *p_ext,  bool b_unique, int i_command,
207                    va_list args )
208 {
209     vlc_mutex_lock( &p_ext->p_sys->command_lock );
210
211     /* Create command */
212     struct command_t *cmd = calloc( 1, sizeof( struct command_t ) );
213     cmd->i_command = i_command;
214     switch( i_command )
215     {
216         case CMD_CLICK:
217             cmd->data[0] = va_arg( args, void* );
218             break;
219         case CMD_TRIGGERMENU:
220             {
221                 int *pi = malloc( sizeof( int ) );
222                 if( !pi )
223                 {
224                     free( cmd );
225                     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
226                     return VLC_ENOMEM;
227                 }
228                 *pi = va_arg( args, int );
229                 cmd->data[0] = pi;
230             }
231             break;
232         case CMD_PLAYING_CHANGED:
233             {
234                 int *pi = malloc( sizeof( int ) );
235                 if( !pi )
236                 {
237                     free( cmd );
238                     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
239                     return VLC_ENOMEM;
240                 }
241                 *pi = va_arg( args, int );
242                 cmd->data[0] = pi;
243             }
244             break;
245         case CMD_CLOSE:
246         case CMD_SET_INPUT:
247         case CMD_UPDATE_META:
248             // Nothing to do here
249             break;
250         default:
251             msg_Dbg( p_ext->p_sys->p_mgr,
252                      "Unknown command send to extension: %d", i_command );
253             break;
254     }
255
256     /* Push command to the end of the queue */
257     struct command_t *last = p_ext->p_sys->command;
258     if( !last )
259     {
260         p_ext->p_sys->command = cmd;
261     }
262     else
263     {
264         bool b_skip = false;
265         while( last->next != NULL )
266         {
267             if( b_unique && last->i_command == i_command )
268             {
269                 // Do not push this 'unique' command a second time
270                 b_skip = !memcmp( last->data, cmd->data, sizeof( cmd->data ) );
271                 break;
272             }
273             else
274             {
275                 last = last->next;
276             }
277         }
278         if( !b_skip )
279         {
280             last->next = cmd;
281         }
282         else
283         {
284             FreeCommands( cmd );
285         }
286     }
287
288     vlc_cond_signal( &p_ext->p_sys->wait );
289     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
290     return VLC_SUCCESS;
291 }
292
293 /* Thread loop */
294 static void* Run( void *data )
295 {
296     extension_t *p_ext = data;
297     extensions_manager_t *p_mgr = p_ext->p_sys->p_mgr;
298
299     vlc_mutex_lock( &p_ext->p_sys->command_lock );
300
301     while( !p_ext->p_sys->b_exiting )
302     {
303         /* Pop command in front */
304         struct command_t *cmd = p_ext->p_sys->command;
305         vlc_mutex_unlock( &p_ext->p_sys->command_lock );
306
307         /* Run command */
308         if( cmd )
309         {
310             if( LockExtension( p_ext ) )
311             {
312                 switch( cmd->i_command )
313                 {
314                     case CMD_ACTIVATE:
315                     {
316                         if( lua_ExecuteFunction( p_mgr, p_ext, "activate", LUA_END ) < 0 )
317                         {
318                             msg_Dbg( p_mgr, "Could not activate extension!" );
319                             Deactivate( p_mgr, p_ext );
320                             cmd = NULL;
321                         }
322                         break;
323                     }
324
325                     case CMD_DEACTIVATE:
326                     {
327                         msg_Dbg( p_mgr, "Deactivating '%s'", p_ext->psz_title );
328                         if( lua_ExtensionDeactivate( p_mgr, p_ext ) < 0 )
329                         {
330                             msg_Warn( p_mgr, "Extension '%s' did not deactivate properly",
331                                       p_ext->psz_title );
332                         }
333                         p_ext->p_sys->b_exiting = true;
334                         RemoveActivated( p_mgr, p_ext );
335                         break;
336                     }
337
338                     case CMD_CLOSE:
339                     {
340                         lua_ExecuteFunction( p_mgr, p_ext, "close", LUA_END );
341                         break;
342                     }
343
344                     case CMD_CLICK:
345                     {
346                         extension_widget_t *p_widget = cmd->data[0];
347                         assert( p_widget );
348                         msg_Dbg( p_mgr, "Clicking '%s': '%s'",
349                                  p_ext->psz_name, p_widget->psz_text );
350                         if( !lua_ExtensionWidgetClick( p_mgr, p_ext, p_widget )
351                             < 0 )
352                         {
353                             msg_Warn( p_mgr, "Could not translate click" );
354                         }
355                         break;
356                     }
357
358                     case CMD_TRIGGERMENU:
359                     {
360                         int *pi_id = cmd->data[0];
361                         assert( pi_id );
362                         msg_Dbg( p_mgr, "Trigger menu %d of '%s'",
363                                  *pi_id, p_ext->psz_name );
364                         lua_ExtensionTriggerMenu( p_mgr, p_ext, *pi_id );
365                         break;
366                     }
367
368                     case CMD_SET_INPUT:
369                     {
370                         lua_ExecuteFunction( p_mgr, p_ext, "input_changed", LUA_END );
371                         break;
372                     }
373
374                     case CMD_UPDATE_META:
375                     {
376                         lua_ExecuteFunction( p_mgr, p_ext, "meta_changed", LUA_END );
377                         break;
378                     }
379
380                     case CMD_PLAYING_CHANGED:
381                     {
382                         lua_ExecuteFunction( p_mgr, p_ext, "playing_changed",
383                                 LUA_NUM, *((int *)cmd->data[0]), LUA_END );
384                         break;
385                     }
386
387                     default:
388                     {
389                         msg_Dbg( p_mgr, "Unknown command in extension command queue: %d",
390                                  cmd->i_command );
391                         break;
392                     }
393                 }
394                 UnlockExtension( p_ext );
395             }
396         }
397
398         vlc_mutex_lock( &p_ext->p_sys->command_lock );
399         if( p_ext->p_sys->command )
400         {
401             cmd = p_ext->p_sys->command;
402             p_ext->p_sys->command = cmd->next;
403             cmd->next = NULL; // This prevents FreeCommands from freeing next
404             FreeCommands( cmd );
405         }
406
407         if( !p_ext->p_sys->b_exiting && !p_ext->p_sys->command )
408         {
409             vlc_cond_wait( &p_ext->p_sys->wait, &p_ext->p_sys->command_lock );
410         }
411     }
412
413     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
414     msg_Dbg( p_mgr, "Extension thread end: '%s'", p_ext->psz_title );
415
416     // Note: At this point, the extension should be deactivated
417     return NULL;
418 }