input.c 15.7 KB
Newer Older
1
2
/*****************************************************************************
 * input.c: input thread
Michel Kaempf's avatar
Michel Kaempf committed
3
4
 * Read an MPEG2 stream, demultiplex and parse it before sending it to
 * decoders.
5
6
 *****************************************************************************
 * Copyright (C) 1998, 1999, 2000 VideoLAN
Christophe Massiot's avatar
Christophe Massiot committed
7
 * $Id: input.c,v 1.84 2001/02/19 19:08:59 massiot Exp $
8
 *
9
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
10
11
12
13
14
 *
 * 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.
15
 * 
16
17
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
20
 *
21
22
23
 * 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.
24
 *****************************************************************************/
Michel Kaempf's avatar
Michel Kaempf committed
25

26
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
27
 * Preamble
28
 *****************************************************************************/
29
30
#include "defs.h"

31
32
33
34
35
36
37
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
Michel Kaempf's avatar
Michel Kaempf committed
38

39
40
41
42
#ifdef STATS
#   include <sys/times.h>
#endif

Michel Kaempf's avatar
Michel Kaempf committed
43
#include "config.h"
44
45
#include "common.h"
#include "threads.h"
Michel Kaempf's avatar
Michel Kaempf committed
46
#include "mtime.h"
Sam Hocevar's avatar
   
Sam Hocevar committed
47
#include "modules.h"
48

49
#include "intf_msg.h"
Sam Hocevar's avatar
   
Sam Hocevar committed
50
#include "intf_plst.h"
51

52
53
54
#include "stream_control.h"
#include "input_ext-intf.h"
#include "input_ext-dec.h"
Michel Lespinasse's avatar
Yop,    
Michel Lespinasse committed
55

56
#include "input.h"
Sam Hocevar's avatar
   
Sam Hocevar committed
57
58
59
#include "interface.h"

#include "main.h"
Michel Kaempf's avatar
Michel Kaempf committed
60

61
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
62
 * Local prototypes
63
 *****************************************************************************/
Sam Hocevar's avatar
   
Sam Hocevar committed
64
65
66
67
68
static void RunThread       ( input_thread_t *p_input );
static  int InitThread      ( input_thread_t *p_input );
static void ErrorThread     ( input_thread_t *p_input );
static void DestroyThread   ( input_thread_t *p_input );
static void EndThread       ( input_thread_t *p_input );
Michel Kaempf's avatar
Michel Kaempf committed
69

70
/*****************************************************************************
71
 * input_CreateThread: creates a new input thread
72
 *****************************************************************************
73
74
75
76
 * This function creates a new input, and returns a pointer
 * to its description. On error, it returns NULL.
 * If pi_status is NULL, then the function will block until the thread is ready.
 * If not, it will be updated using one of the THREAD_* constants.
77
 *****************************************************************************/
Sam Hocevar's avatar
   
