variables.c 36.1 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * variables.c: routines for object variables handling
 *****************************************************************************
 * Copyright (C) 2002 VideoLAN
5
 * $Id: variables.c,v 1.27 2003/07/22 15:59:06 gbazin Exp $
6
7
8
9
10
11
12
 *
 * Authors: Samuel Hocevar <sam@zoy.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.
13
 *
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <vlc/vlc.h>

#ifdef HAVE_STDLIB_H
#   include <stdlib.h>                                          /* realloc() */
#endif

33
34
35
36
37
38
39
40
41
/*****************************************************************************
 * Private types
 *****************************************************************************/
struct callback_entry_t
{
    vlc_callback_t pf_callback;
    void *         p_data;
};

42
43
44
/*****************************************************************************
 * Local comparison functions, returns 0 if v == w, < 0 if v < w, > 0 if v > w
 *****************************************************************************/
45
static int CmpBool( vlc_value_t v, vlc_value_t w ) { return v.b_bool ? w.b_bool ? 0 : 1 : w.b_bool ? -1 : 0; }
46
static int CmpInt( vlc_value_t v, vlc_value_t w ) { return v.i_int == w.i_int ? 0 : v.i_int > w.i_int ? 1 : -1; }
47
48
49
50
51
52
53
static int CmpTime( vlc_value_t v, vlc_value_t w )
{
    mtime_t v_time,w_time;
    v_time = ( (mtime_t)v.time.i_high << 32 ) + v.time.i_low;
    w_time = ( (mtime_t)w.time.i_high << 32 ) + w.time.i_low;
    return v_time == w_time ? 0 : v_time > w_time ? 1 : -1;
}
54
55
56
57
58
59
60
static int CmpString( vlc_value_t v, vlc_value_t w ) { return strcmp( v.psz_string, w.psz_string ); }
static int CmpFloat( vlc_value_t v, vlc_value_t w ) { return v.f_float == w.f_float ? 0 : v.f_float > w.f_float ? 1 : -1; }
static int CmpAddress( vlc_value_t v, vlc_value_t w ) { return v.p_address == w.p_address ? 0 : v.p_address > w.p_address ? 1 : -1; }

/*****************************************************************************
 * Local duplication functions, and local deallocation functions
 *****************************************************************************/
61
static void DupDummy( vlc_value_t *p_val ) { (void)p_val; /* unused */ }
62
63
static void DupString( vlc_value_t *p_val ) { p_val->psz_string = strdup( p_val->psz_string ); }

gbazin's avatar
   
gbazin committed
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
static void DupList( vlc_value_t *p_val )
{
    int i;
    vlc_list_t *p_list = malloc( sizeof(vlc_list_t) );

    if( p_val->p_list->i_count )
    {
        p_list->i_count = p_val->p_list->i_count;
        p_list->p_values = malloc( p_list->i_count * sizeof(vlc_value_t) );
        p_list->pi_types = malloc( p_list->i_count * sizeof(int) );
    }

    for( i = 0; i < p_list->i_count; i++ )
    {
        p_list->p_values[i] = p_val->p_list->p_values[i];
        switch( p_val->p_list->pi_types[i] & VLC_VAR_TYPE )
        {
        case VLC_VAR_STRING:
            
            DupString( &p_list->p_values[i] );
            break;
        default:
            break;
        }
    }

    p_val->p_list = p_list;
}

93
static void FreeDummy( vlc_value_t *p_val ) { (void)p_val; /* unused */ }
94
static void FreeString( vlc_value_t *p_val ) { free( p_val->psz_string ); }
gbazin's avatar
   
gbazin committed
95
static void FreeMutex( vlc_value_t *p_val ) { vlc_mutex_destroy( (vlc_mutex_t*)p_val->p_address ); free( p_val->p_address ); }
96

gbazin's avatar
   
gbazin committed
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
static void FreeList( vlc_value_t *p_val )
{
    int i;
    for( i = 0; i < p_val->p_list->i_count; i++ )
    {
        switch( p_val->p_list->pi_types[i] & VLC_VAR_TYPE )
        {
        case VLC_VAR_STRING:
            FreeString( &p_val->p_list->p_values[i] );
            break;
        case VLC_VAR_MUTEX:
            FreeMutex( &p_val->p_list->p_values[i] );
            break;
        default:
            break;
        }
    }

    if( p_val->p_list->i_count )
    {
        free( p_val->p_list->p_values );
        free( p_val->p_list->pi_types );
    }
    free( p_val->p_list );
}

123
124
125
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
126
127
128
129
130
131
static int      GetUnused   ( vlc_object_t *, const char * );
static uint32_t HashString  ( const char * );
static int      Insert      ( variable_t *, int, const char * );
static int      InsertInner ( variable_t *, int, uint32_t );
static int      Lookup      ( variable_t *, int, const char * );
static int      LookupInner ( variable_t *, int, uint32_t );
132

133
static void     CheckValue  ( variable_t *, vlc_value_t * );
134

gbazin's avatar
   
gbazin committed
135
136
137
static int      InheritValue( vlc_object_t *, const char *, vlc_value_t *,
                              int );

