1 /*****************************************************************************
2 * item.c: input_item management
3 *****************************************************************************
4 * Copyright (C) 1998-2004 VLC authors and VideoLAN
5 * $Id: 8eb5e84df46dc030e73086e758130e7b6635f859 $
6 *
7 * Authors: Clément Stenac <zorglub@videolan.org>
8 *
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 #include <assert.h>
28 #include <time.h>
29 #include <limits.h>
30 #include <ctype.h>
31
32 #include <vlc_common.h>
33 #include <vlc_url.h>
34 #include <vlc_interface.h>
35 #include <vlc_charset.h>
36 #include <vlc_strings.h>
37
38 #include "item.h"
39 #include "info.h"
40 #include "input_internal.h"
41
42 struct input_item_opaque
43 {
44 struct input_item_opaque *next;
45 void *value;
46 char name[1];
47 };
48
49 static int GuessType( const input_item_t *p_item, bool *p_net );
50
input_item_SetErrorWhenReading(input_item_t * p_i,bool b_error)51 void input_item_SetErrorWhenReading( input_item_t *p_i, bool b_error )
52 {
53 bool b_changed;
54
55 vlc_mutex_lock( &p_i->lock );
56
57 b_changed = p_i->b_error_when_reading != b_error;
58 p_i->b_error_when_reading = b_error;
59
60 vlc_mutex_unlock( &p_i->lock );
61
62 if( b_changed )
63 {
64 vlc_event_send( &p_i->event_manager, &(vlc_event_t) {
65 .type = vlc_InputItemErrorWhenReadingChanged,
66 .u.input_item_error_when_reading_changed.new_value = b_error } );
67 }
68 }
input_item_SignalPreparseEnded(input_item_t * p_i,int status)69 void input_item_SignalPreparseEnded( input_item_t *p_i, int status )
70 {
71 vlc_event_send( &p_i->event_manager, &(vlc_event_t) {
72 .type = vlc_InputItemPreparseEnded,
73 .u.input_item_preparse_ended.new_status = status } );
74 }
75
input_item_SetPreparsed(input_item_t * p_i,bool b_preparsed)76 void input_item_SetPreparsed( input_item_t *p_i, bool b_preparsed )
77 {
78 bool b_send_event = false;
79
80 vlc_mutex_lock( &p_i->lock );
81
82 if( !p_i->p_meta )
83 p_i->p_meta = vlc_meta_New();
84
85 int status = vlc_meta_GetStatus(p_i->p_meta);
86 int new_status;
87 if( b_preparsed )
88 new_status = status | ITEM_PREPARSED;
89 else
90 new_status = status & ~ITEM_PREPARSED;
91 if( status != new_status )
92 {
93 vlc_meta_SetStatus(p_i->p_meta, new_status);
94 b_send_event = true;
95 }
96
97 vlc_mutex_unlock( &p_i->lock );
98
99 if( b_send_event )
100 {
101 vlc_event_send( &p_i->event_manager, &(vlc_event_t) {
102 .type = vlc_InputItemPreparsedChanged,
103 .u.input_item_preparsed_changed.new_status = new_status } );
104 }
105 }
106
input_item_SetArtNotFound(input_item_t * p_i,bool b_not_found)107 void input_item_SetArtNotFound( input_item_t *p_i, bool b_not_found )
108 {
109 vlc_mutex_lock( &p_i->lock );
110
111 if( !p_i->p_meta )
112 p_i->p_meta = vlc_meta_New();
113
114 int status = vlc_meta_GetStatus(p_i->p_meta);
115
116 if( b_not_found )
117 status |= ITEM_ART_NOTFOUND;
118 else
119 status &= ~ITEM_ART_NOTFOUND;
120
121 vlc_meta_SetStatus(p_i->p_meta, status);
122
123 vlc_mutex_unlock( &p_i->lock );
124 }
125
input_item_SetArtFetched(input_item_t * p_i,bool b_art_fetched)126 void input_item_SetArtFetched( input_item_t *p_i, bool b_art_fetched )
127 {
128 vlc_mutex_lock( &p_i->lock );
129
130 if( !p_i->p_meta )
131 p_i->p_meta = vlc_meta_New();
132
133 int status = vlc_meta_GetStatus(p_i->p_meta);
134
135 if( b_art_fetched )
136 status |= ITEM_ART_FETCHED;
137 else
138 status &= ~ITEM_ART_FETCHED;
139
140 vlc_meta_SetStatus(p_i->p_meta, status);
141
142 vlc_mutex_unlock( &p_i->lock );
143 }
144
input_item_SetMeta(input_item_t * p_i,vlc_meta_type_t meta_type,const char * psz_val)145 void input_item_SetMeta( input_item_t *p_i, vlc_meta_type_t meta_type, const char *psz_val )
146 {
147 vlc_mutex_lock( &p_i->lock );
148 if( !p_i->p_meta )
149 p_i->p_meta = vlc_meta_New();
150 vlc_meta_Set( p_i->p_meta, meta_type, psz_val );
151 vlc_mutex_unlock( &p_i->lock );
152
153 /* Notify interested third parties */
154 vlc_event_send( &p_i->event_manager, &(vlc_event_t) {
155 .type = vlc_InputItemMetaChanged,
156 .u.input_item_meta_changed.meta_type = meta_type } );
157 }
158
input_item_CopyOptions(input_item_t * p_child,input_item_t * p_parent)159 void input_item_CopyOptions( input_item_t *p_child,
160 input_item_t *p_parent )
161 {
162 char **optv = NULL;
163 uint8_t *flagv = NULL;
164 int optc = 0;
165 char **optv_realloc = NULL;
166 uint8_t *flagv_realloc = NULL;
167
168 vlc_mutex_lock( &p_parent->lock );
169
170 if( p_parent->i_options > 0 )
171 {
172 optv = vlc_alloc( p_parent->i_options, sizeof (*optv) );
173 if( likely(optv) )
174 flagv = vlc_alloc( p_parent->i_options, sizeof (*flagv) );
175
176 if( likely(flagv) )
177 {
178 for( int i = 0; i < p_parent->i_options; i++ )
179 {
180 char *psz_dup = strdup( p_parent->ppsz_options[i] );
181 if( likely(psz_dup) )
182 {
183 flagv[optc] = p_parent->optflagv[i];
184 optv[optc++] = psz_dup;
185 }
186 }
187 }
188 }
189
190 vlc_mutex_unlock( &p_parent->lock );
191
192 if( likely(optv && flagv && optc ) )
193 {
194 vlc_mutex_lock( &p_child->lock );
195
196 if( INT_MAX - p_child->i_options >= optc &&
197 SIZE_MAX / sizeof (*flagv) >= (size_t) (p_child->i_options + optc) )
198 flagv_realloc = realloc( p_child->optflagv,
199 (p_child->i_options + optc) * sizeof (*flagv) );
200 if( likely(flagv_realloc) )
201 {
202 p_child->optflagv = flagv_realloc;
203 if( SIZE_MAX / sizeof (*optv) >= (size_t) (p_child->i_options + optc) )
204 optv_realloc = realloc( p_child->ppsz_options,
205 (p_child->i_options + optc) * sizeof (*optv) );
206 if( likely(optv_realloc) )
207 {
208 p_child->ppsz_options = optv_realloc;
209 memcpy( p_child->ppsz_options + p_child->i_options, optv,
210 optc * sizeof (*optv) );
211 memcpy( p_child->optflagv + p_child->i_options, flagv,
212 optc * sizeof (*flagv) );
213 p_child->i_options += optc;
214 p_child->optflagc += optc;
215 }
216 }
217
218 vlc_mutex_unlock( &p_child->lock );
219 }
220
221 if( unlikely(!flagv_realloc || !optv_realloc) )
222 {
223 /* Didn't copy pointers, so need to free the strdup() */
224 for( int i=0; i<optc; i++ )
225 free( optv[i] );
226 }
227
228 free( flagv );
229 free( optv );
230 }
231
input_item_HasErrorWhenReading(input_item_t * p_item)232 bool input_item_HasErrorWhenReading( input_item_t *p_item )
233 {
234 vlc_mutex_lock( &p_item->lock );
235
236 bool b_error = p_item->b_error_when_reading;
237
238 vlc_mutex_unlock( &p_item->lock );
239
240 return b_error;
241 }
242
input_item_MetaMatch(input_item_t * p_i,vlc_meta_type_t meta_type,const char * psz)243 bool input_item_MetaMatch( input_item_t *p_i,
244 vlc_meta_type_t meta_type, const char *psz )
245 {
246 vlc_mutex_lock( &p_i->lock );
247
248 if( !p_i->p_meta )
249 {
250 vlc_mutex_unlock( &p_i->lock );
251 return false;
252 }
253 const char *psz_meta = vlc_meta_Get( p_i->p_meta, meta_type );
254 bool b_ret = psz_meta && strcasestr( psz_meta, psz );
255
256 vlc_mutex_unlock( &p_i->lock );
257
258 return b_ret;
259 }
260
input_item_GetMeta(input_item_t * p_i,vlc_meta_type_t meta_type)261 char *input_item_GetMeta( input_item_t *p_i, vlc_meta_type_t meta_type )
262 {
263 vlc_mutex_lock( &p_i->lock );
264
265 if( !p_i->p_meta )
266 {
267 vlc_mutex_unlock( &p_i->lock );
268 return NULL;
269 }
270
271 char *psz = NULL;
272 if( vlc_meta_Get( p_i->p_meta, meta_type ) )
273 psz = strdup( vlc_meta_Get( p_i->p_meta, meta_type ) );
274
275 vlc_mutex_unlock( &p_i->lock );
276 return psz;
277 }
278
279 /* Get the title of a given item or fallback to the name if the title is empty */
input_item_GetTitleFbName(input_item_t * p_item)280 char *input_item_GetTitleFbName( input_item_t *p_item )
281 {
282 char *psz_ret;
283 vlc_mutex_lock( &p_item->lock );
284
285 if( !p_item->p_meta )
286 {
287 psz_ret = p_item->psz_name ? strdup( p_item->psz_name ) : NULL;
288 vlc_mutex_unlock( &p_item->lock );
289 return psz_ret;
290 }
291
292 const char *psz_title = vlc_meta_Get( p_item->p_meta, vlc_meta_Title );
293 if( !EMPTY_STR( psz_title ) )
294 psz_ret = strdup( psz_title );
295 else
296 psz_ret = p_item->psz_name ? strdup( p_item->psz_name ) : NULL;
297
298 vlc_mutex_unlock( &p_item->lock );
299 return psz_ret;
300 }
301
input_item_GetName(input_item_t * p_item)302 char *input_item_GetName( input_item_t *p_item )
303 {
304 vlc_mutex_lock( &p_item->lock );
305
306 char *psz_name = p_item->psz_name ? strdup( p_item->psz_name ) : NULL;
307
308 vlc_mutex_unlock( &p_item->lock );
309 return psz_name;
310 }
input_item_SetName(input_item_t * p_item,const char * psz_name)311 void input_item_SetName( input_item_t *p_item, const char *psz_name )
312 {
313 vlc_mutex_lock( &p_item->lock );
314
315 free( p_item->psz_name );
316 p_item->psz_name = strdup( psz_name );
317
318 vlc_mutex_unlock( &p_item->lock );
319 }
320
input_item_GetURI(input_item_t * p_i)321 char *input_item_GetURI( input_item_t *p_i )
322 {
323 vlc_mutex_lock( &p_i->lock );
324
325 char *psz_s = p_i->psz_uri ? strdup( p_i->psz_uri ) : NULL;
326
327 vlc_mutex_unlock( &p_i->lock );
328 return psz_s;
329 }
330
input_item_SetURI(input_item_t * p_i,const char * psz_uri)331 void input_item_SetURI( input_item_t *p_i, const char *psz_uri )
332 {
333 assert( psz_uri );
334 #ifndef NDEBUG
335 if( !strstr( psz_uri, "://" )
336 || strchr( psz_uri, ' ' ) || strchr( psz_uri, '"' ) )
337 fprintf( stderr, "Warning: %s(\"%s\"): file path instead of URL.\n",
338 __func__, psz_uri );
339 #endif
340 vlc_mutex_lock( &p_i->lock );
341 free( p_i->psz_uri );
342 p_i->psz_uri = strdup( psz_uri );
343
344 p_i->i_type = GuessType( p_i, &p_i->b_net );
345
346 if( p_i->psz_name )
347 ;
348 else
349 if( p_i->i_type == ITEM_TYPE_FILE || p_i->i_type == ITEM_TYPE_DIRECTORY )
350 {
351 const char *psz_filename = strrchr( p_i->psz_uri, '/' );
352
353 if( psz_filename && *psz_filename == '/' )
354 psz_filename++;
355 if( psz_filename && *psz_filename )
356 p_i->psz_name = strdup( psz_filename );
357
358 /* Make the name more readable */
359 if( p_i->psz_name )
360 {
361 vlc_uri_decode( p_i->psz_name );
362 EnsureUTF8( p_i->psz_name );
363 }
364 }
365 else
366 { /* Strip login and password from title */
367 int r;
368 vlc_url_t url;
369
370 vlc_UrlParse( &url, psz_uri );
371 if( url.psz_protocol )
372 {
373 if( url.i_port > 0 )
374 r=asprintf( &p_i->psz_name, "%s://%s:%d%s", url.psz_protocol,
375 url.psz_host, url.i_port,
376 url.psz_path ? url.psz_path : "" );
377 else
378 r=asprintf( &p_i->psz_name, "%s://%s%s", url.psz_protocol,
379 url.psz_host ? url.psz_host : "",
380 url.psz_path ? url.psz_path : "" );
381 }
382 else
383 {
384 if( url.i_port > 0 )
385 r=asprintf( &p_i->psz_name, "%s:%d%s", url.psz_host, url.i_port,
386 url.psz_path ? url.psz_path : "" );
387 else
388 r=asprintf( &p_i->psz_name, "%s%s", url.psz_host,
389 url.psz_path ? url.psz_path : "" );
390 }
391 vlc_UrlClean( &url );
392 if( -1==r )
393 p_i->psz_name=NULL; /* recover from undefined value */
394 }
395
396 vlc_mutex_unlock( &p_i->lock );
397 }
398
input_item_GetDuration(input_item_t * p_i)399 mtime_t input_item_GetDuration( input_item_t *p_i )
400 {
401 vlc_mutex_lock( &p_i->lock );
402
403 mtime_t i_duration = p_i->i_duration;
404
405 vlc_mutex_unlock( &p_i->lock );
406 return i_duration;
407 }
408
input_item_SetDuration(input_item_t * p_i,mtime_t i_duration)409 void input_item_SetDuration( input_item_t *p_i, mtime_t i_duration )
410 {
411 bool b_send_event = false;
412
413 vlc_mutex_lock( &p_i->lock );
414 if( p_i->i_duration != i_duration )
415 {
416 p_i->i_duration = i_duration;
417 b_send_event = true;
418 }
419 vlc_mutex_unlock( &p_i->lock );
420
421 if( b_send_event )
422 {
423 vlc_event_send( &p_i->event_manager, &(vlc_event_t) {
424 .type = vlc_InputItemDurationChanged,
425 .u.input_item_duration_changed.new_duration = i_duration } );
426 }
427 }
428
input_item_GetNowPlayingFb(input_item_t * p_item)429 char *input_item_GetNowPlayingFb( input_item_t *p_item )
430 {
431 char *psz_meta = input_item_GetMeta( p_item, vlc_meta_NowPlaying );
432 if( !psz_meta || strlen( psz_meta ) == 0 )
433 {
434 free( psz_meta );
435 return input_item_GetMeta( p_item, vlc_meta_ESNowPlaying );
436 }
437
438 return psz_meta;
439 }
440
input_item_IsPreparsed(input_item_t * p_item)441 bool input_item_IsPreparsed( input_item_t *p_item )
442 {
443 vlc_mutex_lock( &p_item->lock );
444 bool b_preparsed = p_item->p_meta ? ( vlc_meta_GetStatus(p_item->p_meta) & ITEM_PREPARSED ) != 0 : false;
445 vlc_mutex_unlock( &p_item->lock );
446
447 return b_preparsed;
448 }
449
input_item_IsArtFetched(input_item_t * p_item)450 bool input_item_IsArtFetched( input_item_t *p_item )
451 {
452 vlc_mutex_lock( &p_item->lock );
453 bool b_fetched = p_item->p_meta ? ( vlc_meta_GetStatus(p_item->p_meta) & ITEM_ART_FETCHED ) != 0 : false;
454 vlc_mutex_unlock( &p_item->lock );
455
456 return b_fetched;
457 }
458
input_item_ShouldPreparseSubItems(input_item_t * p_item)459 bool input_item_ShouldPreparseSubItems( input_item_t *p_item )
460 {
461 bool b_ret;
462
463 vlc_mutex_lock( &p_item->lock );
464 b_ret = p_item->i_preparse_depth == -1 ? true : p_item->i_preparse_depth > 0;
465 vlc_mutex_unlock( &p_item->lock );
466
467 return b_ret;
468 }
469
input_item_Hold(input_item_t * p_item)470 input_item_t *input_item_Hold( input_item_t *p_item )
471 {
472 input_item_owner_t *owner = item_owner(p_item);
473
474 atomic_fetch_add( &owner->refs, 1 );
475 return p_item;
476 }
477
input_item_Release(input_item_t * p_item)478 void input_item_Release( input_item_t *p_item )
479 {
480 input_item_owner_t *owner = item_owner(p_item);
481
482 if( atomic_fetch_sub(&owner->refs, 1) != 1 )
483 return;
484
485 vlc_event_manager_fini( &p_item->event_manager );
486
487 free( p_item->psz_name );
488 free( p_item->psz_uri );
489 if( p_item->p_stats != NULL )
490 {
491 vlc_mutex_destroy( &p_item->p_stats->lock );
492 free( p_item->p_stats );
493 }
494
495 if( p_item->p_meta != NULL )
496 vlc_meta_Delete( p_item->p_meta );
497
498 for( input_item_opaque_t *o = p_item->opaques, *next; o != NULL; o = next )
499 {
500 next = o->next;
501 free( o );
502 }
503
504 for( int i = 0; i < p_item->i_options; i++ )
505 free( p_item->ppsz_options[i] );
506 TAB_CLEAN( p_item->i_options, p_item->ppsz_options );
507 free( p_item->optflagv );
508
509 for( int i = 0; i < p_item->i_es; i++ )
510 {
511 es_format_Clean( p_item->es[i] );
512 free( p_item->es[i] );
513 }
514 TAB_CLEAN( p_item->i_es, p_item->es );
515
516 for( int i = 0; i < p_item->i_epg; i++ )
517 vlc_epg_Delete( p_item->pp_epg[i] );
518 TAB_CLEAN( p_item->i_epg, p_item->pp_epg );
519
520 for( int i = 0; i < p_item->i_categories; i++ )
521 info_category_Delete( p_item->pp_categories[i] );
522 TAB_CLEAN( p_item->i_categories, p_item->pp_categories );
523
524 for( int i = 0; i < p_item->i_slaves; i++ )
525 input_item_slave_Delete( p_item->pp_slaves[i] );
526 TAB_CLEAN( p_item->i_slaves, p_item->pp_slaves );
527
528 vlc_mutex_destroy( &p_item->lock );
529 free( owner );
530 }
531
input_item_AddOption(input_item_t * p_input,const char * psz_option,unsigned flags)532 int input_item_AddOption( input_item_t *p_input, const char *psz_option,
533 unsigned flags )
534 {
535 int err = VLC_SUCCESS;
536
537 if( psz_option == NULL )
538 return VLC_EGENERIC;
539
540 vlc_mutex_lock( &p_input->lock );
541 if (flags & VLC_INPUT_OPTION_UNIQUE)
542 {
543 for (int i = 0 ; i < p_input->i_options; i++)
544 if( !strcmp( p_input->ppsz_options[i], psz_option ) )
545 goto out;
546 }
547
548 uint8_t *flagv = realloc (p_input->optflagv, p_input->optflagc + 1);
549 if (flagv == NULL)
550 {
551 err = VLC_ENOMEM;
552 goto out;
553 }
554
555 p_input->optflagv = flagv;
556
557 char* psz_option_dup = strdup( psz_option );
558 if( unlikely( !psz_option_dup ) )
559 {
560 err = VLC_ENOMEM;
561 goto out;
562 }
563
564 TAB_APPEND(p_input->i_options, p_input->ppsz_options, psz_option_dup);
565
566 flagv[p_input->optflagc++] = flags;
567
568 out:
569 vlc_mutex_unlock( &p_input->lock );
570 return err;
571 }
572
input_item_AddOptions(input_item_t * p_item,int i_options,const char * const * ppsz_options,unsigned i_flags)573 int input_item_AddOptions( input_item_t *p_item, int i_options,
574 const char *const *ppsz_options,
575 unsigned i_flags )
576 {
577 int i_ret = VLC_SUCCESS;
578 for( int i = 0; i < i_options && i_ret == VLC_SUCCESS; i++ )
579 i_ret = input_item_AddOption( p_item, ppsz_options[i], i_flags );
580 return i_ret;
581 }
582
input_item_AddOpaque(input_item_t * item,const char * name,void * value)583 int input_item_AddOpaque(input_item_t *item, const char *name, void *value)
584 {
585 assert(name != NULL);
586
587 size_t namelen = strlen(name);
588 input_item_opaque_t *entry = malloc(sizeof (*entry) + namelen);
589 if (unlikely(entry == NULL))
590 return VLC_ENOMEM;
591
592 memcpy(entry->name, name, namelen + 1);
593 entry->value = value;
594
595 vlc_mutex_lock(&item->lock);
596 entry->next = item->opaques;
597 item->opaques = entry;
598 vlc_mutex_unlock(&item->lock);
599 return VLC_SUCCESS;
600 }
601
input_item_ApplyOptions(vlc_object_t * obj,input_item_t * item)602 void input_item_ApplyOptions(vlc_object_t *obj, input_item_t *item)
603 {
604 vlc_mutex_lock(&item->lock);
605 assert(item->optflagc == (unsigned)item->i_options);
606
607 for (unsigned i = 0; i < (unsigned)item->i_options; i++)
608 var_OptionParse(obj, item->ppsz_options[i],
609 !!(item->optflagv[i] & VLC_INPUT_OPTION_TRUSTED));
610
611 for (const input_item_opaque_t *o = item->opaques; o != NULL; o = o->next)
612 {
613 var_Create(obj, o->name, VLC_VAR_ADDRESS);
614 var_SetAddress(obj, o->name, o->value);
615 }
616
617 vlc_mutex_unlock(&item->lock);
618 }
619
bsearch_strcmp_cb(const void * a,const void * b)620 static int bsearch_strcmp_cb(const void *a, const void *b)
621 {
622 const char *const *entry = b;
623 return strcasecmp(a, *entry);
624 }
625
input_item_IsMaster(const char * psz_filename)626 static bool input_item_IsMaster(const char *psz_filename)
627 {
628 static const char *const ppsz_master_exts[] = { MASTER_EXTENSIONS };
629
630 const char *psz_ext = strrchr(psz_filename, '.');
631 if (psz_ext == NULL || *(++psz_ext) == '\0')
632 return false;
633
634 return bsearch(psz_ext, ppsz_master_exts, ARRAY_SIZE(ppsz_master_exts),
635 sizeof(const char *), bsearch_strcmp_cb) != NULL;
636 }
637
input_item_slave_GetType(const char * psz_filename,enum slave_type * p_slave_type)638 bool input_item_slave_GetType(const char *psz_filename,
639 enum slave_type *p_slave_type)
640 {
641 static const char *const ppsz_sub_exts[] = { SLAVE_SPU_EXTENSIONS };
642 static const char *const ppsz_audio_exts[] = { SLAVE_AUDIO_EXTENSIONS };
643
644 static struct {
645 enum slave_type i_type;
646 const char *const *ppsz_exts;
647 size_t nmemb;
648 } p_slave_list[] = {
649 { SLAVE_TYPE_SPU, ppsz_sub_exts, ARRAY_SIZE(ppsz_sub_exts) },
650 { SLAVE_TYPE_AUDIO, ppsz_audio_exts, ARRAY_SIZE(ppsz_audio_exts) },
651 };
652
653 const char *psz_ext = strrchr(psz_filename, '.');
654 if (psz_ext == NULL || *(++psz_ext) == '\0')
655 return false;
656
657 for (unsigned int i = 0; i < sizeof(p_slave_list) / sizeof(*p_slave_list); ++i)
658 {
659 if (bsearch(psz_ext, p_slave_list[i].ppsz_exts, p_slave_list[i].nmemb,
660 sizeof(const char *), bsearch_strcmp_cb))
661 {
662 *p_slave_type = p_slave_list[i].i_type;
663 return true;
664 }
665 }
666 return false;
667 }
668
input_item_slave_New(const char * psz_uri,enum slave_type i_type,enum slave_priority i_priority)669 input_item_slave_t *input_item_slave_New(const char *psz_uri, enum slave_type i_type,
670 enum slave_priority i_priority)
671 {
672 if( !psz_uri )
673 return NULL;
674
675 input_item_slave_t *p_slave = malloc( sizeof( *p_slave ) + strlen( psz_uri ) + 1 );
676 if( !p_slave )
677 return NULL;
678
679 p_slave->i_type = i_type;
680 p_slave->i_priority = i_priority;
681 p_slave->b_forced = false;
682 strcpy( p_slave->psz_uri, psz_uri );
683
684 return p_slave;
685 }
686
input_item_AddSlave(input_item_t * p_item,input_item_slave_t * p_slave)687 int input_item_AddSlave(input_item_t *p_item, input_item_slave_t *p_slave)
688 {
689 if( p_item == NULL || p_slave == NULL
690 || p_slave->i_priority < SLAVE_PRIORITY_MATCH_NONE )
691 return VLC_EGENERIC;
692
693 vlc_mutex_lock( &p_item->lock );
694
695 TAB_APPEND(p_item->i_slaves, p_item->pp_slaves, p_slave);
696
697 vlc_mutex_unlock( &p_item->lock );
698 return VLC_SUCCESS;
699 }
700
InputItemFindCat(input_item_t * p_item,int * pi_index,const char * psz_cat)701 static info_category_t *InputItemFindCat( input_item_t *p_item,
702 int *pi_index, const char *psz_cat )
703 {
704 vlc_assert_locked( &p_item->lock );
705 for( int i = 0; i < p_item->i_categories && psz_cat; i++ )
706 {
707 info_category_t *p_cat = p_item->pp_categories[i];
708
709 if( !strcmp( p_cat->psz_name, psz_cat ) )
710 {
711 if( pi_index )
712 *pi_index = i;
713 return p_cat;
714 }
715 }
716 return NULL;
717 }
718
719 /**
720 * Get a info item from a given category in a given input item.
721 *
722 * \param p_i The input item to get info from
723 * \param psz_cat String representing the category for the info
724 * \param psz_name String representing the name of the desired info
725 * \return A pointer to the string with the given info if found, or an
726 * empty string otherwise. The caller should free the returned
727 * pointer.
728 */
input_item_GetInfo(input_item_t * p_i,const char * psz_cat,const char * psz_name)729 char *input_item_GetInfo( input_item_t *p_i,
730 const char *psz_cat,
731 const char *psz_name )
732 {
733 vlc_mutex_lock( &p_i->lock );
734
735 const info_category_t *p_cat = InputItemFindCat( p_i, NULL, psz_cat );
736 if( p_cat )
737 {
738 info_t *p_info = info_category_FindInfo( p_cat, NULL, psz_name );
739 if( p_info && p_info->psz_value )
740 {
741 char *psz_ret = strdup( p_info->psz_value );
742 vlc_mutex_unlock( &p_i->lock );
743 return psz_ret;
744 }
745 }
746 vlc_mutex_unlock( &p_i->lock );
747 return strdup( "" );
748 }
749
InputItemVaAddInfo(input_item_t * p_i,const char * psz_cat,const char * psz_name,const char * psz_format,va_list args)750 static int InputItemVaAddInfo( input_item_t *p_i,
751 const char *psz_cat,
752 const char *psz_name,
753 const char *psz_format, va_list args )
754 {
755 vlc_assert_locked( &p_i->lock );
756
757 info_category_t *p_cat = InputItemFindCat( p_i, NULL, psz_cat );
758 if( !p_cat )
759 {
760 p_cat = info_category_New( psz_cat );
761 if( !p_cat )
762 return VLC_ENOMEM;
763 TAB_APPEND(p_i->i_categories, p_i->pp_categories, p_cat);
764 }
765 info_t *p_info = info_category_VaAddInfo( p_cat, psz_name, psz_format, args );
766 if( !p_info || !p_info->psz_value )
767 return VLC_EGENERIC;
768 return VLC_SUCCESS;
769 }
770
input_item_AddInfo(input_item_t * p_i,const char * psz_cat,const char * psz_name,const char * psz_format,...)771 int input_item_AddInfo( input_item_t *p_i,
772 const char *psz_cat,
773 const char *psz_name,
774 const char *psz_format, ... )
775 {
776 va_list args;
777
778 vlc_mutex_lock( &p_i->lock );
779
780 va_start( args, psz_format );
781 const int i_ret = InputItemVaAddInfo( p_i, psz_cat, psz_name, psz_format, args );
782 va_end( args );
783
784 vlc_mutex_unlock( &p_i->lock );
785
786
787 if( !i_ret )
788 vlc_event_send( &p_i->event_manager, &(vlc_event_t) {
789 .type = vlc_InputItemInfoChanged } );
790
791 return i_ret;
792 }
793
input_item_DelInfo(input_item_t * p_i,const char * psz_cat,const char * psz_name)794 int input_item_DelInfo( input_item_t *p_i,
795 const char *psz_cat,
796 const char *psz_name )
797 {
798 vlc_mutex_lock( &p_i->lock );
799 int i_cat;
800 info_category_t *p_cat = InputItemFindCat( p_i, &i_cat, psz_cat );
801 if( !p_cat )
802 {
803 vlc_mutex_unlock( &p_i->lock );
804 return VLC_EGENERIC;
805 }
806
807 if( psz_name )
808 {
809 /* Remove a specific info */
810 int i_ret = info_category_DeleteInfo( p_cat, psz_name );
811 if( i_ret )
812 {
813 vlc_mutex_unlock( &p_i->lock );
814 return VLC_EGENERIC;
815 }
816 }
817 else
818 {
819 /* Remove the complete categorie */
820 info_category_Delete( p_cat );
821 TAB_ERASE(p_i->i_categories, p_i->pp_categories, i_cat);
822 }
823 vlc_mutex_unlock( &p_i->lock );
824
825 vlc_event_send( &p_i->event_manager,
826 &(vlc_event_t) { .type = vlc_InputItemInfoChanged } );
827
828 return VLC_SUCCESS;
829 }
input_item_ReplaceInfos(input_item_t * p_item,info_category_t * p_cat)830 void input_item_ReplaceInfos( input_item_t *p_item, info_category_t *p_cat )
831 {
832 vlc_mutex_lock( &p_item->lock );
833 int i_cat;
834 info_category_t *p_old = InputItemFindCat( p_item, &i_cat, p_cat->psz_name );
835 if( p_old )
836 {
837 info_category_Delete( p_old );
838 p_item->pp_categories[i_cat] = p_cat;
839 }
840 else
841 TAB_APPEND(p_item->i_categories, p_item->pp_categories, p_cat);
842 vlc_mutex_unlock( &p_item->lock );
843
844 vlc_event_send( &p_item->event_manager,
845 &(vlc_event_t) { .type = vlc_InputItemInfoChanged } );
846 }
847
input_item_MergeInfos(input_item_t * p_item,info_category_t * p_cat)848 void input_item_MergeInfos( input_item_t *p_item, info_category_t *p_cat )
849 {
850 vlc_mutex_lock( &p_item->lock );
851 info_category_t *p_old = InputItemFindCat( p_item, NULL, p_cat->psz_name );
852 if( p_old )
853 {
854 for( int i = 0; i < p_cat->i_infos; i++ )
855 info_category_ReplaceInfo( p_old, p_cat->pp_infos[i] );
856 TAB_CLEAN( p_cat->i_infos, p_cat->pp_infos );
857 info_category_Delete( p_cat );
858 }
859 else
860 TAB_APPEND(p_item->i_categories, p_item->pp_categories, p_cat);
861 vlc_mutex_unlock( &p_item->lock );
862
863 vlc_event_send( &p_item->event_manager,
864 &(vlc_event_t) { .type = vlc_InputItemInfoChanged } );
865 }
866
input_item_SetEpgEvent(input_item_t * p_item,const vlc_epg_event_t * p_epg_evt)867 void input_item_SetEpgEvent( input_item_t *p_item, const vlc_epg_event_t *p_epg_evt )
868 {
869 bool b_changed = false;
870 vlc_mutex_lock( &p_item->lock );
871
872 for( int i = 0; i < p_item->i_epg; i++ )
873 {
874 vlc_epg_t *p_epg = p_item->pp_epg[i];
875 for( size_t j = 0; j < p_epg->i_event; j++ )
876 {
877 /* Same event can exist in more than one table */
878 if( p_epg->pp_event[j]->i_id == p_epg_evt->i_id )
879 {
880 vlc_epg_event_t *p_dup = vlc_epg_event_Duplicate( p_epg_evt );
881 if( p_dup )
882 {
883 if( p_epg->p_current == p_epg->pp_event[j] )
884 p_epg->p_current = p_dup;
885 vlc_epg_event_Delete( p_epg->pp_event[j] );
886 p_epg->pp_event[j] = p_dup;
887 b_changed = true;
888 }
889 break;
890 }
891 }
892 }
893 vlc_mutex_unlock( &p_item->lock );
894
895 if ( b_changed )
896 {
897 vlc_event_send( &p_item->event_manager,
898 &(vlc_event_t) { .type = vlc_InputItemInfoChanged } );
899 }
900 }
901
902 //#define EPG_DEBUG
903 #ifdef EPG_DEBUG
InputItemAddInfo(input_item_t * p_i,const char * psz_cat,const char * psz_name,const char * psz_format,...)904 static int InputItemAddInfo( input_item_t *p_i,
905 const char *psz_cat,
906 const char *psz_name,
907 const char *psz_format, ... )
908 {
909 va_list args;
910
911 va_start( args, psz_format );
912 const int i_ret = InputItemVaAddInfo( p_i, psz_cat, psz_name, psz_format, args );
913 va_end( args );
914
915 return i_ret;
916 }
917 #endif
918
input_item_SetEpg(input_item_t * p_item,const vlc_epg_t * p_update,bool b_current_source)919 void input_item_SetEpg( input_item_t *p_item, const vlc_epg_t *p_update, bool b_current_source )
920 {
921 vlc_epg_t *p_epg = vlc_epg_Duplicate( p_update );
922 if( !p_epg )
923 return;
924
925 vlc_mutex_lock( &p_item->lock );
926
927 /* */
928 vlc_epg_t **pp_epg = NULL;
929 for( int i = 0; i < p_item->i_epg; i++ )
930 {
931 if( p_item->pp_epg[i]->i_source_id == p_update->i_source_id &&
932 p_item->pp_epg[i]->i_id == p_update->i_id )
933 {
934 pp_epg = &p_item->pp_epg[i];
935 break;
936 }
937 }
938
939 /* replace with new version */
940 if( pp_epg )
941 {
942 vlc_epg_Delete( *pp_epg );
943 if( *pp_epg == p_item->p_epg_table ) /* current table can have changed */
944 p_item->p_epg_table = NULL;
945 *pp_epg = p_epg;
946 }
947 else
948 {
949 TAB_APPEND( p_item->i_epg, p_item->pp_epg, p_epg );
950 }
951
952 if( b_current_source && p_epg->b_present )
953 p_item->p_epg_table = p_epg;
954
955 vlc_mutex_unlock( &p_item->lock );
956
957 #ifdef EPG_DEBUG
958 char *psz_epg;
959 if( asprintf( &psz_epg, "EPG %s", p_epg->psz_name ? p_epg->psz_name : "unknown" ) < 0 )
960 goto signal;
961
962 input_item_DelInfo( p_item, psz_epg, NULL );
963
964 vlc_mutex_lock( &p_item->lock );
965 for( size_t i = 0; i < p_epg->i_event; i++ )
966 {
967 const vlc_epg_event_t *p_evt = p_epg->pp_event[i];
968 time_t t_start = (time_t)p_evt->i_start;
969 struct tm tm_start;
970 char psz_start[128];
971
972 localtime_r( &t_start, &tm_start );
973
974 snprintf( psz_start, sizeof(psz_start), "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d",
975 1900 + tm_start.tm_year, 1 + tm_start.tm_mon, tm_start.tm_mday,
976 tm_start.tm_hour, tm_start.tm_min, tm_start.tm_sec );
977 if( p_evt->psz_short_description || p_evt->psz_description )
978 InputItemAddInfo( p_item, psz_epg, psz_start, "%s (%2.2d:%2.2d) - %s %s",
979 p_evt->psz_name,
980 p_evt->i_duration/60/60, (p_evt->i_duration/60)%60,
981 p_evt->psz_short_description ? p_evt->psz_short_description : "" ,
982 p_evt->psz_description ? p_evt->psz_description : "" );
983 else
984 InputItemAddInfo( p_item, psz_epg, psz_start, "%s (%2.2d:%2.2d)",
985 p_evt->psz_name,
986 p_evt->i_duration/60/60, (p_evt->i_duration/60)%60 );
987 }
988 vlc_mutex_unlock( &p_item->lock );
989 free( psz_epg );
990 signal:
991 #endif
992 vlc_event_send( &p_item->event_manager,
993 &(vlc_event_t){ .type = vlc_InputItemInfoChanged, } );
994 }
995
input_item_ChangeEPGSource(input_item_t * p_item,int i_source_id)996 void input_item_ChangeEPGSource( input_item_t *p_item, int i_source_id )
997 {
998 vlc_mutex_lock( &p_item->lock );
999 p_item->p_epg_table = NULL;
1000 if( i_source_id > 0 )
1001 {
1002 /* Update pointer to current/next table in the full schedule */
1003 for( int i = 0; i < p_item->i_epg; i++ )
1004 {
1005 if( p_item->pp_epg[i]->i_source_id == i_source_id &&
1006 p_item->pp_epg[i]->b_present )
1007 {
1008 p_item->p_epg_table = p_item->pp_epg[i];
1009 break;
1010 }
1011 }
1012 }
1013 vlc_mutex_unlock( &p_item->lock );
1014 }
1015
input_item_SetEpgTime(input_item_t * p_item,int64_t i_time)1016 void input_item_SetEpgTime( input_item_t *p_item, int64_t i_time )
1017 {
1018 vlc_mutex_lock( &p_item->lock );
1019 p_item->i_epg_time = i_time;
1020 vlc_mutex_unlock( &p_item->lock );
1021 }
1022
input_item_SetEpgOffline(input_item_t * p_item)1023 void input_item_SetEpgOffline( input_item_t *p_item )
1024 {
1025 input_item_ChangeEPGSource( p_item, -1 );
1026
1027 #ifdef EPG_DEBUG
1028 vlc_mutex_lock( &p_item->lock );
1029 const int i_epg_info = p_item->i_epg;
1030 if( i_epg_info > 0 )
1031 {
1032 char *ppsz_epg_info[i_epg_info];
1033 for( int i = 0; i < p_item->i_epg; i++ )
1034 {
1035 const vlc_epg_t *p_epg = p_item->pp_epg[i];
1036 if( asprintf( &ppsz_epg_info[i], "EPG %s", p_epg->psz_name ? p_epg->psz_name : "unknown" ) < 0 )
1037 ppsz_epg_info[i] = NULL;
1038 }
1039 vlc_mutex_unlock( &p_item->lock );
1040
1041 for( int i = 0; i < i_epg_info; i++ )
1042 {
1043 if( !ppsz_epg_info[i] )
1044 continue;
1045 input_item_DelInfo( p_item, ppsz_epg_info[i], NULL );
1046 free( ppsz_epg_info[i] );
1047 }
1048 }
1049 else
1050 vlc_mutex_unlock( &p_item->lock );
1051 #endif
1052
1053 vlc_event_send( &p_item->event_manager,
1054 &(vlc_event_t) { .type = vlc_InputItemInfoChanged } );
1055 }
1056
1057 input_item_t *
input_item_NewExt(const char * psz_uri,const char * psz_name,mtime_t duration,int type,enum input_item_net_type i_net)1058 input_item_NewExt( const char *psz_uri, const char *psz_name,
1059 mtime_t duration, int type, enum input_item_net_type i_net )
1060 {
1061 input_item_owner_t *owner = calloc( 1, sizeof( *owner ) );
1062 if( unlikely(owner == NULL) )
1063 return NULL;
1064
1065 atomic_init( &owner->refs, 1 );
1066
1067 input_item_t *p_input = &owner->item;
1068 vlc_event_manager_t * p_em = &p_input->event_manager;
1069
1070 vlc_mutex_init( &p_input->lock );
1071
1072 p_input->psz_name = NULL;
1073 if( psz_name )
1074 input_item_SetName( p_input, psz_name );
1075
1076 p_input->psz_uri = NULL;
1077 if( psz_uri )
1078 input_item_SetURI( p_input, psz_uri );
1079 else
1080 {
1081 p_input->i_type = ITEM_TYPE_UNKNOWN;
1082 p_input->b_net = false;
1083 }
1084
1085 TAB_INIT( p_input->i_options, p_input->ppsz_options );
1086 p_input->optflagc = 0;
1087 p_input->optflagv = NULL;
1088 p_input->opaques = NULL;
1089
1090 p_input->i_duration = duration;
1091 TAB_INIT( p_input->i_categories, p_input->pp_categories );
1092 TAB_INIT( p_input->i_es, p_input->es );
1093 p_input->p_stats = NULL;
1094 p_input->p_meta = NULL;
1095 TAB_INIT( p_input->i_epg, p_input->pp_epg );
1096 TAB_INIT( p_input->i_slaves, p_input->pp_slaves );
1097
1098 vlc_event_manager_init( p_em, p_input );
1099
1100 if( type != ITEM_TYPE_UNKNOWN )
1101 p_input->i_type = type;
1102 p_input->b_error_when_reading = false;
1103
1104 if( i_net != ITEM_NET_UNKNOWN )
1105 p_input->b_net = i_net == ITEM_NET;
1106 return p_input;
1107 }
1108
input_item_Copy(input_item_t * p_input)1109 input_item_t *input_item_Copy( input_item_t *p_input )
1110 {
1111 vlc_meta_t *meta = NULL;
1112 input_item_t *item;
1113 bool b_net;
1114
1115 vlc_mutex_lock( &p_input->lock );
1116
1117 item = input_item_NewExt( p_input->psz_uri, p_input->psz_name,
1118 p_input->i_duration, p_input->i_type,
1119 ITEM_NET_UNKNOWN );
1120 if( likely(item != NULL) && p_input->p_meta != NULL )
1121 {
1122 meta = vlc_meta_New();
1123 vlc_meta_Merge( meta, p_input->p_meta );
1124 }
1125 b_net = p_input->b_net;
1126 vlc_mutex_unlock( &p_input->lock );
1127
1128 if( likely(item != NULL) )
1129 { /* No need to lock; no other thread has seen this new item yet. */
1130 input_item_CopyOptions( item, p_input );
1131 item->p_meta = meta;
1132 item->b_net = b_net;
1133 }
1134
1135 return item;
1136 }
1137
1138 struct item_type_entry
1139 {
1140 const char *psz_scheme;
1141 uint8_t i_type;
1142 bool b_net;
1143 };
1144
typecmp(const void * key,const void * entry)1145 static int typecmp( const void *key, const void *entry )
1146 {
1147 const struct item_type_entry *type = entry;
1148 const char *uri = key, *scheme = type->psz_scheme;
1149
1150 return strncmp( uri, scheme, strlen( scheme ) );
1151 }
1152
1153 /* Guess the type of the item using the beginning of the mrl */
GuessType(const input_item_t * p_item,bool * p_net)1154 static int GuessType( const input_item_t *p_item, bool *p_net )
1155 {
1156 static const struct item_type_entry tab[] =
1157 { /* /!\ Alphabetical order /!\ */
1158 /* Short match work, not just exact match */
1159 { "alsa", ITEM_TYPE_CARD, false },
1160 { "atsc", ITEM_TYPE_CARD, false },
1161 { "bd", ITEM_TYPE_DISC, false },
1162 { "bluray", ITEM_TYPE_DISC, false },
1163 { "cable", ITEM_TYPE_CARD, false },
1164 { "cdda", ITEM_TYPE_DISC, false },
1165 { "cqam", ITEM_TYPE_CARD, false },
1166 { "dc1394", ITEM_TYPE_CARD, false },
1167 { "dccp", ITEM_TYPE_STREAM, true },
1168 { "deckli", ITEM_TYPE_CARD, false }, /* decklink */
1169 { "dir", ITEM_TYPE_DIRECTORY, false },
1170 { "dshow", ITEM_TYPE_CARD, false },
1171 { "dtv", ITEM_TYPE_CARD, false },
1172 { "dvb", ITEM_TYPE_CARD, false },
1173 { "dvd", ITEM_TYPE_DISC, false },
1174 { "eyetv", ITEM_TYPE_CARD, false },
1175 { "fd", ITEM_TYPE_UNKNOWN, false },
1176 { "file", ITEM_TYPE_FILE, false },
1177 { "ftp", ITEM_TYPE_FILE, true },
1178 { "http", ITEM_TYPE_FILE, true },
1179 { "icyx", ITEM_TYPE_STREAM, true },
1180 { "imem", ITEM_TYPE_UNKNOWN, false },
1181 { "isdb-", ITEM_TYPE_CARD, false },
1182 { "itpc", ITEM_TYPE_PLAYLIST, true },
1183 { "jack", ITEM_TYPE_CARD, false },
1184 { "linsys", ITEM_TYPE_CARD, false },
1185 { "live", ITEM_TYPE_STREAM, true }, /* livedotcom */
1186 { "mms", ITEM_TYPE_STREAM, true },
1187 { "mtp", ITEM_TYPE_DISC, false },
1188 { "nfs", ITEM_TYPE_FILE, true },
1189 { "ofdm", ITEM_TYPE_CARD, false },
1190 { "oss", ITEM_TYPE_CARD, false },
1191 { "pnm", ITEM_TYPE_STREAM, true },
1192 { "pulse", ITEM_TYPE_CARD, false },
1193 { "qam", ITEM_TYPE_CARD, false },
1194 { "qpsk", ITEM_TYPE_CARD, false },
1195 { "qtcapt", ITEM_TYPE_CARD, false }, /* qtcapture */
1196 { "qtsound",ITEM_TYPE_CARD, false },
1197 { "raw139", ITEM_TYPE_CARD, false }, /* raw1394 */
1198 { "rt", ITEM_TYPE_STREAM, true }, /* rtp, rtsp, rtmp */
1199 { "satell", ITEM_TYPE_CARD, false }, /* satellite */
1200 { "satip", ITEM_TYPE_STREAM, true }, /* satellite over ip */
1201 { "screen", ITEM_TYPE_CARD, false },
1202 { "sdp", ITEM_TYPE_STREAM, true },
1203 { "sftp", ITEM_TYPE_FILE, true },
1204 { "shm", ITEM_TYPE_CARD, false },
1205 { "smb", ITEM_TYPE_FILE, true },
1206 { "stream", ITEM_TYPE_STREAM, false },
1207 { "svcd", ITEM_TYPE_DISC, false },
1208 { "tcp", ITEM_TYPE_STREAM, true },
1209 { "terres", ITEM_TYPE_CARD, false }, /* terrestrial */
1210 { "udp", ITEM_TYPE_STREAM, true }, /* udplite too */
1211 { "unsv", ITEM_TYPE_STREAM, true },
1212 { "upnp", ITEM_TYPE_FILE, true },
1213 { "usdigi", ITEM_TYPE_CARD, false }, /* usdigital */
1214 { "v4l", ITEM_TYPE_CARD, false },
1215 { "vcd", ITEM_TYPE_DISC, false },
1216 { "vdr", ITEM_TYPE_STREAM, true },
1217 { "wasapi", ITEM_TYPE_CARD, false },
1218 { "window", ITEM_TYPE_CARD, false },
1219 };
1220
1221 #ifndef NDEBUG
1222 for( size_t i = 1; i < ARRAY_SIZE( tab ); i++ )
1223 assert( typecmp( (tab + i)->psz_scheme, tab + i - 1 ) > 0 );
1224 #endif
1225
1226 *p_net = false;
1227
1228 if( strstr( p_item->psz_uri, "://" ) == NULL )
1229 return ITEM_TYPE_UNKNOWN; /* invalid URI */
1230
1231 const struct item_type_entry *e =
1232 bsearch( p_item->psz_uri, tab, ARRAY_SIZE( tab ),
1233 sizeof( tab[0] ), typecmp );
1234 if( e == NULL )
1235 return ITEM_TYPE_UNKNOWN;
1236
1237 *p_net = e->b_net;
1238 return e->i_type;
1239 }
1240
input_item_node_Create(input_item_t * p_input)1241 input_item_node_t *input_item_node_Create( input_item_t *p_input )
1242 {
1243 input_item_node_t* p_node = malloc( sizeof( input_item_node_t ) );
1244 if( !p_node )
1245 return NULL;
1246
1247 assert( p_input );
1248
1249 p_node->p_item = p_input;
1250 input_item_Hold( p_input );
1251
1252 p_node->i_children = 0;
1253 p_node->pp_children = NULL;
1254
1255 return p_node;
1256 }
1257
input_item_node_Delete(input_item_node_t * p_node)1258 void input_item_node_Delete( input_item_node_t *p_node )
1259 {
1260 for( int i = 0; i < p_node->i_children; i++ )
1261 input_item_node_Delete( p_node->pp_children[i] );
1262
1263 input_item_Release( p_node->p_item );
1264 free( p_node->pp_children );
1265 free( p_node );
1266 }
1267
input_item_node_AppendItem(input_item_node_t * p_node,input_item_t * p_item)1268 input_item_node_t *input_item_node_AppendItem( input_item_node_t *p_node, input_item_t *p_item )
1269 {
1270 int i_preparse_depth;
1271 input_item_node_t *p_new_child = input_item_node_Create( p_item );
1272 if( !p_new_child ) return NULL;
1273
1274 vlc_mutex_lock( &p_node->p_item->lock );
1275 i_preparse_depth = p_node->p_item->i_preparse_depth;
1276 vlc_mutex_unlock( &p_node->p_item->lock );
1277
1278 vlc_mutex_lock( &p_item->lock );
1279 p_item->i_preparse_depth = i_preparse_depth > 0 ?
1280 i_preparse_depth -1 :
1281 i_preparse_depth;
1282 vlc_mutex_unlock( &p_item->lock );
1283
1284 input_item_node_AppendNode( p_node, p_new_child );
1285 return p_new_child;
1286 }
1287
input_item_node_AppendNode(input_item_node_t * p_parent,input_item_node_t * p_child)1288 void input_item_node_AppendNode( input_item_node_t *p_parent,
1289 input_item_node_t *p_child )
1290 {
1291 assert(p_parent != NULL);
1292 assert(p_child != NULL);
1293 TAB_APPEND(p_parent->i_children, p_parent->pp_children, p_child);
1294 }
1295
input_item_node_RemoveNode(input_item_node_t * parent,input_item_node_t * child)1296 void input_item_node_RemoveNode( input_item_node_t *parent,
1297 input_item_node_t *child )
1298 {
1299 TAB_REMOVE(parent->i_children, parent->pp_children, child);
1300 }
1301
input_item_node_PostAndDelete(input_item_node_t * p_root)1302 void input_item_node_PostAndDelete( input_item_node_t *p_root )
1303 {
1304 vlc_event_send( &p_root->p_item->event_manager, &(vlc_event_t) {
1305 .type = vlc_InputItemSubItemTreeAdded,
1306 .u.input_item_subitem_tree_added.p_root = p_root } );
1307
1308 input_item_node_Delete( p_root );
1309 }
1310
1311 /* Called by es_out when a new Elementary Stream is added or updated. */
input_item_UpdateTracksInfo(input_item_t * item,const es_format_t * fmt)1312 void input_item_UpdateTracksInfo(input_item_t *item, const es_format_t *fmt)
1313 {
1314 int i;
1315 es_format_t *fmt_copy = malloc(sizeof *fmt_copy);
1316 if (!fmt_copy)
1317 return;
1318
1319 es_format_Copy(fmt_copy, fmt);
1320
1321 vlc_mutex_lock( &item->lock );
1322
1323 for( i = 0; i < item->i_es; i++ )
1324 {
1325 if (item->es[i]->i_id != fmt->i_id)
1326 continue;
1327
1328 /* We've found the right ES, replace it */
1329 es_format_Clean(item->es[i]);
1330 free(item->es[i]);
1331 item->es[i] = fmt_copy;
1332 vlc_mutex_unlock( &item->lock );
1333 return;
1334 }
1335
1336 /* ES not found, insert it */
1337 TAB_APPEND(item->i_es, item->es, fmt_copy);
1338 vlc_mutex_unlock( &item->lock );
1339 }
1340
rdh_compar_type(input_item_t * p1,input_item_t * p2)1341 static int rdh_compar_type(input_item_t *p1, input_item_t *p2)
1342 {
1343 if (p1->i_type != p2->i_type)
1344 {
1345 if (p1->i_type == ITEM_TYPE_DIRECTORY)
1346 return -1;
1347 if (p2->i_type == ITEM_TYPE_DIRECTORY)
1348 return 1;
1349 }
1350 return 0;
1351 }
1352
rdh_compar_filename(const void * a,const void * b)1353 static int rdh_compar_filename(const void *a, const void *b)
1354 {
1355 input_item_node_t *const *na = a, *const *nb = b;
1356 input_item_t *ia = (*na)->p_item, *ib = (*nb)->p_item;
1357
1358 int i_ret = rdh_compar_type(ia, ib);
1359 if (i_ret != 0)
1360 return i_ret;
1361
1362 return vlc_filenamecmp(ia->psz_name, ib->psz_name);
1363 }
1364
rdh_sort(input_item_node_t * p_node)1365 static void rdh_sort(input_item_node_t *p_node)
1366 {
1367 if (p_node->i_children <= 0)
1368 return;
1369
1370 /* Sort current node */
1371 qsort(p_node->pp_children, p_node->i_children,
1372 sizeof(input_item_node_t *), rdh_compar_filename);
1373
1374 /* Sort all children */
1375 for (int i = 0; i < p_node->i_children; i++)
1376 rdh_sort(p_node->pp_children[i]);
1377 }
1378
1379 /**
1380 * Does the provided file name has one of the extension provided ?
1381 */
rdh_file_has_ext(const char * psz_filename,const char * psz_ignored_exts)1382 static bool rdh_file_has_ext(const char *psz_filename,
1383 const char *psz_ignored_exts)
1384 {
1385 if (psz_ignored_exts == NULL)
1386 return false;
1387
1388 const char *ext = strrchr(psz_filename, '.');
1389 if (ext == NULL)
1390 return false;
1391
1392 size_t extlen = strlen(++ext);
1393
1394 for (const char *type = psz_ignored_exts, *end; type[0]; type = end + 1)
1395 {
1396 end = strchr(type, ',');
1397 if (end == NULL)
1398 end = type + strlen(type);
1399
1400 if (type + extlen == end && !strncasecmp(ext, type, extlen))
1401 return true;
1402
1403 if (*end == '\0')
1404 break;
1405 }
1406
1407 return false;
1408 }
1409
rdh_file_is_ignored(struct vlc_readdir_helper * p_rdh,const char * psz_filename)1410 static bool rdh_file_is_ignored(struct vlc_readdir_helper *p_rdh,
1411 const char *psz_filename)
1412 {
1413 return (psz_filename[0] == '\0'
1414 || strcmp(psz_filename, ".") == 0
1415 || strcmp(psz_filename, "..") == 0
1416 || (!p_rdh->b_show_hiddenfiles && psz_filename[0] == '.')
1417 || rdh_file_has_ext(psz_filename, p_rdh->psz_ignored_exts));
1418 }
1419
1420 struct rdh_slave
1421 {
1422 input_item_slave_t *p_slave;
1423 char *psz_filename;
1424 input_item_node_t *p_node;
1425 };
1426
1427 struct rdh_dir
1428 {
1429 input_item_node_t *p_node;
1430 char psz_path[];
1431 };
1432
rdh_name_from_filename(const char * psz_filename)1433 static char *rdh_name_from_filename(const char *psz_filename)
1434 {
1435 /* remove leading white spaces */
1436 while (*psz_filename != '\0' && *psz_filename == ' ')
1437 psz_filename++;
1438
1439 char *psz_name = strdup(psz_filename);
1440 if (!psz_name)
1441 return NULL;
1442
1443 /* remove extension */
1444 char *psz_ptr = strrchr(psz_name, '.');
1445 if (psz_ptr && psz_ptr != psz_name)
1446 *psz_ptr = '\0';
1447
1448 /* remove trailing white spaces */
1449 int i = strlen(psz_name) - 1;
1450 while (psz_name[i] == ' ' && i >= 0)
1451 psz_name[i--] = '\0';
1452
1453 /* convert to lower case */
1454 psz_ptr = psz_name;
1455 while (*psz_ptr != '\0')
1456 {
1457 *psz_ptr = tolower(*psz_ptr);
1458 psz_ptr++;
1459 }
1460
1461 return psz_name;
1462 }
1463
rdh_get_slave_priority(input_item_t * p_item,input_item_slave_t * p_slave,const char * psz_slave_filename)1464 static uint8_t rdh_get_slave_priority(input_item_t *p_item,
1465 input_item_slave_t *p_slave,
1466 const char *psz_slave_filename)
1467 {
1468 uint8_t i_priority = SLAVE_PRIORITY_MATCH_NONE;
1469 char *psz_item_name = rdh_name_from_filename(p_item->psz_name);
1470 char *psz_slave_name = rdh_name_from_filename(psz_slave_filename);
1471
1472 if (!psz_item_name || !psz_slave_name)
1473 goto done;
1474
1475 size_t i_item_len = strlen(psz_item_name);
1476 size_t i_slave_len = strlen(psz_slave_name);
1477
1478 /* The slave name len should not be twice longer than the item name len. */
1479 if (i_item_len > i_slave_len || i_slave_len > 2 * i_item_len)
1480 goto done;
1481
1482 /* check if the names match exactly */
1483 if (!strcmp(psz_item_name, psz_slave_name))
1484 {
1485 i_priority = SLAVE_PRIORITY_MATCH_ALL;
1486 goto done;
1487 }
1488
1489 /* "cdg" slaves have to be a full match */
1490 if (p_slave->i_type == SLAVE_TYPE_SPU)
1491 {
1492 char *psz_ext = strrchr(psz_slave_name, '.');
1493 if (psz_ext != NULL && strcasecmp(++psz_ext, "cdg") == 0)
1494 goto done;
1495 }
1496
1497 /* check if the item name is a substring of the slave name */
1498 const char *psz_sub = strstr(psz_slave_name, psz_item_name);
1499
1500 if (psz_sub)
1501 {
1502 /* check if the item name was found at the end of the slave name */
1503 if (strlen(psz_sub + strlen(psz_item_name)) == 0)
1504 {
1505 i_priority = SLAVE_PRIORITY_MATCH_RIGHT;
1506 goto done;
1507 }
1508 else
1509 {
1510 i_priority = SLAVE_PRIORITY_MATCH_LEFT;
1511 goto done;
1512 }
1513 }
1514
1515 done:
1516 free(psz_item_name);
1517 free(psz_slave_name);
1518 return i_priority;
1519 }
1520
rdh_should_match_idx(struct vlc_readdir_helper * p_rdh,struct rdh_slave * p_rdh_sub)1521 static int rdh_should_match_idx(struct vlc_readdir_helper *p_rdh,
1522 struct rdh_slave *p_rdh_sub)
1523 {
1524 char *psz_ext = strrchr(p_rdh_sub->psz_filename, '.');
1525 if (!psz_ext)
1526 return false;
1527 psz_ext++;
1528
1529 if (strcasecmp(psz_ext, "sub") != 0)
1530 return false;
1531
1532 for (size_t i = 0; i < p_rdh->i_slaves; i++)
1533 {
1534 struct rdh_slave *p_rdh_slave = p_rdh->pp_slaves[i];
1535
1536 if (p_rdh_slave == NULL || p_rdh_slave == p_rdh_sub)
1537 continue;
1538
1539 /* check that priorities match */
1540 if (p_rdh_slave->p_slave->i_priority !=
1541 p_rdh_sub->p_slave->i_priority)
1542 continue;
1543
1544 /* check that the filenames without extension match */
1545 if (strncasecmp(p_rdh_sub->psz_filename, p_rdh_slave->psz_filename,
1546 strlen(p_rdh_sub->psz_filename) - 3 ) != 0)
1547 continue;
1548
1549 /* check that we have an idx file */
1550 char *psz_ext_idx = strrchr(p_rdh_slave->psz_filename, '.');
1551 if (psz_ext_idx == NULL)
1552 continue;
1553 psz_ext_idx++;
1554 if (strcasecmp(psz_ext_idx, "idx" ) == 0)
1555 return true;
1556 }
1557 return false;
1558 }
1559
rdh_attach_slaves(struct vlc_readdir_helper * p_rdh,input_item_node_t * p_parent_node)1560 static void rdh_attach_slaves(struct vlc_readdir_helper *p_rdh,
1561 input_item_node_t *p_parent_node)
1562 {
1563 if (p_rdh->i_sub_autodetect_fuzzy == 0)
1564 return;
1565
1566 /* Try to match slaves for each items of the node */
1567 for (int i = 0; i < p_parent_node->i_children; i++)
1568 {
1569 input_item_node_t *p_node = p_parent_node->pp_children[i];
1570 input_item_t *p_item = p_node->p_item;
1571
1572 enum slave_type unused;
1573 if (!input_item_IsMaster(p_item->psz_name)
1574 || input_item_slave_GetType(p_item->psz_name, &unused))
1575 continue; /* don't match 2 possible slaves between each others */
1576
1577 for (size_t j = 0; j < p_rdh->i_slaves; j++)
1578 {
1579 struct rdh_slave *p_rdh_slave = p_rdh->pp_slaves[j];
1580
1581 /* Don't try to match slaves with themselves or slaves already
1582 * attached with the higher priority */
1583 if (p_rdh_slave->p_node == p_node
1584 || p_rdh_slave->p_slave->i_priority == SLAVE_PRIORITY_MATCH_ALL)
1585 continue;
1586
1587 uint8_t i_priority =
1588 rdh_get_slave_priority(p_item, p_rdh_slave->p_slave,
1589 p_rdh_slave->psz_filename);
1590
1591 if (i_priority < p_rdh->i_sub_autodetect_fuzzy)
1592 continue;
1593
1594 /* Drop the ".sub" slave if a ".idx" slave matches */
1595 if (p_rdh_slave->p_slave->i_type == SLAVE_TYPE_SPU
1596 && rdh_should_match_idx(p_rdh, p_rdh_slave))
1597 continue;
1598
1599 input_item_slave_t *p_slave =
1600 input_item_slave_New(p_rdh_slave->p_slave->psz_uri,
1601 p_rdh_slave->p_slave->i_type,
1602 i_priority);
1603 if (p_slave == NULL)
1604 break;
1605
1606 if (input_item_AddSlave(p_item, p_slave) != VLC_SUCCESS)
1607 {
1608 input_item_slave_Delete(p_slave);
1609 break;
1610 }
1611
1612 /* Remove the corresponding node if any: This slave won't be
1613 * added in the parent node */
1614 if (p_rdh_slave->p_node != NULL)
1615 {
1616 input_item_node_RemoveNode(p_parent_node, p_rdh_slave->p_node);
1617 input_item_node_Delete(p_rdh_slave->p_node);
1618 p_rdh_slave->p_node = NULL;
1619 }
1620
1621 p_rdh_slave->p_slave->i_priority = i_priority;
1622 }
1623 }
1624
1625 /* Attach all children */
1626 for (int i = 0; i < p_parent_node->i_children; i++)
1627 rdh_attach_slaves(p_rdh, p_parent_node->pp_children[i]);
1628 }
1629
rdh_unflatten(struct vlc_readdir_helper * p_rdh,input_item_node_t ** pp_node,const char * psz_path,int i_net)1630 static int rdh_unflatten(struct vlc_readdir_helper *p_rdh,
1631 input_item_node_t **pp_node, const char *psz_path,
1632 int i_net)
1633 {
1634 /* Create an input input for each sub folders that is contained in the full
1635 * path. Update pp_node to point to the direct parent of the future item to
1636 * add. */
1637
1638 assert(psz_path != NULL);
1639 const char *psz_subpaths = psz_path;
1640
1641 while ((psz_subpaths = strchr(psz_subpaths, '/')))
1642 {
1643 input_item_node_t *p_subnode = NULL;
1644
1645 /* Check if this sub folder item was already added */
1646 for (size_t i = 0; i < p_rdh->i_dirs && p_subnode == NULL; i++)
1647 {
1648 struct rdh_dir *rdh_dir = p_rdh->pp_dirs[i];
1649 if (!strncmp(rdh_dir->psz_path, psz_path, psz_subpaths - psz_path))
1650 p_subnode = rdh_dir->p_node;
1651 }
1652
1653 /* The sub folder item doesn't exist, so create it */
1654 if (p_subnode == NULL)
1655 {
1656 size_t i_sub_path_len = psz_subpaths - psz_path;
1657 struct rdh_dir *p_rdh_dir =
1658 malloc(sizeof(struct rdh_dir) + 1 + i_sub_path_len);
1659 if (p_rdh_dir == NULL)
1660 return VLC_ENOMEM;
1661 strncpy(p_rdh_dir->psz_path, psz_path, i_sub_path_len);
1662 p_rdh_dir->psz_path[i_sub_path_len] = 0;
1663
1664 const char *psz_subpathname = strrchr(p_rdh_dir->psz_path, '/');
1665 if (psz_subpathname != NULL)
1666 ++psz_subpathname;
1667 else
1668 psz_subpathname = p_rdh_dir->psz_path;
1669
1670 input_item_t *p_item =
1671 input_item_NewExt("vlc://nop", psz_subpathname, -1,
1672 ITEM_TYPE_DIRECTORY, i_net);
1673 if (p_item == NULL)
1674 {
1675 free(p_rdh_dir);
1676 return VLC_ENOMEM;
1677 }
1678 input_item_CopyOptions(p_item, (*pp_node)->p_item);
1679 *pp_node = input_item_node_AppendItem(*pp_node, p_item);
1680 input_item_Release(p_item);
1681 if (*pp_node == NULL)
1682 {
1683 free(p_rdh_dir);
1684 return VLC_ENOMEM;
1685 }
1686 p_rdh_dir->p_node = *pp_node;
1687 TAB_APPEND(p_rdh->i_dirs, p_rdh->pp_dirs, p_rdh_dir);
1688 }
1689 else
1690 *pp_node = p_subnode;
1691 psz_subpaths++;
1692 }
1693 return VLC_SUCCESS;
1694 }
1695
1696 #undef vlc_readdir_helper_init
vlc_readdir_helper_init(struct vlc_readdir_helper * p_rdh,vlc_object_t * p_obj,input_item_node_t * p_node)1697 void vlc_readdir_helper_init(struct vlc_readdir_helper *p_rdh,
1698 vlc_object_t *p_obj, input_item_node_t *p_node)
1699 {
1700 /* Read options from the parent item. This allows vlc_stream_ReadDir()
1701 * users to specify options whitout touhing any vlc_object_t. Apply options
1702 * on a temporary object in order to not apply options (that can be
1703 * insecure) to the current object. */
1704 vlc_object_t *p_var_obj = vlc_object_create(p_obj, sizeof(vlc_object_t));
1705 if (p_var_obj != NULL)
1706 {
1707 input_item_ApplyOptions(p_var_obj, p_node->p_item);
1708 p_obj = p_var_obj;
1709 }
1710
1711 p_rdh->p_node = p_node;
1712 p_rdh->b_show_hiddenfiles = var_InheritBool(p_obj, "show-hiddenfiles");
1713 p_rdh->psz_ignored_exts = var_InheritString(p_obj, "ignore-filetypes");
1714 bool b_autodetect = var_InheritBool(p_obj, "sub-autodetect-file");
1715 p_rdh->i_sub_autodetect_fuzzy = !b_autodetect ? 0 :
1716 var_InheritInteger(p_obj, "sub-autodetect-fuzzy");
1717 p_rdh->b_flatten = var_InheritBool(p_obj, "extractor-flatten");
1718 TAB_INIT(p_rdh->i_slaves, p_rdh->pp_slaves);
1719 TAB_INIT(p_rdh->i_dirs, p_rdh->pp_dirs);
1720
1721 if (p_var_obj != NULL)
1722 vlc_object_release(p_var_obj);
1723 }
1724
vlc_readdir_helper_finish(struct vlc_readdir_helper * p_rdh,bool b_success)1725 void vlc_readdir_helper_finish(struct vlc_readdir_helper *p_rdh, bool b_success)
1726 {
1727 if (b_success)
1728 {
1729 rdh_sort(p_rdh->p_node);
1730 rdh_attach_slaves(p_rdh, p_rdh->p_node);
1731 }
1732 free(p_rdh->psz_ignored_exts);
1733
1734 /* Remove unmatched slaves */
1735 for (size_t i = 0; i < p_rdh->i_slaves; i++)
1736 {
1737 struct rdh_slave *p_rdh_slave = p_rdh->pp_slaves[i];
1738 if (p_rdh_slave != NULL)
1739 {
1740 input_item_slave_Delete(p_rdh_slave->p_slave);
1741 free(p_rdh_slave->psz_filename);
1742 free(p_rdh_slave);
1743 }
1744 }
1745 TAB_CLEAN(p_rdh->i_slaves, p_rdh->pp_slaves);
1746
1747 for (size_t i = 0; i < p_rdh->i_dirs; i++)
1748 free(p_rdh->pp_dirs[i]);
1749 TAB_CLEAN(p_rdh->i_dirs, p_rdh->pp_dirs);
1750 }
1751
vlc_readdir_helper_additem(struct vlc_readdir_helper * p_rdh,const char * psz_uri,const char * psz_flatpath,const char * psz_filename,int i_type,int i_net)1752 int vlc_readdir_helper_additem(struct vlc_readdir_helper *p_rdh,
1753 const char *psz_uri, const char *psz_flatpath,
1754 const char *psz_filename, int i_type, int i_net)
1755 {
1756 enum slave_type i_slave_type;
1757 struct rdh_slave *p_rdh_slave = NULL;
1758 assert(psz_flatpath || psz_filename);
1759
1760 if (!p_rdh->b_flatten)
1761 {
1762 if (psz_filename == NULL)
1763 {
1764 psz_filename = strrchr(psz_flatpath, '/');
1765 if (psz_filename != NULL)
1766 ++psz_filename;
1767 else
1768 psz_filename = psz_flatpath;
1769 }
1770 }
1771 else
1772 {
1773 if (psz_filename == NULL)
1774 psz_filename = psz_flatpath;
1775 psz_flatpath = NULL;
1776 }
1777
1778 if (p_rdh->i_sub_autodetect_fuzzy != 0
1779 && input_item_slave_GetType(psz_filename, &i_slave_type))
1780 {
1781 p_rdh_slave = malloc(sizeof(*p_rdh_slave));
1782 if (!p_rdh_slave)
1783 return VLC_ENOMEM;
1784
1785 p_rdh_slave->p_node = NULL;
1786 p_rdh_slave->psz_filename = strdup(psz_filename);
1787 p_rdh_slave->p_slave = input_item_slave_New(psz_uri, i_slave_type,
1788 SLAVE_PRIORITY_MATCH_NONE);
1789 if (!p_rdh_slave->p_slave || !p_rdh_slave->psz_filename)
1790 {
1791 free(p_rdh_slave->psz_filename);
1792 free(p_rdh_slave);
1793 return VLC_ENOMEM;
1794 }
1795
1796 TAB_APPEND(p_rdh->i_slaves, p_rdh->pp_slaves, p_rdh_slave);
1797 }
1798
1799 if (rdh_file_is_ignored(p_rdh, psz_filename))
1800 return VLC_SUCCESS;
1801
1802 input_item_node_t *p_node = p_rdh->p_node;
1803
1804 if (psz_flatpath != NULL)
1805 {
1806 int i_ret = rdh_unflatten(p_rdh, &p_node, psz_flatpath, i_net);
1807 if (i_ret != VLC_SUCCESS)
1808 return i_ret;
1809 }
1810
1811 input_item_t *p_item = input_item_NewExt(psz_uri, psz_filename, -1, i_type,
1812 i_net);
1813 if (p_item == NULL)
1814 return VLC_ENOMEM;
1815
1816 input_item_CopyOptions(p_item, p_node->p_item);
1817 p_node = input_item_node_AppendItem(p_node, p_item);
1818 input_item_Release(p_item);
1819 if (p_node == NULL)
1820 return VLC_ENOMEM;
1821
1822 /* A slave can also be an item. If there is a match, this item will be
1823 * removed from the parent node. This is not a common case, since most
1824 * slaves will be ignored by rdh_file_is_ignored() */
1825 if (p_rdh_slave != NULL)
1826 p_rdh_slave->p_node = p_node;
1827 return VLC_SUCCESS;
1828 }
1829