Sam Hocevar committed
78
input_thread_t *input_CreateThread ( playlist_item_t *p_item, int *pi_status )
Michel Kaempf's avatar
Michel Kaempf committed
79
{
80
81
82
    input_thread_t *    p_input;                        /* thread descriptor */
    int                 i_status;                           /* thread status */

83
84
85
    /* Allocate descriptor */
    p_input = (input_thread_t *)malloc( sizeof(input_thread_t) );
    if( p_input == NULL )
Michel Kaempf's avatar
Michel Kaempf committed
86
    {
Sam Hocevar's avatar
   
Sam Hocevar committed
87
88
        intf_ErrMsg( "input error: can't allocate input thread (%s)",
                     strerror(errno) );
Michel Kaempf's avatar
Michel Kaempf committed
89
90
        return( NULL );
    }
91
92
93
94

    /* Initialize thread properties */
    p_input->b_die              = 0;
    p_input->b_error            = 0;
Sam Hocevar's avatar
   
Sam Hocevar committed
95
96
97
98
99
    p_input->b_eof              = 0;

    /* Set target */
    p_input->p_source           = p_item->psz_name;

100
    /* I have never understood that stuff --Meuuh */
101
102
    p_input->pi_status          = (pi_status != NULL) ? pi_status : &i_status;
    *p_input->pi_status         = THREAD_CREATE;
Michel Kaempf's avatar
Michel Kaempf committed
103

104
    /* Initialize stream description */
105
106
    p_input->stream.i_es_number = 0;
    p_input->stream.i_selected_es_number = 0;
107
    p_input->stream.i_pgrm_number = 0;
108
    p_input->stream.i_new_status = p_input->stream.i_new_rate = 0;
109
    p_input->stream.i_seek = NO_SEEK;
Christophe Massiot's avatar
Christophe Massiot committed
110
    p_input->stream.i_mux_rate = 0;
Michel Kaempf's avatar
Michel Kaempf committed
111

112
113
114
115
116
    /* Initialize stream control properties. */
    p_input->stream.control.i_status = PLAYING_S;
    p_input->stream.control.i_rate = DEFAULT_RATE;
    p_input->stream.control.b_mute = 0;
    p_input->stream.control.b_bw = 0;
Michel Kaempf's avatar
Michel Kaempf committed
117

Sam Hocevar's avatar
   
Sam Hocevar committed
118
119
    /* Initialize default settings for spawned decoders */
    p_input->p_default_aout = p_main->p_aout;
Sam Hocevar's avatar
   
Sam Hocevar committed
120
    p_input->p_default_vout = p_main->p_vout;
Sam Hocevar's avatar
   
Sam Hocevar committed
121

Michel Kaempf's avatar
Michel Kaempf committed
122
    /* Create thread and set locks. */
123
    vlc_mutex_init( &p_input->stream.stream_lock );
124
    vlc_cond_init( &p_input->stream.stream_wait );
125
126
127
    vlc_mutex_init( &p_input->stream.control.control_lock );
    if( vlc_thread_create( &p_input->thread_id, "input", (void *) RunThread,
                           (void *) p_input ) )
Michel Kaempf's avatar
Michel Kaempf committed
128
    {
Sam Hocevar's avatar
   
Sam Hocevar committed
129
130
        intf_ErrMsg( "input error: can't create input thread (%s)",
                     strerror(errno) );
Michel Kaempf's avatar
Michel Kaempf committed
131
132
133
        free( p_input );
        return( NULL );
    }
134

135
136
137
138
    /* If status is NULL, wait until the thread is created */
    if( pi_status == NULL )
    {
        do
139
        {
140
            msleep( THREAD_SLEEP );
141
        } while( (i_status != THREAD_READY) && (i_status != THREAD_ERROR)
Sam Hocevar's avatar
   
Sam Hocevar committed
142
                && (i_status != THREAD_FATAL) );
143
144
        if( i_status != THREAD_READY )
        {
145
146
            return( NULL );
        }
147
    }
Michel Kaempf's avatar
Michel Kaempf committed
148
149
150
    return( p_input );
}

151
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
152
 * input_DestroyThread: mark an input thread as zombie
153
 *****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
154
 * This function should not return until the thread is effectively cancelled.
155
 *****************************************************************************/
156
void input_DestroyThread( input_thread_t *p_input, int *pi_status )
Michel Kaempf's avatar
Michel Kaempf committed
157
{
158
    int         i_status;                                   /* thread status */
159
160
161

    /* Set status */
    p_input->pi_status = (pi_status != NULL) ? pi_status : &i_status;
162
163
    *p_input->pi_status = THREAD_DESTROY;

164
165
    /* Request thread destruction */
    p_input->b_die = 1;
Michel Kaempf's avatar
Michel Kaempf committed
166

167
168
169
170
171
    /* Make the thread exit of an eventual vlc_cond_wait() */
    vlc_mutex_lock( &p_input->stream.stream_lock );
    vlc_cond_signal( &p_input->stream.stream_wait );
    vlc_mutex_unlock( &p_input->stream.stream_lock );

172
173
174
175
176
177
    /* If status is NULL, wait until thread has been destroyed */
    if( pi_status == NULL )
    {
        do
        {
            msleep( THREAD_SLEEP );
178
179
        } while ( (i_status != THREAD_OVER) && (i_status != THREAD_ERROR)
                  && (i_status != THREAD_FATAL) );
180
    }
Michel Kaempf's avatar
Michel Kaempf committed
181
182
}