138
139
140
141
142
143
144
/*****************************************************************************
 * var_Create: initialize a vlc variable
 *****************************************************************************
 * We hash the given string and insert it into the sorted list. The insertion
 * may require slow memory copies, but think about what we gain in the log(n)
 * lookup phase when setting/getting the variable value!
 *****************************************************************************/
145
int __var_Create( vlc_object_t *p_this, const char *psz_name, int i_type )
146
147
{
    int i_new;
148
    variable_t *p_var;
gbazin's avatar
   
gbazin committed
149
    static vlc_list_t dummy_null_list = {0, NULL, NULL};
150
151
152

    vlc_mutex_lock( &p_this->var_lock );

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    /* FIXME: if the variable already exists, we don't duplicate it. But we
     * duplicate the lookups. It's not that serious, but if anyone finds some
     * time to rework Insert() so that only one lookup has to be done, feel
     * free to do so. */
    i_new = Lookup( p_this->p_vars, p_this->i_vars, psz_name );

    if( i_new >= 0 )
    {
        /* If the types differ, variable creation failed. */
        if( i_type != p_this->p_vars[i_new].i_type )
        {
            vlc_mutex_unlock( &p_this->var_lock );
            return VLC_EBADVAR;
        }

        p_this->p_vars[i_new].i_usage++;
        vlc_mutex_unlock( &p_this->var_lock );
        return VLC_SUCCESS;
    }

    i_new = Insert( p_this->p_vars, p_this->i_vars, psz_name );

175
176
177
178
179
180
181
182
183
184
    if( (p_this->i_vars & 15) == 15 )
    {
        p_this->p_vars = realloc( p_this->p_vars,
                                  (p_this->i_vars+17) * sizeof(variable_t) );
    }

    memmove( p_this->p_vars + i_new + 1,
             p_this->p_vars + i_new,
             (p_this->i_vars - i_new) * sizeof(variable_t) );

185
186
    p_this->i_vars++;

187
188
189
190
    p_var = &p_this->p_vars[i_new];

    p_var->i_hash = HashString( psz_name );
    p_var->psz_name = strdup( psz_name );
gbazin's avatar
   
gbazin committed
191
    p_var->psz_text = NULL;
192

193
194
    p_var->i_type = i_type;
    memset( &p_var->val, 0, sizeof(vlc_value_t) );
195

196
197
198
    p_var->pf_dup = DupDummy;
    p_var->pf_free = FreeDummy;

199
    p_var->i_usage = 1;
200

201
    p_var->i_default = -1;
gbazin's avatar
   
gbazin committed
202
203
    p_var->choices.i_count = 0;
    p_var->choices.p_values = NULL;
gbazin's avatar
   
gbazin committed
204
205
    p_var->choices_text.i_count = 0;
    p_var->choices_text.p_values = NULL;
206

207
    p_var->b_incallback = VLC_FALSE;
208
209
210
    p_var->i_entries = 0;
    p_var->p_entries = NULL;

211
212
213
214
    /* Always initialize the variable, even if it is a list variable; this
     * will lead to errors if the variable is not initialized, but it will
     * not cause crashes in the variable handling. */
    switch( i_type & VLC_VAR_TYPE )
215
    {
216
        case VLC_VAR_BOOL:
217
            p_var->pf_cmp = CmpBool;
218
219
220
            p_var->val.b_bool = VLC_FALSE;
            break;
        case VLC_VAR_INTEGER:
221
            p_var->pf_cmp = CmpInt;
222
223
224
225
226
            p_var->val.i_int = 0;
            break;
        case VLC_VAR_STRING:
        case VLC_VAR_MODULE:
        case VLC_VAR_FILE:
gbazin's avatar
   
gbazin committed
227
228
        case VLC_VAR_DIRECTORY:
        case VLC_VAR_VARIABLE:
229
230
231
232
            p_var->pf_cmp = CmpString;
            p_var->pf_dup = DupString;
            p_var->pf_free = FreeString;
            p_var->val.psz_string = "";
233
234
            break;
        case VLC_VAR_FLOAT:
235
            p_var->pf_cmp = CmpFloat;
236
237
238
            p_var->val.f_float = 0.0;
            break;
        case VLC_VAR_TIME:
239
240
241
            p_var->pf_cmp = CmpTime;
            p_var->val.time.i_low = 0;
            p_var->val.time.i_high = 0;
242
243
            break;
        case VLC_VAR_ADDRESS:
244
            p_var->pf_cmp = CmpAddress;
245
246
            p_var->val.p_address = NULL;
            break;
247
        case VLC_VAR_MUTEX:
248
            p_var->pf_cmp = CmpAddress;
gbazin's avatar
   
gbazin committed
249
            p_var->pf_free = FreeMutex;
250
251
252
            p_var->val.p_address = malloc( sizeof(vlc_mutex_t) );
            vlc_mutex_init( p_this, (vlc_mutex_t*)p_var->val.p_address );
            break;
gbazin's avatar
   
gbazin committed
253
254
255
256
257
258
        case VLC_VAR_LIST:
            p_var->pf_cmp = CmpAddress;
            p_var->pf_dup = DupList;
            p_var->pf_free = FreeList;
            p_var->val.p_list = &dummy_null_list;
            break;
259
    }
260

261
262
263
    /* Duplicate the default data we stored. */
    p_var->pf_dup( &p_var->val );

264
    vlc_mutex_unlock( &p_this->var_lock );
265
266

    return VLC_SUCCESS;
267
268
269
270
271
272
273
274
}

