]> git.sesse.net Git - vlc/blob - modules/media_library/sql_media_library.c
FreeType: mark some options as safe
[vlc] / modules / media_library / sql_media_library.c
1 /*****************************************************************************
2  * sql_media_library.c: SQL-based media library
3  *****************************************************************************
4  * Copyright (C) 2008-2010 the VideoLAN Team and AUTHORS
5  * $Id$
6  *
7  * Authors: Antoine Lejeune <phytos@videolan.org>
8  *          Jean-Philippe André <jpeg@videolan.org>
9  *          Rémi Duraffort <ivoire@videolan.org>
10  *          Adrien Maglo <magsoft@videolan.org>
11  *          Srikanth Raju <srikiraju at gmail dot com>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #include "sql_media_library.h"
29
30 static const char* ppsz_AudioExtensions[] = { EXTENSIONS_AUDIO_CSV, NULL };
31 static const char* ppsz_VideoExtensions[] = { EXTENSIONS_VIDEO_CSV, NULL };
32
33 #define MEDIA_LIBRARY_PATH_TEXT N_( "Filename of the SQLite database" )
34 #define MEDIA_LIBRARY_PATH_LONGTEXT N_( "Path to the file containing " \
35                                         "the SQLite database" )
36
37 #define IGNORE_TEXT N_( "Ignored extensions in the media library" )
38 #define IGNORE_LONGTEXT N_( "Files with these extensions will not be added to"\
39                             " the media library when scanning directories." )
40
41 #define RECURSIVE_TEXT N_( "Subdirectory recursive scanning" )
42 #define RECURSIVE_LONGTEXT N_( "When scanning a directory, scan also all its"\
43         " subdirectories." )
44
45
46
47 /*****************************************************************************
48  * Static functions
49  *****************************************************************************/
50
51 /* Module entry point and exit point */
52 static int load( vlc_object_t* );
53 static void unload( vlc_object_t* );
54
55 static int CreateInputItemFromMedia( media_library_t *p_ml,
56                               input_item_t **pp_item,
57                               ml_media_t *p_media );
58
59
60 struct ml_table_elt
61 {
62     int column_id;
63     char column_name[];
64 };
65
66 static const struct ml_table_elt ml_table_map[]=
67 {
68     { ML_ALBUM_COVER,   "album_cover" },
69     { ML_ALBUM_ID,          "album_id" },
70     { ML_ALBUM,         "album_title" },
71     { ML_COMMENT,           "comment" },
72     { ML_COVER,         "cover" },
73     { ML_DIRECTORY,         "directory_id" },
74     { ML_DIRECTORY,         "directory_id" },
75     { ML_DISC_NUMBER,   "disc" },
76     { ML_DURATION,          "duration" },
77     { ML_EXTRA,         "extra" },
78     { ML_FILESIZE,          "filesize" },
79     { ML_FIRST_PLAYED,  "first_played" },
80     { ML_GENRE,         "genre" },
81     { ML_ID,            "id" },
82     { ML_IMPORT_TIME,   "import_time" },
83     { ML_LANGUAGE,          "language" },
84     { ML_LAST_PLAYED,   "last_played" },
85     { ML_LAST_SKIPPED,  "last_skipped" },
86     { ML_ORIGINAL_TITLE,"original_title" },
87     { ML_PEOPLE_ID,         "people_id" },
88     { ML_PEOPLE,            "people_name" },
89     { ML_PEOPLE_ROLE,   "people_role" },
90     { ML_PLAYED_COUNT,  "played_count" },
91     { ML_PREVIEW,           "preview" },
92     { ML_SCORE,         "score" },
93     { ML_SKIPPED_COUNT, "skipped_count" },
94     { ML_TITLE,         "title" },
95     { ML_TRACK_NUMBER,  "track" },
96     { ML_TYPE,          "type" },
97     { ML_URI,           "uri" },
98     { ML_VOTE,          "vote" },
99     { ML_YEAR,          "year" }
100 };
101
102 /*****************************************************************************
103  * Module description
104  *****************************************************************************/
105 vlc_module_begin()
106     set_shortname( "Media Library" )
107     set_description( _( "Media Library based on a SQL based database" ) )
108     set_capability( "media-library", 1 )
109     set_callbacks( load, unload )
110     set_category( CAT_ADVANCED )
111     set_subcategory( SUBCAT_ADVANCED_MISC )
112     add_string( "ml-filename", "vlc-media-library.db",
113             MEDIA_LIBRARY_PATH_TEXT, MEDIA_LIBRARY_PATH_LONGTEXT, false )
114     add_string( "ml-username", "",  N_( "Username for the database" ),
115             N_( "Username for the database" ), false )
116     add_string( "ml-password", "",  N_( "Password for the database" ),
117             N_( "Password for the database" ), false )
118     add_integer( "ml-port", 0,
119             N_( "Port for the database" ), N_("Port for the database"), false )
120     add_bool( "ml-recursive-scan", true, RECURSIVE_TEXT,
121             RECURSIVE_LONGTEXT, false )
122     add_bool( "ml-auto-add", true,  N_("Auto add new medias"),
123             N_( "Automatically add new medias to ML" ), false )
124 vlc_module_end()
125
126
127 /**
128  * @brief Load module
129  * @param obj Parent object
130  */
131 static int load( vlc_object_t *obj )
132 {
133     msg_Dbg( obj, "loading media library module" );
134
135     media_library_t *p_ml = ( media_library_t * ) obj;
136     p_ml->p_sys = ( media_library_sys_t* )
137                         calloc( 1, sizeof( media_library_sys_t ) );
138     if( !p_ml->p_sys )
139         return VLC_ENOMEM;
140
141     p_ml->functions.pf_Find               = FindVa;
142     p_ml->functions.pf_FindAdv            = FindAdv;
143     p_ml->functions.pf_Control            = Control;
144     p_ml->functions.pf_InputItemFromMedia = GetInputItemFromMedia;
145     p_ml->functions.pf_Update             = Update;
146     p_ml->functions.pf_Delete             = Delete;
147     p_ml->functions.pf_GetMedia           = GetMedia;
148
149     vlc_mutex_init( &p_ml->p_sys->lock );
150
151     /* Initialise Sql module */
152     InitDatabase( p_ml );
153
154     /* Initialise the media pool */
155     ARRAY_INIT( p_ml->p_sys->mediapool );
156     vlc_mutex_init( &p_ml->p_sys->pool_mutex );
157
158     /* Create variables system */
159     var_Create( p_ml, "media-added", VLC_VAR_INTEGER );
160     var_Create( p_ml, "media-deleted", VLC_VAR_INTEGER );
161     var_Create( p_ml, "media-meta-change", VLC_VAR_INTEGER );
162
163     /* Launching the directory monitoring thread */
164     monitoring_thread_t *p_mon =
165             vlc_object_create( p_ml, sizeof( monitoring_thread_t ) );
166     if( !p_mon )
167     {
168         vlc_mutex_destroy( &p_ml->p_sys->lock );
169         sql_Destroy( p_ml->p_sys->p_sql );
170         free( p_ml->p_sys );
171         return VLC_ENOMEM;
172     }
173     p_ml->p_sys->p_mon = p_mon;
174
175     p_mon->p_ml = p_ml;
176
177     if( vlc_clone( &p_mon->thread, RunMonitoringThread, p_mon,
178                 VLC_THREAD_PRIORITY_LOW ) )
179     {
180         msg_Err( p_ml, "cannot spawn the media library monitoring thread" );
181         vlc_mutex_destroy( &p_ml->p_sys->lock );
182         sql_Destroy( p_ml->p_sys->p_sql );
183         free( p_ml->p_sys );
184         vlc_object_release( p_mon );
185         return VLC_EGENERIC;
186     }
187     /* Starting the watching system (starts a thread) */
188     watch_Init( p_ml );
189
190     msg_Dbg( p_ml, "Media library module loaded successfully" );
191
192     return VLC_SUCCESS;
193 }
194
195
196 /**
197  * @brief Unload module
198  *
199  * @param obj the media library object
200  * @return Nothing
201  */
202 static void unload( vlc_object_t *obj )
203 {
204     media_library_t *p_ml = ( media_library_t* ) obj;
205
206     /* Stopping the watching system */
207     watch_Close( p_ml );
208
209     /* Stop the monitoring thread */
210     vlc_cancel( p_ml->p_sys->p_mon->thread );
211     vlc_join( p_ml->p_sys->p_mon->thread, NULL );
212     vlc_object_release( p_ml->p_sys->p_mon );
213
214     /* Destroy the variable */
215     var_Destroy( p_ml, "media-meta-change" );
216     var_Destroy( p_ml, "media-deleted" );
217     var_Destroy( p_ml, "media-added" );
218
219     /* Empty the media pool */
220     ml_media_t* item;
221     FOREACH_ARRAY( item, p_ml->p_sys->mediapool )
222         ml_gc_decref( item );
223     FOREACH_END()
224     vlc_mutex_destroy( &p_ml->p_sys->pool_mutex );
225
226     sql_Destroy( p_ml->p_sys->p_sql );
227
228     vlc_mutex_destroy( &p_ml->p_sys->lock );
229
230     free( p_ml->p_sys );
231 }
232
233 /**
234  * @brief Get results of an SQL-Query on the database (please : free the result)
235  *
236  * @param p_ml the media library object
237  * @param ppp_res char *** in which to store the table of results (allocated)
238  * @param pi_rows resulting row number in table
239  * @param pi_cols resulting column number in table
240  * @param psz_fmt query command with printf-like format enabled
241  * @param va_args format the command
242  * @return VLC_SUCCESS or a VLC error code
243  */
244 int Query( media_library_t *p_ml,
245               char ***ppp_res, int *pi_rows, int *pi_cols,
246               const char *psz_fmt, ... )
247 {
248     va_list argp;
249     va_start( argp, psz_fmt );
250
251     int i_ret = QueryVa( p_ml, ppp_res, pi_rows, pi_cols, psz_fmt, argp );
252
253     va_end( argp );
254     return i_ret;
255 }
256
257 /**
258  * @brief Get results of an SQL-Query on the database (please : free the result)
259  *
260  * @param p_ml the media library object
261  * @param ppp_res char *** in which to store the table of results (allocated)
262  * @param pi_rows resulting row number in table
263  * @param pi_cols resulting column number in table
264  * @param psz_fmt query command with printf-like format enabled
265  * @param va_args format the command
266  * @return VLC_SUCCESS or a VLC error code
267  */
268 int QueryVa( media_library_t *p_ml, char ***ppp_res,
269                       int *pi_rows, int *pi_cols, const char *psz_fmt,
270                       va_list argp )
271 {
272     assert( p_ml );
273     if( !ppp_res || !psz_fmt ) return VLC_EGENERIC;
274
275     char *psz_query = sql_VPrintf( p_ml->p_sys->p_sql, psz_fmt, argp );
276     if( !psz_query )
277         return VLC_ENOMEM;
278
279     int i_ret = sql_Query( p_ml->p_sys->p_sql, psz_query,
280                            ppp_res, pi_rows, pi_cols );
281
282     free( psz_query );
283     return i_ret;
284 }
285
286 /**
287  * @brief Do a SQL-query without any data coming back
288  *
289  * @param p_ml the media library object
290  * @param psz_fmt query command with printf-like format enabled
291  * @param va_args format the command
292  * @return VLC_SUCCESS or a VLC error code
293  */
294 int QuerySimple( media_library_t *p_ml,
295                     const char *psz_fmt, ... )
296 {
297     va_list argp;
298     va_start( argp, psz_fmt );
299
300     int i_ret = QuerySimpleVa( p_ml, psz_fmt, argp );
301
302     va_end( argp );
303     return i_ret;
304 }
305
306 /**
307  * @brief Do a SQL-query without any data coming back
308  *
309  * @param p_ml the media library object
310  * @param psz_fmt query command with printf-like format enabled
311  * @param argp format the command
312  * @return VLC_SUCCESS or a VLC error code
313  */
314 int QuerySimpleVa( media_library_t *p_ml,
315                       const char *psz_fmt, va_list argp )
316 {
317     assert( p_ml );
318
319     int i_ret = VLC_SUCCESS;
320     int i_rows, i_cols;
321     char **pp_results = NULL;
322
323     i_ret = QueryVa( p_ml, &pp_results, &i_rows, &i_cols, psz_fmt, argp );
324
325     FreeSQLResult( p_ml, pp_results );
326     va_end( argp );
327
328     return i_ret;
329 }
330
331 /**
332  * @brief Transforms a string to a ml_result_t, with given type and id (as psz)
333  *
334  * @param res the result of the function
335  * @param psz string to transform into a result
336  * @param psz_id id as a string
337  * @param result_type type of the result
338  * @return ID or a VLC error code
339  */
340 int StringToResult( ml_result_t *p_result, const char *psz,
341                     const char *psz_id, ml_result_type_e result_type )
342 {
343     memset( &p_result->value, 0, sizeof( p_result->value ) );
344
345     p_result->id = psz_id ? atoi( psz_id ) : 0;
346     p_result->type = result_type;
347
348     switch( result_type )
349     {
350         case ML_TYPE_INT:
351             p_result->value.i = psz ? atoi( psz ) : 0;
352             break;
353
354         case ML_TYPE_TIME:
355             p_result->value.time = psz ? ( mtime_t ) atoi( psz )
356                                        : ( mtime_t ) 0LL;
357             break;
358
359         case ML_TYPE_PSZ:
360             p_result->value.psz = psz ? strdup( psz ) : NULL;
361             break;
362
363         case ML_TYPE_MEDIA:
364         default:
365             /* This is an error */
366             return VLC_EGENERIC;
367     }
368
369     return p_result->id;
370 }
371
372
373 /**
374  * @brief fills an ml_result_array_t with result of an SQL query
375  *
376  * @param p_ml the media library object
377  * @param p_media ml_result_array_t object to fill
378  * @param pp_results result of sql query
379  * @param i_rows row number
380  * @param i_cols column number
381  * @param result_type type of the result
382  * @return VLC_SUCCESS or a VLC error code
383  **/
384 int SQLToResultArray( media_library_t *p_ml, vlc_array_t *p_result_array,
385                       char **pp_results, int i_rows, int i_cols,
386                       ml_result_type_e result_type )
387 {
388     assert( p_ml );
389     if( !p_result_array )
390         return VLC_EGENERIC;
391     if( i_cols == 0 )   /* No result */
392         return VLC_SUCCESS;
393     if( i_cols < 0 )
394     {
395         msg_Err( p_ml, "negative number of columns in result ?" );
396         return VLC_EGENERIC;
397     }
398
399     if( i_cols == 1 )
400     {
401         for( int i = 1; i <= i_rows; i++ )
402         {
403             ml_result_t *res = ( ml_result_t* )
404                                     calloc( 1, sizeof( ml_result_t ) );
405             if( !res )
406                 return VLC_ENOMEM;
407             StringToResult( res, pp_results[ i ], NULL, result_type );
408             vlc_array_append( p_result_array, res );
409         }
410     }
411     /* FIXME?: Assuming all double column results are id - result pairs */
412     else if( ( i_cols == 2 ) )
413     {
414         for( int i = 1; i <= i_rows; i++ )
415         {
416             ml_result_t *res = ( ml_result_t* )
417                                     calloc( 1, sizeof( ml_result_t ) );
418             if( !res )
419                 return VLC_ENOMEM;
420             StringToResult( res, pp_results[ i * 2 + 1], pp_results[ i * 2 ],
421                             result_type );
422             vlc_array_append( p_result_array, res );
423         }
424     }
425     else if( result_type == ML_TYPE_MEDIA )
426     {
427         return SQLToMediaArray( p_ml, p_result_array,
428                                 pp_results, i_rows, i_cols );
429     }
430     else
431     {
432         msg_Err( p_ml, "unable to convert SQL result to a ml_result_t array" );
433         return VLC_EGENERIC;
434     }
435     return VLC_SUCCESS;
436 }
437
438
439 /**
440  * @brief fills a vlc_array_t with results of an SQL query
441  *        medias in ml_result_t
442  *
443  * @param p_ml the media library object
444  * @param p_array array to fill with ml_media_t elements (might be initialized)
445  * @param pp_results result of sql query
446  * @param i_rows row number
447  * @param i_cols column number
448  * @return VLC_SUCCESS or a VLC error code
449  * Warning: this returns VLC_EGENERIC if i_rows == 0 (empty result)
450  **/
451 int SQLToMediaArray( media_library_t *p_ml, vlc_array_t *p_result_array,
452                      char **pp_results, int i_rows, int i_cols )
453 {
454     int i_ret = VLC_SUCCESS;
455     assert( p_ml );
456
457     #define res( i, j ) ( pp_results[ i * i_cols + j ] )
458     #define atoinull( a ) ( (a) ? atoi( a ) : 0 )
459     #define strdupnull( a ) ( (a) ? strdup( a ) : NULL )
460
461     if( i_rows == 0 )
462         return VLC_EGENERIC;
463
464     if( !p_result_array || !pp_results || i_rows < 0 || i_cols <= 0 )
465     {
466         msg_Warn( p_ml, "bad arguments (%s:%d)", __FILE__, __LINE__ );
467         return VLC_EGENERIC;
468     }
469
470     vlc_array_t* p_intermediate_array = vlc_array_new();
471
472     /* Analyze first row */
473     int *indexes = ( int* ) calloc( i_cols + 1, sizeof( int ) );
474     if( !indexes )
475         return VLC_ENOMEM;
476
477     const int count = sizeof( ml_table_map )/ sizeof( struct ml_table_elt );
478     for( int col = 0; col < i_cols; col++ )
479     {
480         //binary search
481         int low = 0, high = count - 1;
482         int answer = -1;
483         while( low <= high ) {
484             int mid = (low + high ) / 2;
485             char* mid_val = ml_table_map[mid].column_name;
486             int cmp = strcmp( mid_val, res( 0, col ) );
487             if( cmp > 0 )
488                 low = mid + 1;
489             else if ( cmp < 0 )
490                 high = mid - 1;
491             else
492             {
493                 answer = mid;  break;
494             }
495         }
496         if( answer == -1 )
497             msg_Warn( p_ml, "unknown column: %s", res( 0, col ) );
498         else
499             indexes[col] = ml_table_map[answer].column_id;
500     }
501
502     /* Read rows 1 to i_rows */
503     ml_media_t  *p_media  = NULL;
504     ml_result_t *p_result = NULL;
505
506     for( int row = 1; ( row <= i_rows ) && ( i_ret == VLC_SUCCESS ); row++ )
507     {
508         p_media = media_New( p_ml, 0, ML_MEDIA, false );
509         if( !p_media )
510         {
511             free( indexes );
512             return VLC_ENOMEM;
513         }
514         p_result = ( ml_result_t * ) calloc( 1, sizeof( ml_result_t ) );
515         if( !p_result )
516         {
517             ml_gc_decref( p_media );
518             free( indexes );
519             return VLC_ENOMEM;
520         }
521
522         char* psz_append_pname = NULL;
523         char* psz_append_prole = NULL;
524         int i_append_pid = 0;
525
526 #define SWITCH_INT( key, value ) case key: \
527         p_media-> value = atoinull( res( row, col ) );
528 #define SWITCH_PSZ( key, value ) case key: \
529         p_media-> value = strdupnull( res( row, col ) );
530
531         ml_LockMedia( p_media );
532         for( int col = 0; ( col < i_cols ) && ( i_ret == VLC_SUCCESS ); col++ )
533         {
534             switch( indexes[ col ] )
535             {
536                 SWITCH_INT( ML_ALBUM_ID, i_album_id );
537                 SWITCH_PSZ( ML_ALBUM, psz_album );
538                 SWITCH_PSZ( ML_COMMENT, psz_comment );
539                 SWITCH_INT( ML_DISC_NUMBER, i_disc_number );
540                 SWITCH_INT( ML_DURATION, i_duration );
541                 SWITCH_PSZ( ML_EXTRA, psz_extra );
542                 SWITCH_INT( ML_FILESIZE, i_filesize );
543                 SWITCH_INT( ML_FIRST_PLAYED, i_first_played );
544                 SWITCH_PSZ( ML_GENRE, psz_genre);
545                 SWITCH_INT( ML_IMPORT_TIME, i_import_time );
546                 SWITCH_PSZ( ML_LANGUAGE, psz_language );
547                 SWITCH_INT( ML_LAST_PLAYED, i_last_played );
548                 SWITCH_INT( ML_LAST_SKIPPED, i_last_skipped );
549                 SWITCH_PSZ( ML_ORIGINAL_TITLE, psz_orig_title );
550                 SWITCH_INT( ML_PLAYED_COUNT, i_played_count );
551                 SWITCH_PSZ( ML_PREVIEW, psz_preview );
552                 SWITCH_INT( ML_SCORE, i_score );
553                 SWITCH_INT( ML_SKIPPED_COUNT, i_skipped_count );
554                 SWITCH_PSZ( ML_TITLE, psz_title );
555                 SWITCH_INT( ML_TRACK_NUMBER, i_track_number );
556                 SWITCH_INT( ML_TYPE, i_type );
557                 SWITCH_INT( ML_VOTE, i_vote);
558                 SWITCH_INT( ML_YEAR, i_year );
559             case ML_ALBUM_COVER:
560                 /* See ML_COVER */
561                 // Discard attachment://
562                 if( !p_media->psz_cover || !*p_media->psz_cover
563                  || !strncmp( p_media->psz_cover, "attachment://", 13 ) )
564                 {
565                     free( p_media->psz_cover );
566                     p_media->psz_cover = strdupnull( res( row, col ) );
567                 }
568                 break;
569             case ML_PEOPLE:
570                 psz_append_pname = strdupnull( res( row, col ) );
571                 break;
572             case ML_PEOPLE_ID:
573                 i_append_pid = atoinull( res( row, col ) );
574                 break;
575             case ML_PEOPLE_ROLE:
576                 psz_append_prole = strdupnull( res( row, col ) );
577                 break;
578             case ML_COVER:
579                 /* See ML_ALBUM_COVER */
580                 if( !p_media->psz_cover || !*p_media->psz_cover
581                      || !strncmp( p_media->psz_cover, "attachment://", 13 ) )
582                 {
583                     free( p_media->psz_cover );
584                     p_media->psz_cover = strdupnull( res( row, col ) );
585                 }
586                 break;
587             case ML_ID:
588                 p_media->i_id = atoinull( res( row, col ) );
589                 if( p_media->i_id <= 0 )
590                     msg_Warn( p_ml, "entry with id null or inferior to zero" );
591                 break;
592             case ML_URI:
593                 p_media->psz_uri = strdupnull( res( row, col ) );
594                 if( !p_media->psz_uri )
595                     msg_Warn( p_ml, "entry without uri" );
596                 break;
597             case ML_DIRECTORY:
598                 break; // The column directory_id is'nt part of the media model
599             default:
600                 msg_Warn( p_ml, "unknown element, row %d column %d (of %d) - %s - %s",
601                         row, col, i_cols, res( 0 , col ), res( row, col ) );
602                 break;
603             }
604         }
605
606 #undef SWITCH_INT
607 #undef SWITCH_PSZ
608         int i_appendrow;
609         ml_result_t* p_append = NULL;
610         for( i_appendrow = 0; i_appendrow < vlc_array_count( p_intermediate_array ); i_appendrow++ )
611         {
612             p_append = ( ml_result_t* )
613                 vlc_array_item_at_index( p_intermediate_array, i_appendrow );
614             if( p_append->id == p_media->i_id )
615                 break;
616         }
617         if( i_appendrow == vlc_array_count( p_intermediate_array ) )
618         {
619             p_result->id   = p_media->i_id;
620             p_result->type = ML_TYPE_MEDIA;
621             p_result->value.p_media = p_media;
622             if( psz_append_pname && i_append_pid && psz_append_prole )
623                 ml_CreateAppendPersonAdv( &(p_result->value.p_media->p_people),
624                         psz_append_prole, psz_append_pname, i_append_pid );
625             vlc_array_append( p_intermediate_array, p_result );
626             ml_UnlockMedia( p_media );
627         }
628         else /* This is a repeat row and the people need to be put together */
629         {
630             ml_LockMedia( p_append->value.p_media );
631             if( psz_append_pname && i_append_pid && psz_append_prole )
632                 ml_CreateAppendPersonAdv( &(p_append->value.p_media->p_people),
633                         psz_append_prole, psz_append_pname, i_append_pid );
634             ml_UnlockMedia( p_append->value.p_media );
635             ml_UnlockMedia( p_media );
636             ml_gc_decref( p_media );
637         }
638         FREENULL( psz_append_prole );
639         FREENULL( psz_append_pname );
640         i_append_pid = 0;
641     }
642     p_media = NULL;
643     free( indexes );
644
645     /* Now check if these medias are already on the pool, and sync */
646     for( int i = 0; i < vlc_array_count( p_intermediate_array ); i++ )
647     {
648         p_result =
649             ( ml_result_t* )vlc_array_item_at_index( p_intermediate_array, i );
650         p_media = p_result->value.p_media;
651         ml_media_t* p_poolmedia = pool_GetMedia( p_ml, p_result->id );
652         /* TODO: Pool_syncMedia might be cleaner? */
653
654         p_result = ( ml_result_t* ) calloc( 1, sizeof( ml_result_t * ) );
655         if( !p_result )
656             goto quit_sqlmediaarray;
657         if( p_poolmedia )
658         {
659             /* TODO: This might cause some weird stuff to occur w/ GC? */
660             ml_CopyMedia( p_poolmedia, p_media );
661             p_result->id = p_poolmedia->i_id;
662             p_result->type = ML_TYPE_MEDIA;
663             p_result->value.p_media = p_poolmedia;
664             vlc_array_append( p_result_array, p_result );
665         }
666         else
667         {
668             i_ret = pool_InsertMedia( p_ml, p_media, false );
669             if( i_ret == VLC_SUCCESS )
670             {
671                 ml_gc_incref( p_media );
672                 p_result->id = p_media->i_id;
673                 p_result->type = ML_TYPE_MEDIA;
674                 p_result->value.p_media = p_media;
675                 vlc_array_append( p_result_array, p_result );
676             }
677         }
678     }
679
680     #undef strdupnull
681     #undef atoinull
682     #undef res
683 quit_sqlmediaarray:
684     for( int k = 0; k < vlc_array_count( p_intermediate_array ); k++ )
685     {
686         ml_result_t* temp = ((ml_result_t*)vlc_array_item_at_index( p_intermediate_array, k ));
687         ml_FreeResult( temp );
688     }
689     vlc_array_destroy( p_intermediate_array );
690     return i_ret;
691 }
692
693
694 /**
695  * @brief Returns (unique) ID of media with specified URI
696  *
697  * @param p_ml the media library object
698  * @param psz_uri URI to look for
699  * @return i_id: (first) ID found, VLC_EGENERIC in case of error
700  * NOTE: Normally, there should not be more than one ID with one URI
701  */
702 int GetMediaIdOfURI( media_library_t *p_ml, const char *psz_uri )
703 {
704     int i_ret = VLC_EGENERIC;
705     vlc_array_t *p_array = vlc_array_new();
706     i_ret = Find( p_ml, p_array, ML_ID, ML_URI, psz_uri, ML_LIMIT, 1, ML_END );
707     if( ( i_ret == VLC_SUCCESS )
708         && ( vlc_array_count( p_array ) > 0 )
709         && vlc_array_item_at_index( p_array, 0 ) )
710     {
711         i_ret = ( (ml_result_t*)vlc_array_item_at_index( p_array, 0 ) )
712                         ->value.i;
713     }
714     else
715     {
716         i_ret = VLC_EGENERIC;
717     }
718     vlc_array_destroy( p_array );
719     return i_ret;
720 }
721
722
723 /**
724  * @brief Control function for media library
725  *
726  * @param p_ml Media library handle
727  * @param i_query query type
728  * @param args query arguments
729  * @return VLC_SUCCESS if ok
730  */
731 int Control( media_library_t *p_ml, int i_query, va_list args )
732 {
733     switch( i_query )
734     {
735     case ML_ADD_INPUT_ITEM:
736     {
737         input_item_t *p_item = (input_item_t *)va_arg( args, input_item_t * );
738         return AddInputItem( p_ml, p_item );
739     }
740
741     case ML_ADD_PLAYLIST_ITEM:
742     {
743         playlist_item_t *p_item = (playlist_item_t *)va_arg( args, playlist_item_t * );
744         return AddPlaylistItem( p_ml, p_item );
745     }
746
747     case ML_ADD_MONITORED:
748     {
749         char *psz_dir = (char *)va_arg( args, char * );
750         return AddDirToMonitor( p_ml, psz_dir );
751     }
752
753     case ML_GET_MONITORED:
754     {
755         vlc_array_t *p_array = (vlc_array_t *)va_arg( args, vlc_array_t * );
756         return ListMonitoredDirs( p_ml, p_array );
757     }
758
759     case ML_DEL_MONITORED:
760     {
761         char *psz_dir = (char *)va_arg( args, char * );
762         return RemoveDirToMonitor( p_ml, psz_dir );
763     }
764
765     default:
766         return VLC_EGENERIC;
767     }
768 }
769
770
771 /**
772  * @brief Create a new (empty) database. The database might be initialized
773  *
774  * @param p_ml This ML
775  * @return VLC_SUCCESS or VLC_EGENERIC
776  * @note This function is transactional
777  */
778 int CreateEmptyDatabase( media_library_t *p_ml )
779 {
780     assert( p_ml );
781     int i_ret = VLC_SUCCESS;
782     msg_Dbg( p_ml, "creating a new (empty) database" );
783
784     Begin( p_ml );
785
786     /* Albums */
787     i_ret= QuerySimple( p_ml,
788                         "CREATE TABLE album ( "
789                         "id INTEGER PRIMARY KEY,"
790                         "album_artist_id INTEGER,"
791                         "title VARCHAR(1024),"
792                         "cover VARCHAR(1024) )" );
793     if( i_ret != VLC_SUCCESS )
794         goto quit_createemptydatabase;
795
796     /* Add "unknown" entry to albums */
797     i_ret = QuerySimple( p_ml,
798                         "INSERT INTO album ( id, title, cover, album_artist_id ) "
799                         "VALUES ( 0, 'Unknown', '', 0 )" );
800
801     if( i_ret != VLC_SUCCESS )
802         goto quit_createemptydatabase;
803
804     /* Main media table */
805     i_ret= QuerySimple( p_ml,
806                         "CREATE TABLE media ( "
807                         "id INTEGER PRIMARY KEY,"
808                         "timestamp INTEGER,"            /* File timestamp */
809                         "uri VARCHAR(1024),"
810                         "type INTEGER,"
811                         "title VARCHAR(1024),"
812                         "original_title VARCHAR(1024),"
813                         "album_id INTEGER,"
814                         "cover VARCHAR(1024),"
815                         "preview VARCHAR(1024),"        /* Video preview */
816                         "track INTEGER,"                /* Track number */
817                         "disc INTEGER,"                 /* Disc number */
818                         "year INTEGER,"
819                         "genre VARCHAR(1024),"
820                         "vote INTEGER,"                 /* Rating/Stars */
821                         "score INTEGER,"                /* ML score/rating */
822                         "comment VARCHAR(1024),"        /* Comment */
823                         "filesize INTEGER,"
824                         /* Dates and times */
825                         "duration INTEGER,"             /* Length of media */
826                         "played_count INTEGER,"
827                         "last_played DATE,"
828                         "first_played DATE,"
829                         "import_time DATE,"
830                         "skipped_count INTEGER,"
831                         "last_skipped DATE,"
832                         "directory_id INTEGER,"
833                         "CONSTRAINT associated_album FOREIGN KEY(album_id) "
834             "REFERENCES album(id) ON DELETE SET DEFAULT ON UPDATE RESTRICT)" );
835     if( i_ret != VLC_SUCCESS )
836         goto quit_createemptydatabase;
837
838     /* People */
839     i_ret = QuerySimple( p_ml,
840                         "CREATE TABLE people ( "
841                         "id INTEGER PRIMARY KEY,"
842                         "name VARCHAR(1024) ,"
843                         "role VARCHAR(1024) )" );
844     if( i_ret != VLC_SUCCESS )
845         goto quit_createemptydatabase;
846
847     /* Media to people */
848     i_ret = QuerySimple( p_ml,
849                         "CREATE TABLE media_to_people ( "
850                         "media_id INTEGER, "
851                         "people_id INTEGER, "
852                         "PRIMARY KEY( media_id, people_id ), "
853                         "CONSTRAINT associated_people FOREIGN KEY(people_id) "
854             "REFERENCES people(id) ON DELETE SET DEFAULT ON UPDATE RESTRICT, "
855                         "CONSTRAINT associated_media FOREIGN KEY(media_id) "
856             "REFERENCES media(id) ON DELETE CASCADE ON UPDATE RESTRICT )" );
857     if( i_ret != VLC_SUCCESS )
858         goto quit_createemptydatabase;
859
860     /* Add "unknown" entry to people */
861     i_ret = QuerySimple( p_ml,
862                         "INSERT INTO people ( id, name, role ) "
863                         "VALUES ( 0, 'Unknown', NULL )" );
864     if( i_ret != VLC_SUCCESS )
865         goto quit_createemptydatabase;
866
867     /* recursive is set to 1 if the directory is added to the database
868        by recursion and 0 if not */
869     i_ret = QuerySimple( p_ml,
870                         "CREATE TABLE directories ( "
871                         "id INTEGER PRIMARY KEY,"
872                         "uri VARCHAR(1024),"
873                         "timestamp INTEGER,"
874                         "recursive INTEGER )" );
875     if( i_ret != VLC_SUCCESS )
876         goto quit_createemptydatabase;
877
878     /* Create information table
879      * This table should have one row and the version number is the version
880      * of the database
881      * Other information may be stored here at later stages */
882     i_ret = QuerySimple( p_ml,
883                         "CREATE TABLE information ( "
884                         "version INTEGER PRIMARY KEY )" );
885     if( i_ret != VLC_SUCCESS )
886         goto quit_createemptydatabase;
887
888     /* Insert current DB version */
889     i_ret = QuerySimple( p_ml,
890                         "INSERT INTO information ( version ) "
891                         "VALUES ( %d )", ML_DBVERSION );
892     if( i_ret != VLC_SUCCESS )
893         goto quit_createemptydatabase;
894
895     /* Text data: song lyrics or subtitles */
896     i_ret = QuerySimple( p_ml,
897                         "CREATE TABLE extra ( "
898                         "id INTEGER PRIMARY KEY,"
899                         "extra TEXT,"
900                         "language VARCHAR(256),"
901                         "bitrate INTEGER,"
902                         "samplerate INTEGER,"
903                         "bpm INTEGER )" );
904     if( i_ret != VLC_SUCCESS )
905         goto quit_createemptydatabase;
906
907     /* Emulating foreign keys with triggers */
908     /* Warning: Lots of SQL */
909     if( !strcmp( module_get_name( p_ml->p_sys->p_sql->p_module, false ),
910         "SQLite" ) )
911     {
912     i_ret = QuerySimple( p_ml,
913     "\nCREATE TRIGGER genfkey1_insert_referencing BEFORE INSERT ON \"media\" WHEN\n"
914     "    new.\"album_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"album\" WHERE new.\"album_id\" == \"id\")\n"
915     "BEGIN\n"
916     "  SELECT RAISE(ABORT, 'constraint genfkey1_insert_referencing failed. Cannot insert album_id into media. Album did not exist');\n"
917     "END;\n"
918     "\n"
919     "CREATE TRIGGER genfkey1_update_referencing BEFORE\n"
920     "    UPDATE OF album_id ON \"media\" WHEN \n"
921     "    new.\"album_id\" IS NOT NULL AND \n"
922     "    NOT EXISTS (SELECT 1 FROM \"album\" WHERE new.\"album_id\" == \"id\")\n"
923     "BEGIN\n"
924     "  SELECT RAISE(ABORT, 'constraint genfkey1_update_referencing failed. Cannot update album_id in media. Album did not exist');\n"
925     "END;\n"
926     "\n"
927     "CREATE TRIGGER genfkey1_delete_referenced BEFORE DELETE ON \"album\" WHEN\n"
928     "    EXISTS (SELECT 1 FROM \"media\" WHERE old.\"id\" == \"album_id\")\n"
929     "BEGIN\n"
930     "  SELECT RAISE(ABORT, 'constraint genfkey1_delete_referenced failed. Cannot delete album, media still exist');\n"
931     "END;\n"
932     "\n"
933     "\n"
934     "CREATE TRIGGER genfkey1_update_referenced AFTER\n"
935     "    UPDATE OF id ON \"album\" WHEN \n"
936     "    EXISTS (SELECT 1 FROM \"media\" WHERE old.\"id\" == \"album_id\")\n"
937     "BEGIN\n"
938     "  SELECT RAISE(ABORT, 'constraint genfkey1_update_referenced failed. Cannot change album id in album, media still exist');\n"
939     "END;\n"
940     "\n"
941     "\n"
942     "CREATE TRIGGER genfkey2_insert_referencing BEFORE INSERT ON \"media_to_people\" WHEN \n"
943     "    new.\"media_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"media\" WHERE new.\"media_id\" == \"id\")\n"
944     "BEGIN\n"
945     "  SELECT RAISE(ABORT, 'constraint genfkey2_insert_referencing failed. Cannot insert into media_to_people, that media does not exist');\n"
946     "END;\n"
947     "\n"
948     "CREATE TRIGGER genfkey2_update_referencing BEFORE\n"
949     "    UPDATE OF media_id ON \"media_to_people\" WHEN \n"
950     "    new.\"media_id\" IS NOT NULL AND \n"
951     "    NOT EXISTS (SELECT 1 FROM \"media\" WHERE new.\"media_id\" == \"id\")\n"
952     "BEGIN\n"
953     "  SELECT RAISE(ABORT, 'constraint genfkey2_update_referencing failed. Cannot update media_to_people, that media does not exist');\n"
954     "END;\n"
955     "\n"
956     "CREATE TRIGGER genfkey2_delete_referenced BEFORE DELETE ON \"media\" WHEN\n"
957     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"media_id\")\n"
958     "BEGIN\n"
959     "  DELETE FROM \"media_to_people\" WHERE \"media_id\" = old.\"id\";\n"
960     "END;\n"
961     "\n"
962     "CREATE TRIGGER genfkey2_update_referenced AFTER\n"
963     "    UPDATE OF id ON \"media\" WHEN \n"
964     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"media_id\")\n"
965     "BEGIN\n"
966     "  SELECT RAISE(ABORT, 'constraint genfkey2_update_referenced failed. Cannot update media id, refs still exist in media_to_people');\n"
967     "END;\n"
968     "\n"
969     "CREATE TRIGGER genfkey3_insert_referencing BEFORE INSERT ON \"media_to_people\" WHEN \n"
970     "    new.\"people_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"people\" WHERE new.\"people_id\" == \"id\")\n"
971     "BEGIN\n"
972     "  SELECT RAISE(ABORT, 'constraint genfkey3_insert_referencing failed. Cannot insert into media_to_people, people does not exist');\n"
973     "END;\n"
974     "CREATE TRIGGER genfkey3_update_referencing BEFORE\n"
975     "    UPDATE OF people_id ON \"media_to_people\" WHEN \n"
976     "    new.\"people_id\" IS NOT NULL AND \n"
977     "    NOT EXISTS (SELECT 1 FROM \"people\" WHERE new.\"people_id\" == \"id\")\n"
978     "BEGIN\n"
979     "  SELECT RAISE(ABORT, 'constraint genfkey3_update_referencing failed. Cannot update media_to_people, people does not exist');\n"
980     "END;\n"
981     "\n"
982     "CREATE TRIGGER genfkey3_delete_referenced BEFORE DELETE ON \"people\" WHEN\n"
983     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"people_id\")\n"
984     "BEGIN\n"
985     "  UPDATE media_to_people SET people_id = 0 WHERE people_id == old.\"id\";\n"
986     "END;\n"
987     "\n"
988     "CREATE TRIGGER genfkey3_update_referenced AFTER\n"
989     "    UPDATE OF id ON \"people\" WHEN \n"
990     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"people_id\")\n"
991     "BEGIN\n"
992     "  SELECT RAISE(ABORT, 'constraint genfkey3_update_referenced failed. Cannot update people_id, people does not exist');\n"
993     "END;\n"
994     "\n"
995     "CREATE TRIGGER keep_people_clean AFTER \n"
996     "    DELETE ON \"media_to_people\"\n"
997     "    WHEN NOT EXISTS( SELECT 1 from \"media_to_people\" WHERE old.\"people_id\" == \"people_id\" )\n"
998     "BEGIN\n"
999     "    DELETE FROM people WHERE people.id = old.\"people_id\" AND people.id != 0;\n"
1000     "END;\n"
1001     "\n"
1002     "CREATE TRIGGER keep_album_clean AFTER\n"
1003     "    DELETE ON \"media\"\n"
1004     "    WHEN NOT EXISTS( SELECT 1 FROM \"media\" WHERE old.\"album_id\" == \"album_id\" )\n"
1005     "BEGIN\n"
1006     "    DELETE FROM album WHERE album.id = old.\"album_id\" AND album.id != 0;\n"
1007     "END;" );
1008     if( i_ret != VLC_SUCCESS )
1009         goto quit_createemptydatabase;
1010     }
1011
1012 quit_createemptydatabase:
1013     if( i_ret == VLC_SUCCESS )
1014         Commit( p_ml );
1015     else
1016         Rollback( p_ml );
1017     return VLC_SUCCESS;
1018 }
1019
1020
1021 /**
1022  * @brief Initiates database (create the database and the tables if needed)
1023  *
1024  * @param p_ml This ML
1025  * @return VLC_SUCCESS or an error code
1026  */
1027 int InitDatabase( media_library_t *p_ml )
1028 {
1029     assert( p_ml );
1030     msg_Dbg( p_ml, "initializing database" );
1031
1032     /* Select database name */
1033     char *psz_dbhost = NULL, *psz_user = NULL, *psz_pass = NULL;
1034     int i_port = 0;
1035     psz_dbhost = config_GetPsz( p_ml, "ml-filename" );
1036     psz_user = config_GetPsz( p_ml, "ml-username" );
1037     psz_pass = config_GetPsz( p_ml, "ml-password" );
1038     i_port = config_GetInt( p_ml, "ml-port" );
1039
1040     /* Let's consider that a filename with a DIR_SEP is a full URL */
1041     if( strchr( psz_dbhost, DIR_SEP_CHAR ) == NULL )
1042     {
1043         char *psz_datadir = config_GetUserDir( VLC_DATA_DIR );
1044         char *psz_tmp = psz_dbhost;
1045         if( asprintf( &psz_dbhost, "%s" DIR_SEP "%s",
1046                       psz_datadir, psz_tmp ) == -1 )
1047         {
1048             free( psz_datadir );
1049             free( psz_tmp );
1050             return VLC_ENOMEM;
1051         }
1052         free( psz_datadir );
1053         free( psz_tmp );
1054     }
1055
1056     p_ml->p_sys->p_sql = sql_Create( p_ml, NULL, psz_dbhost, i_port, psz_user,
1057                                      psz_pass );
1058     if( !p_ml->p_sys->p_sql )
1059     {
1060         vlc_mutex_destroy( &p_ml->p_sys->lock );
1061         free( p_ml->p_sys );
1062         return VLC_EGENERIC;
1063     }
1064
1065     /* Let's check if tables exist */
1066     int i_version = GetDatabaseVersion( p_ml );
1067     if( i_version <= 0 )
1068         CreateEmptyDatabase( p_ml );
1069     else if( i_version != ML_DBVERSION )
1070         return VLC_EGENERIC;
1071
1072     /**
1073      * The below code ensures that correct code is written
1074      * when database versions are changed
1075      */
1076
1077 #if ML_DBVERSION != 1
1078 #error "ML versioning code needs to be updated. Is this done correctly?"
1079 #endif
1080
1081     msg_Dbg( p_ml, "ML initialized" );
1082     return VLC_SUCCESS;
1083 }
1084
1085 /**
1086  * @brief Gets the current version number from the database
1087  *
1088  * @param p_ml media library object
1089  * @return version number of the current db. <= 0 on error.
1090  */
1091 int GetDatabaseVersion( media_library_t *p_ml )
1092 {
1093     int i_rows, i_cols;
1094     char **pp_results;
1095     int i_return;
1096     i_return = Query( p_ml, &pp_results, &i_rows, &i_cols,
1097         "SELECT version FROM information ORDER BY version DESC LIMIT 1" );
1098     if( i_return != VLC_SUCCESS )
1099         i_return = -1;
1100     else
1101         i_return = atoi( pp_results[ 1 ] );
1102
1103     FreeSQLResult( p_ml, pp_results );
1104
1105     return i_return;
1106 }
1107
1108  /**
1109  * @brief Object constructor for ml_media_t
1110  * @param p_ml The media library object
1111  * @param id If 0, this item isn't in database. If non zero, it is and
1112  * it will be a singleton
1113  * @param select Type of object
1114  * @param reload Whether to reload from database
1115  */
1116 ml_media_t* GetMedia( media_library_t* p_ml, int id,
1117                         ml_select_e select, bool reload )
1118 {
1119     assert( id > 0 );
1120     assert( select == ML_MEDIA || select == ML_MEDIA_SPARSE );
1121     int i_ret = VLC_SUCCESS;
1122     ml_media_t* p_media = NULL;
1123     if( !reload )
1124     {
1125         p_media = pool_GetMedia( p_ml, id );
1126         if( !p_media )
1127             reload = true;
1128         else
1129         {
1130             ml_LockMedia( p_media );
1131             if( p_media->b_sparse == true && select == ML_MEDIA )
1132                 reload = true;
1133             /* Utilise ML_MEDIA_EXTRA load? TODO */
1134             ml_UnlockMedia( p_media );
1135             ml_gc_incref( p_media );
1136         }
1137     }
1138     else
1139     {
1140         vlc_array_t *p_array = vlc_array_new();
1141         i_ret = ml_Find( p_ml, p_array, select, ML_ID, id );
1142         assert( vlc_array_count( p_array ) == 1 );
1143         if( ( i_ret == VLC_SUCCESS )
1144             && ( vlc_array_count( p_array ) > 0 )
1145             && vlc_array_item_at_index( p_array, 0 ) )
1146         {
1147             p_media = ((ml_result_t*)vlc_array_item_at_index( p_array, 0 ))->value.p_media;
1148             ml_gc_incref( p_media );
1149             ml_FreeResult( vlc_array_item_at_index( p_array, 0 ) );
1150         }
1151         vlc_array_destroy( p_array );
1152         if( select == ML_MEDIA )
1153             p_media->b_sparse = false;
1154         else
1155             p_media->b_sparse = true;
1156     }
1157     return p_media;
1158 }
1159 /**
1160  * @brief Create an input item from media (given its ID)
1161  *
1162  * @param p_ml This media_library_t object
1163  * @param i_media Media ID
1164  * @return input_item_t* created
1165  *
1166  * @note This is a public function (pf_InputItemFromMedia)
1167  * The input_item will have a refcount at 2 (1 for the ML, 1 for you)
1168  */
1169 input_item_t* GetInputItemFromMedia( media_library_t *p_ml, int i_media )
1170 {
1171     input_item_t *p_item = NULL;
1172
1173     p_item = watch_get_itemOfMediaId( p_ml, i_media );
1174     if( !p_item )
1175     {
1176         ml_media_t* p_media = media_New( p_ml, i_media, ML_MEDIA, true );
1177         if( p_media == NULL )
1178             return NULL;
1179         CreateInputItemFromMedia( p_ml, &p_item, p_media );
1180         watch_add_Item( p_ml, p_item, p_media );
1181         ml_gc_decref( p_media );
1182     }
1183
1184     return p_item;
1185 }
1186
1187 /**
1188  * @brief Copy an input_item_t to a ml_media_t
1189  * @param p_media Destination
1190  * @param p_item Source
1191  * @note Media ID will not be set! This function is threadsafe. Leaves
1192  * unsyncable items alone
1193  */
1194 void CopyInputItemToMedia( ml_media_t *p_media, input_item_t *p_item )
1195 {
1196     ml_LockMedia( p_media );
1197 #if 0
1198     // unused meta :
1199     input_item_GetCopyright( item )
1200     input_item_GetRating( item ) /* TODO */
1201     input_item_GetGetting( item )
1202     input_item_GetNowPlaying( item )
1203     input_item_GetTrackID( item )
1204     input_item_GetSetting( item )
1205 #endif
1206     p_media->psz_title      = input_item_GetTitle       ( p_item );
1207     p_media->psz_uri        = input_item_GetURL         ( p_item );
1208     if( !p_media->psz_uri )
1209         p_media->psz_uri    = strdup( p_item->psz_uri );
1210     p_media->psz_album      = input_item_GetAlbum       ( p_item );
1211     p_media->psz_cover      = input_item_GetArtURL      ( p_item );
1212     p_media->psz_genre      = input_item_GetGenre       ( p_item );
1213     p_media->psz_language   = input_item_GetLanguage    ( p_item );
1214     p_media->psz_comment    = input_item_GetDescription ( p_item );
1215     char *psz_track         = input_item_GetTrackNum    ( p_item );
1216     p_media->i_track_number = psz_track ? atoi( psz_track ) : 0;
1217     free( psz_track );
1218     char *psz_date          = input_item_GetDate( p_item );
1219     p_media->i_year         = psz_date ? atoi( psz_date ) : 0;
1220     free( psz_date );
1221     p_media->i_duration     = p_item->i_duration;
1222
1223     /* People */
1224     char *psz_tmp = input_item_GetArtist( p_item );
1225     if( psz_tmp )
1226         ml_CreateAppendPersonAdv( &p_media->p_people, ML_PERSON_ARTIST,
1227                                      psz_tmp, 0 );
1228     free( psz_tmp );
1229     psz_tmp = input_item_GetPublisher( p_item );
1230     if( psz_tmp )
1231         ml_CreateAppendPersonAdv( &p_media->p_people, ML_PERSON_PUBLISHER,
1232                                     psz_tmp, 0 );
1233     free( psz_tmp );
1234     psz_tmp = input_item_GetEncodedBy( p_item );
1235     if( psz_tmp )
1236         ml_CreateAppendPersonAdv( &p_media->p_people, ML_PERSON_ENCODER,
1237                                     psz_tmp, 0 );
1238     free( psz_tmp );
1239
1240     /* Determine input type: audio, video, stream */
1241     /* First read input type */
1242     switch( p_item->i_type )
1243     {
1244     case ITEM_TYPE_FILE:
1245         p_media->i_type |= 0;
1246         break;
1247     case ITEM_TYPE_DISC:
1248     case ITEM_TYPE_CARD:
1249         p_media->i_type |= ML_REMOVABLE;
1250         break;
1251     case ITEM_TYPE_CDDA:
1252     case ITEM_TYPE_NET:
1253         p_media->i_type |= ML_STREAM;
1254         break;
1255     case ITEM_TYPE_PLAYLIST:
1256     case ITEM_TYPE_NODE:
1257     case ITEM_TYPE_DIRECTORY:
1258         p_media->i_type |= ML_NODE;
1259         break;
1260     case ITEM_TYPE_NUMBER:
1261     case ITEM_TYPE_UNKNOWN:
1262     default:
1263         p_media->i_type |= ML_UNKNOWN;
1264         break;
1265     }
1266
1267     /* Then try to guess if this is a video or not */
1268     /* Check file extension, and guess if this is a video or an audio media
1269        Note: this test is not very good, but it's OK for normal files */
1270     char *psz_ext = strrchr( p_item->psz_uri, '.' );
1271     if( psz_ext && strlen( psz_ext ) < 5 )
1272     {
1273         bool b_ok = false;
1274         psz_ext++;
1275         for( unsigned i = 0; ppsz_AudioExtensions[i]; i++ )
1276         {
1277             if( strcasecmp( psz_ext, ppsz_AudioExtensions[i] ) == 0 )
1278             {
1279                 p_media->i_type |= ML_AUDIO;
1280                 b_ok = true;
1281                 break;
1282             }
1283         }
1284         if( !b_ok )
1285         {
1286             for( unsigned i = 0; ppsz_VideoExtensions[i]; i++ )
1287             {
1288                 if( strcasecmp( psz_ext, ppsz_VideoExtensions[i] ) == 0 )
1289                 {
1290                     p_media->i_type |= ML_VIDEO;
1291                     break;
1292                 }
1293             }
1294         }
1295     }
1296     ml_UnlockMedia( p_media );
1297 }
1298
1299 /**
1300  * @brief Copy a ml_media_t to an input_item_t
1301  * @param p_item Destination
1302  * @param p_media Source
1303  */
1304 void CopyMediaToInputItem( input_item_t *p_item, ml_media_t *p_media )
1305 {
1306     ml_LockMedia( p_media );
1307     if( p_media->psz_title && *p_media->psz_title )
1308         input_item_SetTitle( p_item, p_media->psz_title );
1309     if( p_media->psz_uri && *p_media->psz_uri )
1310         input_item_SetURL( p_item, p_media->psz_uri );
1311     if( p_media->psz_album && *p_media->psz_album )
1312         input_item_SetAlbum( p_item, p_media->psz_album );
1313     if( p_media->psz_cover && *p_media->psz_cover )
1314         input_item_SetArtURL( p_item, p_media->psz_cover );
1315     if( p_media->psz_genre && *p_media->psz_genre )
1316         input_item_SetGenre( p_item, p_media->psz_genre );
1317     if( p_media->psz_language && *p_media->psz_language )
1318         input_item_SetLanguage( p_item, p_media->psz_language );
1319     if( p_media->psz_comment && *p_media->psz_comment )
1320         input_item_SetDescription( p_item, p_media->psz_comment );
1321     if( p_media->i_track_number )
1322     {
1323         char *psz_track;
1324         if( asprintf( &psz_track, "%d", p_media->i_track_number ) != -1 )
1325             input_item_SetTrackNum( p_item, psz_track );
1326         free( psz_track );
1327     }
1328     if( p_media->i_year )
1329     {
1330         char *psz_date;
1331         if( asprintf( &psz_date, "%d", p_media->i_year ) != -1 )
1332             input_item_SetDate( p_item, psz_date );
1333         free( psz_date );
1334     }
1335     p_item->i_duration = p_media->i_duration;
1336     ml_person_t *person = p_media->p_people;
1337     while( person )
1338     {
1339         if( !strcmp( person->psz_role, ML_PERSON_ARTIST ) )
1340             input_item_SetArtist( p_item, person->psz_name );
1341         else if( !strcmp( person->psz_role, ML_PERSON_PUBLISHER ) )
1342             input_item_SetPublisher( p_item, person->psz_name );
1343         else if( !strcmp( person->psz_role, ML_PERSON_ENCODER ) )
1344             input_item_SetEncodedBy( p_item, person->psz_name );
1345         person = person->p_next;
1346     }
1347     ml_UnlockMedia( p_media );
1348 }
1349
1350 /**
1351  * @brief Copy a ml_media_t to an input_item_t
1352  * @param p_ml The Media Library object
1353  * @param pp_item A pointer to a new input_item (return value)
1354  * @param p_media The media to copy as an input item
1355  * @note This function is threadsafe
1356  */
1357 static int CreateInputItemFromMedia( media_library_t *p_ml,
1358                               input_item_t **pp_item,
1359                               ml_media_t *p_media )
1360 {
1361     playlist_t *p_pl = pl_Get( p_ml );
1362     *pp_item = input_item_New( VLC_OBJECT( p_pl ),
1363                                p_media->psz_uri,
1364                                p_media->psz_title );
1365                                /* ITEM_TYPE_FILE ); */
1366     if( !*pp_item )
1367         return VLC_EGENERIC;
1368     CopyMediaToInputItem( *pp_item, p_media );
1369     return VLC_SUCCESS;
1370 }
1371
1372 /**
1373  * @brief Find the media_id associated to an input item
1374  * @param p_ml This
1375  * @param p_item Input item to look for
1376  * @return Media ID or <= 0 if not found
1377  */
1378 int GetMediaIdOfInputItem( media_library_t *p_ml, input_item_t *p_item )
1379 {
1380     int i_media_id = watch_get_mediaIdOfItem( p_ml, p_item );
1381     if( i_media_id <= 0 )
1382     {
1383         i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
1384     }
1385     return i_media_id;
1386 }
1387
1388