183
/*****************************************************************************
184
 * RunThread: main thread loop
185
 *****************************************************************************
186
 * Thread in charge of processing the network packets and demultiplexing.
187
 *****************************************************************************/
188
static void RunThread( input_thread_t *p_input )
Michel Kaempf's avatar
Michel Kaempf committed
189
{
190
191
    data_packet_t *         pp_packets[INPUT_READ_ONCE];
    int                     i_error, i;
Michel Kaempf's avatar
Michel Kaempf committed
192

Sam Hocevar's avatar
   
Sam Hocevar committed
193
194
195
196
197
198
199
200
201
202
    if( InitThread( p_input ) )
    {

        /* If we failed, wait before we are killed, and exit */
        *p_input->pi_status = THREAD_ERROR;
        p_input->b_error = 1;
        ErrorThread( p_input );
        DestroyThread( p_input );
        return;
    }
Michel Kaempf's avatar
Michel Kaempf committed
203

Sam Hocevar's avatar
   
Sam Hocevar committed
204
    while( !p_input->b_die && !p_input->b_error && !p_input->b_eof )
205
206
    {

Sam Hocevar's avatar
   
Sam Hocevar committed
207
#ifdef STATS
Sam Hocevar's avatar
   
Sam Hocevar committed
208
        p_input->c_loops++;
Sam Hocevar's avatar
   
Sam Hocevar committed
209
210
#endif

211
        vlc_mutex_lock( &p_input->stream.stream_lock );
212
        if( p_input->stream.i_seek != NO_SEEK )
Sam Hocevar's avatar
   
Sam Hocevar committed
213
        {
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
            if( p_input->stream.b_seekable && p_input->pf_seek != NULL )
            {
                p_input->pf_seek( p_input, p_input->stream.i_seek );

                for( i = 0; i < p_input->stream.i_pgrm_number; i++ )
                {
                    pgrm_descriptor_t * p_pgrm
                                            = p_input->stream.pp_programs[i];
                    /* Escape all decoders for the stream discontinuity they
                     * will encounter. */
                    input_EscapeDiscontinuity( p_input, p_pgrm );

                    /* Reinitialize synchro. */
                    p_pgrm->i_synchro_state = SYNCHRO_REINIT;
                }
            }
230
            p_input->stream.i_seek = NO_SEEK;
Sam Hocevar's avatar
   
Sam Hocevar committed
231
        }
232
        vlc_mutex_unlock( &p_input->stream.stream_lock );
Sam Hocevar's avatar
   
Sam Hocevar committed
233
234

        i_error = p_input->pf_read( p_input, pp_packets );
Sam Hocevar's avatar
   
Sam Hocevar committed
235

Sam Hocevar's avatar
   
Sam Hocevar committed
236
237
238
239
240
        /* Demultiplex read packets. */
        for( i = 0; i < INPUT_READ_ONCE && pp_packets[i] != NULL; i++ )
        {
            p_input->pf_demux( p_input, pp_packets[i] );
        }
Sam Hocevar's avatar
   
Sam Hocevar committed
241

Sam Hocevar's avatar
   
Sam Hocevar committed
242
243
244
        if( i_error )
        {
            if( i_error == 1 )
Sam Hocevar's avatar
Sam Hocevar committed
245
            {
Sam Hocevar's avatar
   
Sam Hocevar committed
246
247
248
249
                /* End of file - we do not set b_die because only the
                 * interface is allowed to do so. */
                intf_WarnMsg( 1, "End of file reached" );
                p_input->b_eof = 1;
Sam Hocevar's avatar
   
Sam Hocevar committed
250
            }
Sam Hocevar's avatar
   
Sam Hocevar committed
251
            else
Sam Hocevar's avatar
   
Sam Hocevar committed
252
            {
Sam Hocevar's avatar
   
Sam Hocevar committed
253
                p_input->b_error = 1;
Sam Hocevar's avatar
Sam Hocevar committed
254
            }
255
256
257
        }
    }

Sam Hocevar's avatar
   
Sam Hocevar committed
258
    if( p_input->b_error || p_input->b_eof )
259
260
261
    {
        ErrorThread( p_input );
    }
262

263
    EndThread( p_input );
Sam Hocevar's avatar
   
Sam Hocevar committed
264
265
266

    DestroyThread( p_input );

267
    intf_DbgMsg("Thread end");
268
269
}