/*****************************************************************************
 * var_Destroy: destroy a vlc variable
 *****************************************************************************
 * Look for the variable and destroy it if it is found. As in var_Create we
 * do a call to memmove() but we have performance counterparts elsewhere.
 *****************************************************************************/
275
int __var_Destroy( vlc_object_t *p_this, const char *psz_name )
276
{
277
    int i_var, i;
278
    variable_t *p_var;
279
280
281

    vlc_mutex_lock( &p_this->var_lock );

282
283
    i_var = GetUnused( p_this, psz_name );
    if( i_var < 0 )
284
285
    {
        vlc_mutex_unlock( &p_this->var_lock );
286
        return i_var;
287
288
    }

289
    p_var = &p_this->p_vars[i_var];
290
291
292
293
294
295
296
297

    if( p_var->i_usage > 1 )
    {
        p_var->i_usage--;
        vlc_mutex_unlock( &p_this->var_lock );
        return VLC_SUCCESS;
    }

298
    /* Free value if needed */
299
    p_var->pf_free( &p_var->val );
300

301
    /* Free choice list if needed */
gbazin's avatar
   
gbazin committed
302
    if( p_var->choices.i_count )
303
    {
gbazin's avatar
   
gbazin committed
304
        for( i = 0 ; i < p_var->choices.i_count ; i++ )
305
        {
gbazin's avatar
   
gbazin committed
306
            p_var->pf_free( &p_var->choices.p_values[i] );
307
308
            if( p_var->choices_text.p_values[i].psz_string )
                free( p_var->choices_text.p_values[i].psz_string );
309
        }
gbazin's avatar
   
gbazin committed
310
        free( p_var->choices.p_values );
311
        free( p_var->choices_text.p_values );
312
313
    }

314
315
316
317
318
319
    /* Free callbacks if needed */
    if( p_var->p_entries )
    {
        free( p_var->p_entries );
    }

320
    free( p_var->psz_name );
gbazin's avatar
   
gbazin committed
321
    if( p_var->psz_text ) free( p_var->psz_text );
322

323
324
325
    memmove( p_this->p_vars + i_var,
             p_this->p_vars + i_var + 1,
             (p_this->i_vars - i_var - 1) * sizeof(variable_t) );
326
327
328
329
330
331
332
333
334
335

    if( (p_this->i_vars & 15) == 0 )
    {
        p_this->p_vars = realloc( p_this->p_vars,
                          (p_this->i_vars) * sizeof( variable_t ) );
    }

    p_this->i_vars--;

    vlc_mutex_unlock( &p_this->var_lock );
336
337
338
339

    return VLC_SUCCESS;
}

340
341
342
/*****************************************************************************
 * var_Change: perform an action on a variable
 *****************************************************************************
343
 *
344
345
 *****************************************************************************/
int __var_Change( vlc_object_t *p_this, const char *psz_name,
gbazin's avatar
   
gbazin committed
346
                  int i_action, vlc_value_t *p_val, vlc_value_t *p_val2 )
