]> git.sesse.net Git - vlc/blob - src/misc/variables.c
* ./include/variables.h, ./src/misc/variables.c: implemented variable
[vlc] / src / misc / variables.c
1 /*****************************************************************************
2  * variables.c: routines for object variables handling
3  *****************************************************************************
4  * Copyright (C) 2002 VideoLAN
5  * $Id: variables.c,v 1.7 2002/10/16 19:39:42 sam Exp $
6  *
7  * Authors: Samuel Hocevar <sam@zoy.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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <vlc/vlc.h>
28
29 #ifdef HAVE_STDLIB_H
30 #   include <stdlib.h>                                          /* realloc() */
31 #endif
32
33 /*****************************************************************************
34  * Private types
35  *****************************************************************************/
36 struct callback_entry_t
37 {
38     vlc_callback_t pf_callback;
39     void *         p_data;
40 };
41
42 /*****************************************************************************
43  * Local prototypes
44  *****************************************************************************/
45 static u32 HashString     ( const char * );
46 static int Insert         ( variable_t *, int, const char * );
47 static int InsertInner    ( variable_t *, int, u32 );
48 static int Lookup         ( variable_t *, int, const char * );
49 static int LookupInner    ( variable_t *, int, u32 );
50
51 /*****************************************************************************
52  * var_Create: initialize a vlc variable
53  *****************************************************************************
54  * We hash the given string and insert it into the sorted list. The insertion
55  * may require slow memory copies, but think about what we gain in the log(n)
56  * lookup phase when setting/getting the variable value!
57  *****************************************************************************/
58 int __var_Create( vlc_object_t *p_this, const char *psz_name, int i_type )
59 {
60     int i_new;
61     variable_t *p_var;
62
63     vlc_mutex_lock( &p_this->var_lock );
64
65     /* FIXME: if the variable already exists, we don't duplicate it. But we
66      * duplicate the lookups. It's not that serious, but if anyone finds some
67      * time to rework Insert() so that only one lookup has to be done, feel
68      * free to do so. */
69     i_new = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
70
71     if( i_new >= 0 )
72     {
73         /* If the types differ, variable creation failed. */
74         if( i_type != p_this->p_vars[i_new].i_type )
75         {
76             vlc_mutex_unlock( &p_this->var_lock );
77             return VLC_EBADVAR;
78         }
79
80         p_this->p_vars[i_new].i_usage++;
81         vlc_mutex_unlock( &p_this->var_lock );
82         return VLC_SUCCESS;
83     }
84
85     i_new = Insert( p_this->p_vars, p_this->i_vars, psz_name );
86
87     if( (p_this->i_vars & 15) == 15 )
88     {
89         p_this->p_vars = realloc( p_this->p_vars,
90                                   (p_this->i_vars+17) * sizeof(variable_t) );
91     }
92
93     memmove( p_this->p_vars + i_new + 1,
94              p_this->p_vars + i_new,
95              (p_this->i_vars - i_new) * sizeof(variable_t) );
96
97     p_this->i_vars++;
98
99     p_var = &p_this->p_vars[i_new];
100
101     p_var->i_hash = HashString( psz_name );
102     p_var->psz_name = strdup( psz_name );
103
104     p_var->i_type = i_type;
105     memset( &p_var->val, 0, sizeof(vlc_value_t) );
106
107     p_var->i_usage = 1;
108
109     p_var->i_entries = 0;
110     p_var->p_entries = NULL;
111
112     /* Always initialize the variable */
113     switch( i_type )
114     {
115         case VLC_VAR_BOOL:
116             p_var->val.b_bool = VLC_FALSE;
117             break;
118         case VLC_VAR_INTEGER:
119             p_var->val.i_int = 0;
120             break;
121         case VLC_VAR_STRING:
122         case VLC_VAR_MODULE:
123         case VLC_VAR_FILE:
124             p_var->val.psz_string = strdup( "" );
125             break;
126         case VLC_VAR_FLOAT:
127             p_var->val.f_float = 0.0;
128             break;
129         case VLC_VAR_TIME:
130             /* TODO */
131             break;
132         case VLC_VAR_ADDRESS:
133         case VLC_VAR_COMMAND:
134             p_var->val.p_address = NULL;
135             break;
136         case VLC_VAR_MUTEX:
137             p_var->val.p_address = malloc( sizeof(vlc_mutex_t) );
138             vlc_mutex_init( p_this, (vlc_mutex_t*)p_var->val.p_address );
139             break;
140     }
141
142     vlc_mutex_unlock( &p_this->var_lock );
143
144     return VLC_SUCCESS;
145 }
146
147 /*****************************************************************************
148  * var_Destroy: destroy a vlc variable
149  *****************************************************************************
150  * Look for the variable and destroy it if it is found. As in var_Create we
151  * do a call to memmove() but we have performance counterparts elsewhere.
152  *****************************************************************************/
153 int __var_Destroy( vlc_object_t *p_this, const char *psz_name )
154 {
155     int i_del;
156     variable_t *p_var;
157
158     vlc_mutex_lock( &p_this->var_lock );
159
160     i_del = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
161
162     if( i_del < 0 )
163     {
164         vlc_mutex_unlock( &p_this->var_lock );
165         return VLC_ENOVAR;
166     }
167
168     p_var = &p_this->p_vars[i_del];
169
170     if( p_var->i_usage > 1 )
171     {
172         p_var->i_usage--;
173         vlc_mutex_unlock( &p_this->var_lock );
174         return VLC_SUCCESS;
175     }
176
177     /* Free value if needed */
178     switch( p_var->i_type )
179     {
180         case VLC_VAR_STRING:
181         case VLC_VAR_MODULE:
182         case VLC_VAR_FILE:
183             free( p_var->val.psz_string );
184             break;
185
186         case VLC_VAR_MUTEX:
187             vlc_mutex_destroy( (vlc_mutex_t*)p_var->val.p_address );
188             free( p_var->val.p_address );
189             break;
190     }
191
192     /* Free callbacks if needed */
193     if( p_var->p_entries )
194     {
195         free( p_var->p_entries );
196     }
197
198     free( p_var->psz_name );
199
200     memmove( p_this->p_vars + i_del,
201              p_this->p_vars + i_del + 1,
202              (p_this->i_vars - i_del - 1) * sizeof(variable_t) );
203
204     if( (p_this->i_vars & 15) == 0 )
205     {
206         p_this->p_vars = realloc( p_this->p_vars,
207                           (p_this->i_vars) * sizeof( variable_t ) );
208     }
209
210     p_this->i_vars--;
211
212     vlc_mutex_unlock( &p_this->var_lock );
213
214     return VLC_SUCCESS;
215 }
216
217 /*****************************************************************************
218  * var_Type: request a variable's type, 0 if not found
219  *****************************************************************************
220  * 
221  *****************************************************************************/
222 int __var_Type( vlc_object_t *p_this, const char *psz_name )
223 {
224     int i_var, i_type;
225
226     vlc_mutex_lock( &p_this->var_lock );
227
228     i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
229
230     if( i_var < 0 )
231     {
232         vlc_mutex_unlock( &p_this->var_lock );
233         return 0;
234     }
235
236     i_type = p_this->p_vars[i_var].i_type;
237
238     vlc_mutex_unlock( &p_this->var_lock );
239
240     return i_type;
241 }
242
243 /*****************************************************************************
244  * var_Set: set a variable's value
245  *****************************************************************************
246  *
247  *****************************************************************************/
248 int __var_Set( vlc_object_t *p_this, const char *psz_name, vlc_value_t val )
249 {
250     int i_var;
251     variable_t *p_var;
252     vlc_value_t oldval;
253
254     vlc_mutex_lock( &p_this->var_lock );
255
256     i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
257
258     if( i_var < 0 )
259     {
260         vlc_mutex_unlock( &p_this->var_lock );
261         return VLC_ENOVAR;
262     }
263
264     p_var = &p_this->p_vars[i_var];
265
266     /* Duplicate data if needed */
267     switch( p_var->i_type )
268     {
269         case VLC_VAR_STRING:
270         case VLC_VAR_MODULE:
271         case VLC_VAR_FILE:
272             val.psz_string = strdup( val.psz_string );
273             break;
274     }
275
276     /* Backup needed stuff */
277     oldval = p_var->val;
278
279     /* Set the variable */
280     p_var->val = val;
281
282     /* Deal with callbacks */
283     if( p_var->i_entries )
284     {
285         int i;
286
287         for( i = p_var->i_entries ; i-- ; )
288         {
289             /* FIXME: oooh, see the deadlocks! see the race conditions! */
290             p_var->p_entries[i].pf_callback( p_this, p_var->psz_name,
291                                              oldval, val,
292                                              p_var->p_entries[i].p_data );
293         }
294     }
295
296     /* Free data if needed */
297     switch( p_var->i_type )
298     {
299         case VLC_VAR_STRING:
300         case VLC_VAR_MODULE:
301         case VLC_VAR_FILE:
302             free( oldval.psz_string );
303             break;
304     }
305
306     vlc_mutex_unlock( &p_this->var_lock );
307
308     return VLC_SUCCESS;
309 }
310
311 /*****************************************************************************
312  * var_Get: get a variable's value
313  *****************************************************************************
314  *
315  *****************************************************************************/
316 int __var_Get( vlc_object_t *p_this, const char *psz_name, vlc_value_t *p_val )
317 {
318     int i_var;
319     variable_t *p_var;
320
321     vlc_mutex_lock( &p_this->var_lock );
322
323     i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
324
325     if( i_var < 0 )
326     {
327         vlc_mutex_unlock( &p_this->var_lock );
328         return VLC_ENOVAR;
329     }
330
331     p_var = &p_this->p_vars[i_var];
332
333     /* Some variables trigger special behaviour. */
334     switch( p_var->i_type )
335     {
336         case VLC_VAR_COMMAND:
337             if( p_var->val.p_address )
338             {
339                 /* We need to save data before releasing the lock */
340                 int i_ret;
341                 int (*pf_command) (vlc_object_t *, char *, char *) =
342                                           p_var->val.p_address;
343                 char *psz_cmd = strdup( p_var->psz_name );
344                 char *psz_arg = strdup( p_val->psz_string );
345
346                 vlc_mutex_unlock( &p_this->var_lock );
347
348                 i_ret = pf_command( p_this, psz_cmd, psz_arg );
349                 free( psz_cmd );
350                 free( psz_arg );
351
352                 return i_ret;
353             }
354             break;
355     }
356
357     /* Really set the variable */
358     *p_val = p_var->val;
359
360     /* Duplicate value if needed */
361     switch( p_var->i_type )
362     {
363         case VLC_VAR_STRING:
364         case VLC_VAR_MODULE:
365         case VLC_VAR_FILE:
366             if( p_val->psz_string )
367             {
368                 p_val->psz_string = strdup( p_val->psz_string );
369             }
370             break;
371     }
372
373     vlc_mutex_unlock( &p_this->var_lock );
374
375     return VLC_SUCCESS;
376 }
377
378 /*****************************************************************************
379  * var_AddCallback: register a callback in a variable
380  *****************************************************************************
381  * We store a function pointer pf_callback that will be called upon variable
382  * modification. p_data is a generic pointer that will be passed as additional
383  * argument to the callback function.
384  *****************************************************************************/
385 int __var_AddCallback( vlc_object_t *p_this, const char *psz_name,
386                        vlc_callback_t pf_callback, void *p_data )
387 {
388     int i_var, i_entry;
389     variable_t *p_var;
390
391     vlc_mutex_lock( &p_this->var_lock );
392
393     i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
394     if( i_var < 0 )
395     {
396         vlc_mutex_unlock( &p_this->var_lock );
397         return VLC_ENOVAR;
398     }
399
400     p_var = &p_this->p_vars[i_var];
401
402     i_entry = p_var->i_entries++;
403
404     if( i_entry )
405     {
406         p_var->p_entries = realloc( p_var->p_entries,
407                                sizeof( callback_entry_t ) * p_var->i_entries );
408     }
409     else
410     {
411         p_var->p_entries = malloc( sizeof( callback_entry_t ) );
412     }
413
414     p_var->p_entries[ i_entry ].pf_callback = pf_callback;
415     p_var->p_entries[ i_entry ].p_data = p_data;
416         
417     vlc_mutex_unlock( &p_this->var_lock );
418
419     return VLC_SUCCESS;
420 }
421
422 /*****************************************************************************
423  * var_DelCallback: remove a callback from a variable
424  *****************************************************************************
425  * pf_callback and p_data have to be given again, because different objects
426  * might have registered the same callback function.
427  *****************************************************************************/
428 int __var_DelCallback( vlc_object_t *p_this, const char *psz_name,
429                        vlc_callback_t pf_callback, void *p_data )
430 {
431     int i_var, i_entry;
432     variable_t *p_var;
433
434     vlc_mutex_lock( &p_this->var_lock );
435
436     i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
437     if( i_var < 0 )
438     {
439         vlc_mutex_unlock( &p_this->var_lock );
440         return VLC_ENOVAR;
441     }
442
443     p_var = &p_this->p_vars[i_var];
444
445     for( i_entry = p_var->i_entries ; i_entry-- ; )
446     {
447         if( p_var->p_entries[i_entry].pf_callback == pf_callback
448             || p_var->p_entries[i_entry].p_data == p_data )
449         {
450             break;
451         }
452     }
453
454     if( i_entry < 0 )
455     {
456         vlc_mutex_unlock( &p_this->var_lock );
457         return VLC_EGENERIC;
458     }
459
460     p_var->i_entries--;
461
462     memmove( p_var->p_entries + i_entry,
463              p_var->p_entries + i_entry + 1,
464              sizeof( callback_entry_t ) * ( p_var->i_entries - i_entry ) );
465
466     if( p_var->i_entries )
467     {
468         p_var->p_entries = realloc( p_var->p_entries,
469                        sizeof( callback_entry_t ) * ( p_var->i_entries ) );
470     }
471     else
472     {
473         free( p_var->p_entries );
474         p_var->p_entries = NULL;
475     }
476
477     vlc_mutex_unlock( &p_this->var_lock );
478
479     return VLC_SUCCESS;
480 }
481
482 /* Following functions are local */
483
484 /*****************************************************************************
485  * HashString: our cool hash function
486  *****************************************************************************
487  * This function is not intended to be crypto-secure, we only want it to be
488  * fast and not suck too much. This one is pretty fast and did 0 collisions
489  * in wenglish's dictionary.
490  *****************************************************************************/
491 static u32 HashString( const char *psz_string )
492 {
493     u32 i_hash = 0;
494
495     while( *psz_string )
496     {
497         i_hash += *psz_string++;
498         i_hash += i_hash << 10;
499         i_hash ^= i_hash >> 8;
500     }
501
502     return i_hash;
503 }
504
505 /*****************************************************************************
506  * Insert: find an empty slot to insert a new variable
507  *****************************************************************************
508  * We use a recursive inner function indexed on the hash. This function does
509  * nothing in the rare cases where a collision may occur, see Lookup()
510  * to see how we handle them.
511  * XXX: does this really need to be written recursively?
512  *****************************************************************************/
513 static int Insert( variable_t *p_vars, int i_count, const char *psz_name )
514 {
515     if( i_count == 0 )
516     {
517         return 0;
518     }
519
520     return InsertInner( p_vars, i_count, HashString( psz_name ) );
521 }
522
523 static int InsertInner( variable_t *p_vars, int i_count, u32 i_hash )
524 {
525     int i_middle;
526
527     if( i_hash <= p_vars[0].i_hash )
528     {
529         return 0;
530     }
531
532     if( i_hash >= p_vars[i_count - 1].i_hash )
533     {
534         return i_count;
535     }
536
537     i_middle = i_count / 2;
538
539     /* We know that 0 < i_middle */
540     if( i_hash < p_vars[i_middle].i_hash )
541     {
542         return InsertInner( p_vars, i_middle, i_hash );
543     }
544
545     /* We know that i_middle + 1 < i_count */
546     if( i_hash > p_vars[i_middle + 1].i_hash )
547     {
548         return i_middle + 1 + InsertInner( p_vars + i_middle + 1,
549                                            i_count - i_middle - 1,
550                                            i_hash );
551     }
552
553     return i_middle + 1;
554 }
555
556 /*****************************************************************************
557  * Lookup: find an existing variable given its name
558  *****************************************************************************
559  * We use a recursive inner function indexed on the hash. Care is taken of
560  * possible hash collisions.
561  * XXX: does this really need to be written recursively?
562  *****************************************************************************/
563 static int Lookup( variable_t *p_vars, int i_count, const char *psz_name )
564 {
565     u32 i_hash;
566     int i, i_pos;
567
568     if( i_count == 0 )
569     {
570         return -1;
571     }
572
573     i_hash = HashString( psz_name );
574
575     i_pos = LookupInner( p_vars, i_count, i_hash );
576
577     /* Hash not found */
578     if( i_hash != p_vars[i_pos].i_hash )
579     {
580         return -1;
581     }
582
583     /* Hash found, entry found */
584     if( !strcmp( psz_name, p_vars[i_pos].psz_name ) )
585     {
586         return i_pos;
587     }
588
589     /* Hash collision! This should be very rare, but we cannot guarantee
590      * it will never happen. Just do an exhaustive search amongst all
591      * entries with the same hash. */
592     for( i = i_pos - 1 ; i > 0 && i_hash == p_vars[i].i_hash ; i-- )
593     {
594         if( !strcmp( psz_name, p_vars[i].psz_name ) )
595         {
596             return i;
597         }
598     }
599
600     for( i = i_pos + 1 ; i < i_count && i_hash == p_vars[i].i_hash ; i++ )
601     {
602         if( !strcmp( psz_name, p_vars[i].psz_name ) )
603         {
604             return i;
605         }
606     }
607
608     /* Hash found, but entry not found */
609     return -1;
610 }
611
612 static int LookupInner( variable_t *p_vars, int i_count, u32 i_hash )
613 {
614     int i_middle;
615
616     if( i_hash <= p_vars[0].i_hash )
617     {
618         return 0;
619     }
620
621     if( i_hash >= p_vars[i_count-1].i_hash )
622     {
623         return i_count - 1;
624     }
625
626     i_middle = i_count / 2;
627
628     /* We know that 0 < i_middle */
629     if( i_hash < p_vars[i_middle].i_hash )
630     {
631         return LookupInner( p_vars, i_middle, i_hash );
632     }
633
634     /* We know that i_middle + 1 < i_count */
635     if( i_hash > p_vars[i_middle].i_hash )
636     {
637         return i_middle + LookupInner( p_vars + i_middle,
638                                        i_count - i_middle,
639                                        i_hash );
640     }
641
642     return i_middle;
643 }
644