270
/*****************************************************************************
Sam Hocevar's avatar
   
Sam Hocevar committed
271
 * InitThread: init the input Thread
272
 *****************************************************************************/
Sam Hocevar's avatar
   
Sam Hocevar committed
273
static int InitThread( input_thread_t * p_input )
Michel Kaempf's avatar
Michel Kaempf committed
274
275
276
{

#ifdef STATS
277
278
279
280
281
282
    /* Initialize statistics */
    p_input->c_loops                    = 0;
    p_input->c_bytes                    = 0;
    p_input->c_payload_bytes            = 0;
    p_input->c_packets_read             = 0;
    p_input->c_packets_trashed          = 0;
Michel Kaempf's avatar
Michel Kaempf committed
283
#endif
Sam Hocevar's avatar
Sam Hocevar committed
284

Sam Hocevar's avatar
   
Sam Hocevar committed
285
    p_input->p_input_module = module_Need( p_main->p_bank,
Sam Hocevar's avatar
   
Sam Hocevar committed
286
287
                                           MODULE_CAPABILITY_INPUT,
                                           (probedata_t *)p_input );
Sam Hocevar's avatar
   
Sam Hocevar committed
288
289

    if( p_input->p_input_module == NULL )
290
    {
Sam Hocevar's avatar
   
Sam Hocevar committed
291
        intf_ErrMsg( "input error: no suitable input module" );
Sam Hocevar's avatar
   
Sam Hocevar committed
292
293
        module_Unneed( p_main->p_bank, p_input->p_input_module );
        return( -1 );
Michel Kaempf's avatar
Michel Kaempf committed
294
    }
295

Sam Hocevar's avatar
   
Sam Hocevar committed
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#define f p_input->p_input_module->p_functions->input.functions.input
    p_input->pf_init          = f.pf_init;
    p_input->pf_open          = f.pf_open;
    p_input->pf_close         = f.pf_close;
    p_input->pf_end           = f.pf_end;
    p_input->pf_read          = f.pf_read;
    p_input->pf_demux         = f.pf_demux;
    p_input->pf_new_packet    = f.pf_new_packet;
    p_input->pf_new_pes       = f.pf_new_pes;
    p_input->pf_delete_packet = f.pf_delete_packet;
    p_input->pf_delete_pes    = f.pf_delete_pes;
    p_input->pf_rewind        = f.pf_rewind;
    p_input->pf_seek          = f.pf_seek;
#undef f

    p_input->pf_open( p_input );
312

Sam Hocevar's avatar
   
Sam Hocevar committed
313
    if( p_input->b_error )
314
    {
Sam Hocevar's avatar
   
Sam Hocevar committed
315
316
        /* We barfed -- exit nicely */
        p_input->pf_close( p_input );
Sam Hocevar's avatar
   
Sam Hocevar committed
317
        module_Unneed( p_main->p_bank, p_input->p_input_module );
Sam Hocevar's avatar
   
Sam Hocevar committed
318
        return( -1 );
319
    }
Sam Hocevar's avatar
   
Sam Hocevar committed
320
321

    p_input->pf_init( p_input );
Sam Hocevar's avatar
   
Sam Hocevar committed
322

Sam Hocevar's avatar
   
Sam Hocevar committed
323
    *p_input->pi_status = THREAD_READY;
Sam Hocevar's avatar
   
Sam Hocevar committed
324
325

    return( 0 );
Michel Kaempf's avatar
Michel Kaempf committed
326
327
}

328
/*****************************************************************************
329
 * ErrorThread: RunThread() error loop
330
 *****************************************************************************
331
 * This function is called when an error occured during thread main's loop.
332
 *****************************************************************************/