347
{
348
    int i_var, i;
349
    variable_t *p_var;
gbazin's avatar
   
gbazin committed
350
    vlc_value_t oldval;
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366

    vlc_mutex_lock( &p_this->var_lock );

    i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );

    if( i_var < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
        return VLC_ENOVAR;
    }

    p_var = &p_this->p_vars[i_var];

    switch( i_action )
    {
        case VLC_VAR_SETMIN:
367
368
369
370
371
            if( p_var->i_type & VLC_VAR_HASMIN )
            {
                p_var->pf_free( &p_var->min );
            }
            p_var->i_type |= VLC_VAR_HASMIN;
372
            p_var->min = *p_val;
373
            p_var->pf_dup( &p_var->min );
374
375
376
            CheckValue( p_var, &p_var->val );
            break;
        case VLC_VAR_SETMAX:
377
378
379
380
381
            if( p_var->i_type & VLC_VAR_HASMAX )
            {
                p_var->pf_free( &p_var->max );
            }
            p_var->i_type |= VLC_VAR_HASMAX;
382
            p_var->max = *p_val;
383
            p_var->pf_dup( &p_var->max );
384
385
386
            CheckValue( p_var, &p_var->val );
            break;
        case VLC_VAR_SETSTEP:
387
388
389
390
391
            if( p_var->i_type & VLC_VAR_HASSTEP )
            {
                p_var->pf_free( &p_var->step );
            }
            p_var->i_type |= VLC_VAR_HASSTEP;
392
            p_var->step = *p_val;
393
394
395
396
397
            p_var->pf_dup( &p_var->step );
            CheckValue( p_var, &p_var->val );
            break;
        case VLC_VAR_ADDCHOICE:
            /* FIXME: the list is sorted, dude. Use something cleverer. */
gbazin's avatar
   
gbazin committed
398
            for( i = p_var->choices.i_count ; i-- ; )
399
            {
gbazin's avatar
   
gbazin committed
400
                if( p_var->pf_cmp( p_var->choices.p_values[i], *p_val ) < 0 )
401
402
403
404
405
406
407
408
409
410
411
412
413
                {
                    break;
                }
            }

            /* The new place is i+1 */
            i++;

            if( p_var->i_default >= i )
            {
                p_var->i_default++;
            }

gbazin's avatar
   
gbazin committed
414
415
            INSERT_ELEM( p_var->choices.p_values, p_var->choices.i_count,
                         i, *p_val );
gbazin's avatar
   
gbazin committed
416
            INSERT_ELEM( p_var->choices_text.p_values,
gbazin's avatar
   
gbazin committed
417
                         p_var->choices_text.i_count, i, *p_val );
gbazin's avatar
   
gbazin committed
418
            p_var->pf_dup( &p_var->choices.p_values[i] );
gbazin's avatar
   
gbazin committed
419
420
421
            p_var->choices_text.p_values[i].psz_string =
                ( p_val2 && p_val2->psz_string ) ?
                strdup( p_val2->psz_string ) : NULL;
422

423
424
425
426
            CheckValue( p_var, &p_var->val );
            break;
        case VLC_VAR_DELCHOICE:
            /* FIXME: the list is sorted, dude. Use something cleverer. */
gbazin's avatar
   
gbazin committed
427
            for( i = 0 ; i < p_var->choices.i_count ; i++ )
428
            {
gbazin's avatar
   
gbazin committed
429
                if( p_var->pf_cmp( p_var->choices.p_values[i], *p_val ) == 0 )
430
431
432
433
434
                {
                    break;
                }
            }

gbazin's avatar
   
gbazin committed
435
            if( i == p_var->choices.i_count )
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
            {
                /* Not found */
                vlc_mutex_unlock( &p_this->var_lock );
                return VLC_EGENERIC;
            }

            if( p_var->i_default > i )
            {
                p_var->i_default--;
            }
            else if( p_var->i_default == i )
            {
                p_var->i_default = -1;
            }

gbazin's avatar
   
gbazin committed
451
            p_var->pf_free( &p_var->choices.p_values[i] );
gbazin's avatar
   
gbazin committed
452
453
            if( p_var->choices_text.p_values[i].psz_string )
                free( p_var->choices_text.p_values[i].psz_string );
gbazin's avatar
   
gbazin committed
454
            REMOVE_ELEM( p_var->choices.p_values, p_var->choices.i_count, i );
gbazin's avatar
   
gbazin committed
455
456
            REMOVE_ELEM( p_var->choices_text.p_values,
                         p_var->choices_text.i_count, i );
457
458
459

            CheckValue( p_var, &p_var->val );
            break;
gbazin's avatar
   
gbazin committed
460
461
462
        case VLC_VAR_CHOICESCOUNT:
            p_val->i_int = p_var->choices.i_count;
            break;
gbazin's avatar
   
gbazin committed
463
464
465
466
467
468
469
470
471
472
473
474
        case VLC_VAR_CLEARCHOICES:
            for( i = 0 ; i < p_var->choices.i_count ; i++ )
            {
                p_var->pf_free( &p_var->choices.p_values[i] );
            }
            if( p_var->choices.i_count )
                free( p_var->choices.p_values );

            p_var->choices.i_count = 0;
            p_var->choices.p_values = NULL;
            p_var->i_default = -1;
            break;
475
476
        case VLC_VAR_SETDEFAULT:
            /* FIXME: the list is sorted, dude. Use something cleverer. */
gbazin's avatar
   
gbazin committed
477
            for( i = 0 ; i < p_var->choices.i_count ; i++ )
478
            {
gbazin's avatar
   
gbazin committed
479
                if( p_var->pf_cmp( p_var->choices.p_values[i], *p_val ) == 0 )
480
481
482
483
484
                {
                    break;
                }
            }

gbazin's avatar
   
gbazin committed
485
            if( i == p_var->choices.i_count )
486
487
488
489
490
491
            {
                /* Not found */
                break;
            }

            p_var->i_default = i;
492
493
            CheckValue( p_var, &p_var->val );
            break;
gbazin's avatar
   
gbazin committed
494
495
496
497
498
499
500
501
502
503
504
505
        case VLC_VAR_SETVALUE:
            /* Duplicate data if needed */
            p_var->pf_dup( p_val );
            /* Backup needed stuff */
            oldval = p_var->val;
            /* Check boundaries and list */
            CheckValue( p_var, p_val );
            /* Set the variable */
            p_var->val = *p_val;
            /* Free data if needed */
            p_var->pf_free( &oldval );
            break;
gbazin's avatar
   
gbazin committed
506
        case VLC_VAR_GETCHOICES:
507
        case VLC_VAR_GETLIST:
gbazin's avatar
   
gbazin committed
508
            p_val->p_list = malloc( sizeof(vlc_list_t) );
gbazin's avatar
   
gbazin committed
509
            if( p_val2 ) p_val2->p_list = malloc( sizeof(vlc_list_t) );
gbazin's avatar
   
gbazin committed
510
            if( p_var->choices.i_count )
gbazin's avatar
   
gbazin committed
511
            {
gbazin's avatar
   
gbazin committed
512
513
                p_val->p_list->p_values = malloc( p_var->choices.i_count
                                                  * sizeof(vlc_value_t) );
gbazin's avatar
   
gbazin committed
514
515
516
517
518
519
520
521
522
523
                p_val->p_list->pi_types = malloc( p_var->choices.i_count
                                                  * sizeof(int) );
                if( p_val2 )
                {
                    p_val2->p_list->p_values =
                        malloc( p_var->choices.i_count * sizeof(vlc_value_t) );
                    p_val2->p_list->pi_types =
                        malloc( p_var->choices.i_count * sizeof(int) );
                }
            }
gbazin's avatar
   
gbazin committed
524
            p_val->p_list->i_count = p_var->choices.i_count;
gbazin's avatar
   
gbazin committed
525
            if( p_val2 ) p_val2->p_list->i_count = p_var->choices.i_count;
gbazin's avatar
   
gbazin committed
526
            for( i = 0 ; i < p_var->choices.i_count ; i++ )
527
            {
gbazin's avatar
   
gbazin committed
528
                p_val->p_list->p_values[i] = p_var->choices.p_values[i];
gbazin's avatar
   
gbazin committed
529
                p_val->p_list->pi_types[i] = p_var->i_type;
gbazin's avatar
   
gbazin committed
530
                p_var->pf_dup( &p_val->p_list->p_values[i] );
gbazin's avatar
   
gbazin committed
531
532
533
534
535
536
537
                if( p_val2 )
                {
                    p_val2->p_list->p_values[i].psz_string =
                        p_var->choices_text.p_values[i].psz_string ?
                    strdup(p_var->choices_text.p_values[i].psz_string) : NULL;
                    p_val2->p_list->pi_types[i] = VLC_VAR_STRING;
                }
538
539
540
            }
            break;
        case VLC_VAR_FREELIST:
gbazin's avatar
   
gbazin committed
541
542
543
544
545
546
547
548
549
550
            FreeList( p_val );
            break;
        case VLC_VAR_SETTEXT:
            if( p_var->psz_text ) free( p_var->psz_text );
            if( p_val && p_val->psz_string )
                p_var->psz_text = strdup( p_val->psz_string );
            break;
        case VLC_VAR_GETTEXT:
            p_val->psz_string = NULL;
            if( p_var->psz_text )
551
            {
gbazin's avatar
   
gbazin committed
552
                p_val->psz_string = strdup( p_var->psz_text );
553
            }
554
            break;
gbazin's avatar
   
gbazin committed
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
        case VLC_VAR_INHERITVALUE:
            {
                vlc_value_t val;

                if( InheritValue( p_this, psz_name, &val, p_var->i_type )
                    == VLC_SUCCESS );
                {
                    /* Duplicate already done */

                    /* Backup needed stuff */
                    oldval = p_var->val;
                    /* Check boundaries and list */
                    CheckValue( p_var, &val );
                    /* Set the variable */
                    p_var->val = val;
                    /* Free data if needed */
                    p_var->pf_free( &oldval );
                }
gbazin's avatar
   
gbazin committed
573
574
575
576
577
578

                if( p_val )
                {
                    *p_val = p_var->val;
                    p_var->pf_dup( p_val );
                }
gbazin's avatar
   
gbazin committed
579
580
            }
            break;
581
582
583
584
585
586
587
588
589
590

        default:
            break;
    }

    vlc_mutex_unlock( &p_this->var_lock );

    return VLC_SUCCESS;
}

591
/*****************************************************************************
592
 * var_Type: request a variable's type
593
 *****************************************************************************
gbazin's avatar
   
gbazin committed
594
 * This function returns the variable type if it exists, or 0 if the
595
 * variable could not be found.
596
597
598
599
600
601
602
603
604
605
606
607
 *****************************************************************************/
int __var_Type( vlc_object_t *p_this, const char *psz_name )
{
    int i_var, i_type;

    vlc_mutex_lock( &p_this->var_lock );

    i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );

    if( i_var < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
gbazin's avatar
   
gbazin committed
608
        return 0;
609
610
611
612
613
614
615
    }

    i_type = p_this->p_vars[i_var].i_type;

    vlc_mutex_unlock( &p_this->var_lock );

    return i_type;
616
617
618
619
620
621
622
623
624
625
}

/*****************************************************************************
 * var_Set: set a variable's value
 *****************************************************************************
 *
 *****************************************************************************/
int __var_Set( vlc_object_t *p_this, const char *psz_name, vlc_value_t val )
{
    int i_var;
626
627
    variable_t *p_var;
    vlc_value_t oldval;
628
629
630

    vlc_mutex_lock( &p_this->var_lock );

631
    i_var = GetUnused( p_this, psz_name );
632
633
634
    if( i_var < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
635
        return i_var;
636
637
    }

638
639
640
    p_var = &p_this->p_vars[i_var];

    /* Duplicate data if needed */
641
    p_var->pf_dup( &val );
642

643
644
    /* Backup needed stuff */
    oldval = p_var->val;
645

646
    /* Check boundaries and list */
647
648
    CheckValue( p_var, &val );

649
650
651
    /* Set the variable */
    p_var->val = val;

652
653
    /* Deal with callbacks. Tell we're in a callback, release the lock,
     * call stored functions, retake the lock. */
654
655
    if( p_var->i_entries )
    {
656
657
658
        int i_var;
        int i_entries = p_var->i_entries;
        callback_entry_t *p_entries = p_var->p_entries;
659

660
661
662
663
664
665
666
667
668
669
670
671
672
673
        p_var->b_incallback = VLC_TRUE;
        vlc_mutex_unlock( &p_this->var_lock );

        /* The real calls */
        for( ; i_entries-- ; )
        {
            p_entries[i_entries].pf_callback( p_this, psz_name, oldval, val,
                                              p_entries[i_entries].p_data );
        }

        vlc_mutex_lock( &p_this->var_lock );

        i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
        if( i_var < 0 )
674
        {
675
            msg_Err( p_this, "variable %s has disappeared", psz_name );
676
677
            vlc_mutex_unlock( &p_this->var_lock );
            return VLC_ENOVAR;
678
        }
679
680
681

        p_var = &p_this->p_vars[i_var];
        p_var->b_incallback = VLC_FALSE;
682
683
684
    }

    /* Free data if needed */
685
    p_var->pf_free( &oldval );
686
687
688
689
690
691
692
693
694
695
696

    vlc_mutex_unlock( &p_this->var_lock );

    return VLC_SUCCESS;
}