333
static void ErrorThread( input_thread_t *p_input )
Michel Kaempf's avatar
Michel Kaempf committed
334
{
335
    while( !p_input->b_die )
Michel Kaempf's avatar
Michel Kaempf committed
336
    {
337
338
        /* Sleep a while */
        msleep( INPUT_IDLE_SLEEP );
Michel Kaempf's avatar
Michel Kaempf committed
339
340
341
    }
}

342
/*****************************************************************************
343
 * EndThread: end the input thread
344
 *****************************************************************************/
345
static void EndThread( input_thread_t * p_input )
346
{
347
    int *       pi_status;                                  /* thread status */
348

349
350
351
    /* Store status */
    pi_status = p_input->pi_status;
    *pi_status = THREAD_END;
Sam Hocevar's avatar
Sam Hocevar committed
352

Sam Hocevar's avatar
   
Sam Hocevar committed
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
#ifdef STATS
    {
        struct tms cpu_usage;
        times( &cpu_usage );

        intf_Msg("input stats: cpu usage (user: %d, system: %d)",
                 cpu_usage.tms_utime, cpu_usage.tms_stime);
    }
#endif

    /* Free all ES and destroy all decoder threads */
    input_EndStream( p_input );

    /* Free demultiplexer's data */
    p_input->pf_end( p_input );

Sam Hocevar's avatar
   
Sam Hocevar committed
369
370
371
    /* Close stream */
    p_input->pf_close( p_input );

Sam Hocevar's avatar
   
Sam Hocevar committed
372
    /* Release modules */
Sam Hocevar's avatar
   
Sam Hocevar committed
373
    module_Unneed( p_main->p_bank, p_input->p_input_module );
Sam Hocevar's avatar
   
Sam Hocevar committed
374

Sam Hocevar's avatar
   
Sam Hocevar committed
375
376
377
378
379
380
381
382
383
384
385
}

/*****************************************************************************
 * DestroyThread: destroy the input thread
 *****************************************************************************/
static void DestroyThread( input_thread_t * p_input )
{
    int *       pi_status;                                  /* thread status */

    /* Store status */
    pi_status = p_input->pi_status;
Sam Hocevar's avatar
   
Sam Hocevar committed
386

Henri Fallon's avatar
   
Henri Fallon committed
387
388
    /* Destroy Mutex locks */
    vlc_mutex_destroy( &p_input->stream.control.control_lock );
Henri Fallon's avatar
   
Henri Fallon committed
389
    vlc_mutex_destroy( &p_input->stream.stream_lock );
Henri Fallon's avatar
   
Henri Fallon committed
390
    
391
    /* Free input structure */
392
    free( p_input );
393

394
395
    /* Update status */
    *pi_status = THREAD_OVER;
Sam Hocevar's avatar
Sam Hocevar committed
396
}
397

Sam Hocevar's avatar
Sam Hocevar committed
398
/*****************************************************************************
Sam Hocevar's avatar
   
Sam Hocevar committed
399
 * input_FileOpen : open a file descriptor
Sam Hocevar's avatar
Sam Hocevar committed
400
 *****************************************************************************/
Sam Hocevar's avatar
   