/*****************************************************************************
 * var_Get: get a variable's value
 *****************************************************************************
 *
 *****************************************************************************/
697
int __var_Get( vlc_object_t *p_this, const char *psz_name, vlc_value_t *p_val )
698
699
{
    int i_var;
700
    variable_t *p_var;
701
702
703
704
705
706
707
708

    vlc_mutex_lock( &p_this->var_lock );

    i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );

    if( i_var < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
709
        return VLC_ENOVAR;
710
711
    }

712
    p_var = &p_this->p_vars[i_var];
713

714
    /* Really get the variable */
715
    *p_val = p_var->val;
716
717

    /* Duplicate value if needed */
718
    p_var->pf_dup( p_val );
719
720
721
722
723
724

    vlc_mutex_unlock( &p_this->var_lock );

    return VLC_SUCCESS;
}

725
726
727
728
729
730
731
732
733
734
/*****************************************************************************
 * var_AddCallback: register a callback in a variable
 *****************************************************************************
 * We store a function pointer pf_callback that will be called upon variable
 * modification. p_data is a generic pointer that will be passed as additional
 * argument to the callback function.
 *****************************************************************************/
int __var_AddCallback( vlc_object_t *p_this, const char *psz_name,
                       vlc_callback_t pf_callback, void *p_data )
{
735
    int i_var;
736
    variable_t *p_var;
737
    callback_entry_t entry;
738

739
740
    entry.pf_callback = pf_callback;
    entry.p_data = p_data;
741

742
743
    vlc_mutex_lock( &p_this->var_lock );

744
    i_var = GetUnused( p_this, psz_name );
745
746
747
    if( i_var < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
748
        return i_var;
749
750
751
752
    }

    p_var = &p_this->p_vars[i_var];

753
754
755
756
    INSERT_ELEM( p_var->p_entries,
                 p_var->i_entries,
                 p_var->i_entries,
                 entry );
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771

    vlc_mutex_unlock( &p_this->var_lock );

    return VLC_SUCCESS;
}

/*****************************************************************************
 * var_DelCallback: remove a callback from a variable
 *****************************************************************************
 * pf_callback and p_data have to be given again, because different objects
 * might have registered the same callback function.
 *****************************************************************************/
int __var_DelCallback( vlc_object_t *p_this, const char *psz_name,
                       vlc_callback_t pf_callback, void *p_data )
{
772
    int i_entry, i_var;
773
774
775
776
    variable_t *p_var;

    vlc_mutex_lock( &p_this->var_lock );

777
    i_var = GetUnused( p_this, psz_name );
778
779
780
    if( i_var < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
781
        return i_var;
782
783
784
785
786
787
788
    }

    p_var = &p_this->p_vars[i_var];

    for( i_entry = p_var->i_entries ; i_entry-- ; )
    {
        if( p_var->p_entries[i_entry].pf_callback == pf_callback
gbazin's avatar
   
gbazin committed
789
            && p_var->p_entries[i_entry].p_data == p_data )
790
791
792
793
794
795
796
797
798
799
800
        {
            break;
        }
    }

    if( i_entry < 0 )
    {
        vlc_mutex_unlock( &p_this->var_lock );
        return VLC_EGENERIC;
    }

801
    REMOVE_ELEM( p_var->p_entries, p_var->i_entries, i_entry );
802
803
804
805
806
807

    vlc_mutex_unlock( &p_this->var_lock );

    return VLC_SUCCESS;
}

808
809
/* Following functions are local */

810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
/*****************************************************************************
 * GetUnused: find an unused variable from its name
 *****************************************************************************
 * We do i_tries tries before giving up, just in case the variable is being
 * modified and called from a callback.
 *****************************************************************************/
static int GetUnused( vlc_object_t *p_this, const char *psz_name )
{
    int i_var, i_tries = 0;

    while( VLC_TRUE )
    {
        i_var = Lookup( p_this->p_vars, p_this->i_vars, psz_name );
        if( i_var < 0 )
        {
            return VLC_ENOVAR;
        }

        if( ! p_this->p_vars[i_var].b_incallback )
        {
            return i_var;
        }

        if( i_tries++ > 100 )
        {
            msg_Err( p_this, "caught in a callback deadlock?" );
            return VLC_ETIMEOUT;
        }

        vlc_mutex_unlock( &p_this->var_lock );
        msleep( THREAD_SLEEP );
        vlc_mutex_lock( &p_this->var_lock );
    }
}

845
846
847
848
849
850
851
/*****************************************************************************
 * HashString: our cool hash function
 *****************************************************************************
 * This function is not intended to be crypto-secure, we only want it to be
 * fast and not suck too much. This one is pretty fast and did 0 collisions
 * in wenglish's dictionary.
 *****************************************************************************/
852
static uint32_t HashString( const char *psz_string )
853
{
854
    uint32_t i_hash = 0;
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883

    while( *psz_string )
    {
        i_hash += *psz_string++;
        i_hash += i_hash << 10;
        i_hash ^= i_hash >> 8;
    }

    return i_hash;
}

/*****************************************************************************
 * Insert: find an empty slot to insert a new variable
 *****************************************************************************
 * We use a recursive inner function indexed on the hash. This function does
 * nothing in the rare cases where a collision may occur, see Lookup()
 * to see how we handle them.
 * XXX: does this really need to be written recursively?
 *****************************************************************************/
static int Insert( variable_t *p_vars, int i_count, const char *psz_name )
{
    if( i_count == 0 )
    {
        return 0;
    }

    return InsertInner( p_vars, i_count, HashString( psz_name ) );
}

884
static int InsertInner( variable_t *p_vars, int i_count, uint32_t i_hash )
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
{
    int i_middle;

    if( i_hash <= p_vars[0].i_hash )
    {
        return 0;
    }

    if( i_hash >= p_vars[i_count - 1].i_hash )
    {
        return i_count;
    }

    i_middle = i_count / 2;

    /* We know that 0 < i_middle */
    if( i_hash < p_vars[i_middle].i_hash )
    {
        return InsertInner( p_vars, i_middle, i_hash );
    }

    /* We know that i_middle + 1 < i_count */
    if( i_hash > p_vars[i_middle + 1].i_hash )
    {
        return i_middle + 1 + InsertInner( p_vars + i_middle + 1,
                                           i_count - i_middle - 1,
                                           i_hash );
    }

    return i_middle + 1;
}

/*****************************************************************************
 * Lookup: find an existing variable given its name
 *****************************************************************************
 * We use a recursive inner function indexed on the hash. Care is taken of
 * possible hash collisions.
 * XXX: does this really need to be written recursively?
 *****************************************************************************/
static int Lookup( variable_t *p_vars, int i_count, const char *psz_name )
{
926
    uint32_t i_hash;
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
    int i, i_pos;

    if( i_count == 0 )
    {
        return -1;
    }

    i_hash = HashString( psz_name );

    i_pos = LookupInner( p_vars, i_count, i_hash );

    /* Hash not found */
    if( i_hash != p_vars[i_pos].i_hash )
    {
        return -1;
    }

    /* Hash found, entry found */
    if( !strcmp( psz_name, p_vars[i_pos].psz_name ) )
    {
        return i_pos;
    }

    /* Hash collision! This should be very rare, but we cannot guarantee
     * it will never happen. Just do an exhaustive search amongst all
     * entries with the same hash. */
    for( i = i_pos - 1 ; i > 0 && i_hash == p_vars[i].i_hash ; i-- )
    {
        if( !strcmp( psz_name, p_vars[i].psz_name ) )
        {
            return i;
        }
    }

    for( i = i_pos + 1 ; i < i_count && i_hash == p_vars[i].i_hash ; i++ )
    {
        if( !strcmp( psz_name, p_vars[i].psz_name ) )
        {
            return i;
        }
    }

    /* Hash found, but entry not found */
    return -1;
}

973
static int LookupInner( variable_t *p_vars, int i_count, uint32_t i_hash )
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
{
    int i_middle;

    if( i_hash <= p_vars[0].i_hash )
    {
        return 0;
    }

    if( i_hash >= p_vars[i_count-1].i_hash )
    {
        return i_count - 1;
    }

    i_middle = i_count / 2;

    /* We know that 0 < i_middle */
    if( i_hash < p_vars[i_middle].i_hash )
    {
        return LookupInner( p_vars, i_middle, i_hash );
    }

    /* We know that i_middle + 1 < i_count */
    if( i_hash > p_vars[i_middle].i_hash )
    {
        return i_middle + LookupInner( p_vars + i_middle,
                                       i_count - i_middle,
                                       i_hash );
    }

    return i_middle;
}

1006
/*****************************************************************************
1007
 * CheckValue: check that a value is valid wrt. a variable
1008
1009
 *****************************************************************************
 * This function checks p_val's value against p_var's limitations such as
1010
 * minimal and maximal value, step, in-list position, and modifies p_val if
1011
1012
1013
1014
 * necessary.
 *****************************************************************************/