Sam Hocevar committed
401
void input_FileOpen( input_thread_t * p_input )
Michel Kaempf's avatar
Michel Kaempf committed
402
{
403
    struct stat         stat_info;
Sam Hocevar's avatar
   
Sam Hocevar committed
404
405
406
    int                 i_stat;

    char *psz_name = p_input->p_source;
Michel Kaempf's avatar
Michel Kaempf committed
407

Sam Hocevar's avatar
   
Sam Hocevar committed
408
409
410
    /* FIXME: this code ought to be in the plugin so that code can
     * be shared with the *_Probe function */
    if( ( i_stat = stat( psz_name, &stat_info ) ) == (-1) )
Sam Hocevar's avatar
   
Sam Hocevar committed
411
    {
Sam Hocevar's avatar
   
Sam Hocevar committed
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
        int i_size = strlen( psz_name );

        if( ( i_size > 4 )
            && !strncasecmp( psz_name, "dvd:", 4 ) )
        {
            /* get rid of the 'dvd:' stuff and try again */
            psz_name += 4;
            i_stat = stat( psz_name, &stat_info );
	}
	else if( ( i_size > 5 )
                 && !strncasecmp( psz_name, "file:", 5 ) )
        {
            /* get rid of the 'file:' stuff and try again */
            psz_name += 5;
            i_stat = stat( psz_name, &stat_info );
	}

	if( i_stat == (-1) )
        {
            intf_ErrMsg( "input error: cannot stat() file `%s' (%s)",
                         psz_name, strerror(errno));
            p_input->b_error = 1;
            return;
        }
Sam Hocevar's avatar
   
Sam Hocevar committed
436
437
438
439
440
441
442
443
444
445
446
447
448
    }

    vlc_mutex_lock( &p_input->stream.stream_lock );

    /* If we are here we can control the pace... */
    p_input->stream.b_pace_control = 1;

    if( S_ISREG(stat_info.st_mode) || S_ISCHR(stat_info.st_mode)
         || S_ISBLK(stat_info.st_mode) )
    {
        p_input->stream.b_seekable = 1;
        p_input->stream.i_size = stat_info.st_size;
    }
Jean-Marc Dressler's avatar
   
Jean-Marc Dressler committed
449
450
451
452
453
    else if( S_ISFIFO(stat_info.st_mode)
#ifndef SYS_BEOS
             || S_ISSOCK(stat_info.st_mode)
#endif
             )
Sam Hocevar's avatar
Sam Hocevar committed
454
    {
455
456
        p_input->stream.b_seekable = 0;
        p_input->stream.i_size = 0;
Benoit Steiner's avatar
   
Benoit Steiner committed
457
458
459
    }
    else
    {
460
        vlc_mutex_unlock( &p_input->stream.stream_lock );
Sam Hocevar's avatar
   
Sam Hocevar committed
461
        intf_ErrMsg( "input error: unknown file type for `%s'",
Sam Hocevar's avatar
   
Sam Hocevar committed
462
                     psz_name );
Sam Hocevar's avatar
   
Sam Hocevar committed
463
464
465
        p_input->b_error = 1;
        return;
    }
466

Sam Hocevar's avatar
   
Sam Hocevar committed
467
468
469
    p_input->stream.i_tell = 0;
    vlc_mutex_unlock( &p_input->stream.stream_lock );

Sam Hocevar's avatar
   
Sam Hocevar committed
470
471
    intf_Msg( "input: opening %s", p_input->p_source );
    if( (p_input->i_handle = open( psz_name,
Sam Hocevar's avatar
   
Sam Hocevar committed
472
473
                                   /*O_NONBLOCK | O_LARGEFILE*/0 )) == (-1) )
    {
Sam Hocevar's avatar
   
Sam Hocevar committed
474
        intf_ErrMsg( "input error: cannot open file (%s)", strerror(errno) );
Sam Hocevar's avatar
   
Sam Hocevar committed
475
476
        p_input->b_error = 1;
        return;
Michel Kaempf's avatar
Michel Kaempf committed
477
478
479
    }

}
Stéphane Borel's avatar
Stéphane Borel committed
480
481

/*****************************************************************************
Sam Hocevar's avatar
   
Sam Hocevar committed
482
 * input_FileClose : close a file descriptor
Stéphane Borel's avatar
Stéphane Borel committed
483
 *****************************************************************************/
Sam Hocevar's avatar
   
Sam Hocevar committed
484
void input_FileClose( input_thread_t * p_input )
Stéphane Borel's avatar
Stéphane Borel committed
485
{
Sam Hocevar's avatar
   
Sam Hocevar committed
486
    close( p_input->i_handle );
Stéphane Borel's avatar
Stéphane Borel committed
487

Sam Hocevar's avatar
   
Sam Hocevar committed
488
    return;
Stéphane Borel's avatar
Stéphane Borel committed
489
}
Sam Hocevar's avatar
   
Sam Hocevar committed
490