static void CheckValue ( variable_t *p_var, vlc_value_t *p_val )
{
1015
    /* Check that our variable is in the list */
gbazin's avatar
   
gbazin committed
1016
    if( p_var->i_type & VLC_VAR_HASCHOICE && p_var->choices.i_count )
1017
1018
1019
1020
    {
        int i;

        /* FIXME: the list is sorted, dude. Use something cleverer. */
gbazin's avatar
   
gbazin committed
1021
        for( i = p_var->choices.i_count ; i-- ; )
1022
        {
gbazin's avatar
   
gbazin committed
1023
            if( p_var->pf_cmp( *p_val, p_var->choices.p_values[i] ) == 0 )
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
            {
                break;
            }
        }

        /* If not found, change it to anything vaguely valid */
        if( i < 0 )
        {
            /* Free the old variable, get the new one, dup it */
            p_var->pf_free( p_val );
gbazin's avatar
   
gbazin committed
1034
1035
            *p_val = p_var->choices.p_values[p_var->i_default >= 0
                                          ? p_var->i_default : 0 ];
1036
1037
1038
1039
1040
1041
            p_var->pf_dup( p_val );
        }
    }

    /* Check that our variable is within the bounds */
    switch( p_var->i_type & VLC_VAR_TYPE )
1042
1043
    {
        case VLC_VAR_INTEGER:
1044
1045
            if( p_var->i_type & VLC_VAR_HASSTEP && p_var->step.i_int
                 && (p_val->i_int % p_var->step.i_int) )
1046
1047
1048
1049
            {
                p_val->i_int = (p_val->i_int + (p_var->step.i_int / 2))
                               / p_var->step.i_int * p_var->step.i_int;
            }
1050
1051
            if( p_var->i_type & VLC_VAR_HASMIN
                 && p_val->i_int < p_var->min.i_int )
1052
1053
1054
            {
                p_val->i_int = p_var->min.i_int;
            }
1055
1056
            if( p_var->i_type & VLC_VAR_HASMAX
                 && p_val->i_int > p_var->max.i_int )
1057
1058
1059
1060
1061
            {
                p_val->i_int = p_var->max.i_int;
            }
            break;
        case VLC_VAR_FLOAT:
1062
            if( p_var->i_type & VLC_VAR_HASSTEP && p_var->step.f_float )
1063
1064
1065
1066
1067
1068
1069
1070
            {
                float f_round = p_var->step.f_float * (float)(int)( 0.5 +
                                        p_val->f_float / p_var->step.f_float );
                if( p_val->f_float != f_round )
                {
                    p_val->f_float = f_round;
                }
            }
1071
1072
            if( p_var->i_type & VLC_VAR_HASMIN
                 && p_val->f_float < p_var->min.f_float )
1073
1074
1075
            {
                p_val->f_float = p_var->min.f_float;
            }
1076
1077
            if( p_var->i_type & VLC_VAR_HASMAX
                 && p_val->f_float > p_var->max.f_float )
1078
1079
1080
1081
1082
            {
                p_val->f_float = p_var->max.f_float;
            }
            break;
        case VLC_VAR_TIME:
1083
            /* FIXME: TODO */
1084
1085
1086
            break;
    }
}
gbazin's avatar
   
gbazin committed
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154

/*****************************************************************************
 * InheritValue: try to inherit the value of this variable from the same one
 *               in our closest parent.
 *****************************************************************************/
static int InheritValue( vlc_object_t *p_this, const char *psz_name,
                         vlc_value_t *p_val, int i_type )
{
    int i_var;
    variable_t *p_var;

    /* No need to take the structure lock,
     * we are only looking for our parents */

    if( !p_this->p_parent )
    {
        switch( i_type & VLC_VAR_TYPE )
        {
        case VLC_VAR_STRING:
            p_val->psz_string = config_GetPsz( p_this, psz_name );
            if( !p_val->psz_string ) p_val->psz_string = strdup("");
            break;
        case VLC_VAR_FLOAT:
            p_val->f_float = config_GetFloat( p_this, psz_name );
            break;
        case VLC_VAR_INTEGER:
            p_val->i_int = config_GetInt( p_this, psz_name );
            break;
        case VLC_VAR_BOOL:
            p_val->b_bool = config_GetInt( p_this, psz_name );
            break;
        default:
            return VLC_ENOOBJ;
            break;
        }

        return VLC_SUCCESS;
    }

    /* Look for the variable */
    vlc_mutex_lock( &p_this->p_parent->var_lock );

    i_var = Lookup( p_this->p_parent->p_vars, p_this->p_parent->i_vars,
                    psz_name );

    if( i_var >= 0 )
    {
        /* We found it! */

        p_var = &p_this->p_parent->p_vars[i_var];

        /* Really get the variable */
        *p_val = p_var->val;

        /* Duplicate value if needed */
        p_var->pf_dup( p_val );

        vlc_mutex_unlock( &p_this->p_parent->var_lock );

        return VLC_SUCCESS;
    }

    vlc_mutex_unlock( &p_this->p_parent->var_lock );

    /* We're still not there */

    return InheritValue( p_this->p_parent, psz_name, p_val, i_type );
}