]> git.sesse.net Git - vlc/blob - modules/media_library/sql_media_library.c
ML: fix memory leak.
[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 int compare_ml_elts( const void *a, const void *b )
67 {
68     return strcmp( ( (struct ml_table_elt* )a )->column_name,
69             ( ( struct ml_table_elt* )b )->column_name );
70 }
71
72 static const struct ml_table_elt ml_table_map[]=
73 {
74     { ML_ALBUM_COVER,   "album_cover" },
75     { ML_ALBUM_ID,          "album_id" },
76     { ML_ALBUM,         "album_title" },
77     { ML_COMMENT,           "comment" },
78     { ML_COVER,         "cover" },
79     { ML_DIRECTORY,         "directory_id" },
80     { ML_DISC_NUMBER,   "disc" },
81     { ML_DURATION,          "duration" },
82     { ML_EXTRA,         "extra" },
83     { ML_FILESIZE,          "filesize" },
84     { ML_FIRST_PLAYED,  "first_played" },
85     { ML_GENRE,         "genre" },
86     { ML_ID,            "id" },
87     { ML_IMPORT_TIME,   "import_time" },
88     { ML_LANGUAGE,          "language" },
89     { ML_LAST_PLAYED,   "last_played" },
90     { ML_LAST_SKIPPED,  "last_skipped" },
91     { ML_ORIGINAL_TITLE,"original_title" },
92     { ML_PEOPLE_ID,         "people_id" },
93     { ML_PEOPLE,            "people_name" },
94     { ML_PEOPLE_ROLE,   "people_role" },
95     { ML_PLAYED_COUNT,  "played_count" },
96     { ML_PREVIEW,           "preview" },
97     { ML_SCORE,         "score" },
98     { ML_SKIPPED_COUNT, "skipped_count" },
99     { ML_TITLE,         "title" },
100     { ML_TRACK_NUMBER,  "track" },
101     { ML_TYPE,          "type" },
102     { ML_URI,           "uri" },
103     { ML_VOTE,          "vote" },
104     { ML_YEAR,          "year" }
105 };
106
107 /*****************************************************************************
108  * Module description
109  *****************************************************************************/
110 vlc_module_begin()
111     set_shortname( "Media Library" )
112     set_description( _( "Media Library based on a SQL based database" ) )
113     set_capability( "media-library", 1 )
114     set_callbacks( load, unload )
115     set_category( CAT_ADVANCED )
116     set_subcategory( SUBCAT_ADVANCED_MISC )
117     add_string( "ml-filename", "vlc-media-library.db",
118             MEDIA_LIBRARY_PATH_TEXT, MEDIA_LIBRARY_PATH_LONGTEXT, false )
119     add_string( "ml-username", "",  N_( "Username for the database" ),
120             N_( "Username for the database" ), false )
121     add_string( "ml-password", "",  N_( "Password for the database" ),
122             N_( "Password for the database" ), false )
123     add_integer( "ml-port", 0,
124             N_( "Port for the database" ), N_("Port for the database"), false )
125     add_bool( "ml-recursive-scan", true, RECURSIVE_TEXT,
126             RECURSIVE_LONGTEXT, false )
127     add_bool( "ml-auto-add", true,  N_("Auto add new medias"),
128             N_( "Automatically add new medias to ML" ), false )
129 vlc_module_end()
130
131
132 /**
133  * @brief Load module
134  * @param obj Parent object
135  */
136 static int load( vlc_object_t *obj )
137 {
138     msg_Dbg( obj, "loading media library module" );
139
140     media_library_t *p_ml = ( media_library_t * ) obj;
141     p_ml->p_sys = ( media_library_sys_t* )
142                         calloc( 1, sizeof( media_library_sys_t ) );
143     if( !p_ml->p_sys )
144         return VLC_ENOMEM;
145
146     p_ml->functions.pf_Find               = FindVa;
147     p_ml->functions.pf_FindAdv            = FindAdv;
148     p_ml->functions.pf_Control            = Control;
149     p_ml->functions.pf_InputItemFromMedia = GetInputItemFromMedia;
150     p_ml->functions.pf_Update             = Update;
151     p_ml->functions.pf_Delete             = Delete;
152     p_ml->functions.pf_GetMedia           = GetMedia;
153
154     vlc_mutex_init( &p_ml->p_sys->lock );
155
156     /* Initialise Sql module */
157     InitDatabase( p_ml );
158
159     /* Initialise the media pool */
160     ARRAY_INIT( p_ml->p_sys->mediapool );
161     vlc_mutex_init( &p_ml->p_sys->pool_mutex );
162
163     /* Create variables system */
164     var_Create( p_ml, "media-added", VLC_VAR_INTEGER );
165     var_Create( p_ml, "media-deleted", VLC_VAR_INTEGER );
166     var_Create( p_ml, "media-meta-change", VLC_VAR_INTEGER );
167
168     /* Launching the directory monitoring thread */
169     monitoring_thread_t *p_mon =
170             vlc_object_create( p_ml, sizeof( monitoring_thread_t ) );
171     if( !p_mon )
172     {
173         vlc_mutex_destroy( &p_ml->p_sys->lock );
174         sql_Destroy( p_ml->p_sys->p_sql );
175         free( p_ml->p_sys );
176         return VLC_ENOMEM;
177     }
178     p_ml->p_sys->p_mon = p_mon;
179
180     p_mon->p_ml = p_ml;
181
182     if( vlc_clone( &p_mon->thread, RunMonitoringThread, p_mon,
183                 VLC_THREAD_PRIORITY_LOW ) )
184     {
185         msg_Err( p_ml, "cannot spawn the media library monitoring thread" );
186         vlc_mutex_destroy( &p_ml->p_sys->lock );
187         sql_Destroy( p_ml->p_sys->p_sql );
188         free( p_ml->p_sys );
189         vlc_object_release( p_mon );
190         return VLC_EGENERIC;
191     }
192     /* Starting the watching system (starts a thread) */
193     watch_Init( p_ml );
194
195     msg_Dbg( p_ml, "Media library module loaded successfully" );
196
197     return VLC_SUCCESS;
198 }
199
200
201 /**
202  * @brief Unload module
203  *
204  * @param obj the media library object
205  * @return Nothing
206  */
207 static void unload( vlc_object_t *obj )
208 {
209     media_library_t *p_ml = ( media_library_t* ) obj;
210
211     /* Stopping the watching system */
212     watch_Close( p_ml );
213
214     /* Stop the monitoring thread */
215     vlc_cancel( p_ml->p_sys->p_mon->thread );
216     vlc_join( p_ml->p_sys->p_mon->thread, NULL );
217     vlc_object_release( p_ml->p_sys->p_mon );
218
219     /* Destroy the variable */
220     var_Destroy( p_ml, "media-meta-change" );
221     var_Destroy( p_ml, "media-deleted" );
222     var_Destroy( p_ml, "media-added" );
223
224     /* Empty the media pool */
225     ml_media_t* item;
226     FOREACH_ARRAY( item, p_ml->p_sys->mediapool )
227         ml_gc_decref( item );
228     FOREACH_END()
229     vlc_mutex_destroy( &p_ml->p_sys->pool_mutex );
230
231     sql_Destroy( p_ml->p_sys->p_sql );
232
233     vlc_mutex_destroy( &p_ml->p_sys->lock );
234
235     free( p_ml->p_sys );
236 }
237
238 /**
239  * @brief Get results of an SQL-Query on the database (please : free the result)
240  *
241  * @param p_ml the media library object
242  * @param ppp_res char *** in which to store the table of results (allocated)
243  * @param pi_rows resulting row number in table
244  * @param pi_cols resulting column number in table
245  * @param psz_fmt query command with printf-like format enabled
246  * @param va_args format the command
247  * @return VLC_SUCCESS or a VLC error code
248  */
249 int Query( media_library_t *p_ml,
250               char ***ppp_res, int *pi_rows, int *pi_cols,
251               const char *psz_fmt, ... )
252 {
253     va_list argp;
254     va_start( argp, psz_fmt );
255
256     int i_ret = QueryVa( p_ml, ppp_res, pi_rows, pi_cols, psz_fmt, argp );
257
258     va_end( argp );
259     return i_ret;
260 }
261
262 /**
263  * @brief Get results of an SQL-Query on the database (please : free the result)
264  *
265  * @param p_ml the media library object
266  * @param ppp_res char *** in which to store the table of results (allocated)
267  * @param pi_rows resulting row number in table
268  * @param pi_cols resulting column number in table
269  * @param psz_fmt query command with printf-like format enabled
270  * @param va_args format the command
271  * @return VLC_SUCCESS or a VLC error code
272  */
273 int QueryVa( media_library_t *p_ml, char ***ppp_res,
274                       int *pi_rows, int *pi_cols, const char *psz_fmt,
275                       va_list argp )
276 {
277     assert( p_ml );
278     if( !ppp_res || !psz_fmt ) return VLC_EGENERIC;
279
280     char *psz_query = sql_VPrintf( p_ml->p_sys->p_sql, psz_fmt, argp );
281     if( !psz_query )
282         return VLC_ENOMEM;
283
284     int i_ret = sql_Query( p_ml->p_sys->p_sql, psz_query,
285                            ppp_res, pi_rows, pi_cols );
286
287     free( psz_query );
288     return i_ret;
289 }
290
291 /**
292  * @brief Do a SQL-query without any data coming back
293  *
294  * @param p_ml the media library object
295  * @param psz_fmt query command with printf-like format enabled
296  * @param va_args format the command
297  * @return VLC_SUCCESS or a VLC error code
298  */
299 int QuerySimple( media_library_t *p_ml,
300                     const char *psz_fmt, ... )
301 {
302     va_list argp;
303     va_start( argp, psz_fmt );
304
305     int i_ret = QuerySimpleVa( p_ml, psz_fmt, argp );
306
307     va_end( argp );
308     return i_ret;
309 }
310
311 /**
312  * @brief Do a SQL-query without any data coming back
313  *
314  * @param p_ml the media library object
315  * @param psz_fmt query command with printf-like format enabled
316  * @param argp format the command
317  * @return VLC_SUCCESS or a VLC error code
318  */
319 int QuerySimpleVa( media_library_t *p_ml,
320                       const char *psz_fmt, va_list argp )
321 {
322     assert( p_ml );
323
324     int i_ret = VLC_SUCCESS;
325     int i_rows, i_cols;
326     char **pp_results = NULL;
327
328     i_ret = QueryVa( p_ml, &pp_results, &i_rows, &i_cols, psz_fmt, argp );
329
330     FreeSQLResult( p_ml, pp_results );
331     va_end( argp );
332
333     return i_ret;
334 }
335
336 /**
337  * @brief Transforms a string to a ml_result_t, with given type and id (as psz)
338  *
339  * @param res the result of the function
340  * @param psz string to transform into a result
341  * @param psz_id id as a string
342  * @param result_type type of the result
343  * @return ID or a VLC error code
344  */
345 int StringToResult( ml_result_t *p_result, const char *psz,
346                     const char *psz_id, ml_result_type_e result_type )
347 {
348     memset( &p_result->value, 0, sizeof( p_result->value ) );
349
350     p_result->id = psz_id ? atoi( psz_id ) : 0;
351     p_result->type = result_type;
352
353     switch( result_type )
354     {
355         case ML_TYPE_INT:
356             p_result->value.i = psz ? atoi( psz ) : 0;
357             break;
358
359         case ML_TYPE_TIME:
360             p_result->value.time = psz ? ( mtime_t ) atoi( psz )
361                                        : ( mtime_t ) 0LL;
362             break;
363
364         case ML_TYPE_PSZ:
365             p_result->value.psz = psz ? strdup( psz ) : NULL;
366             break;
367
368         case ML_TYPE_MEDIA:
369         default:
370             /* This is an error */
371             return VLC_EGENERIC;
372     }
373
374     return p_result->id;
375 }
376
377
378 /**
379  * @brief fills an ml_result_array_t with result of an SQL query
380  *
381  * @param p_ml the media library object
382  * @param p_media ml_result_array_t object to fill
383  * @param pp_results result of sql query
384  * @param i_rows row number
385  * @param i_cols column number
386  * @param result_type type of the result
387  * @return VLC_SUCCESS or a VLC error code
388  **/
389 int SQLToResultArray( media_library_t *p_ml, vlc_array_t *p_result_array,
390                       char **pp_results, int i_rows, int i_cols,
391                       ml_result_type_e result_type )
392 {
393     assert( p_ml );
394     if( !p_result_array )
395         return VLC_EGENERIC;
396     if( i_cols == 0 )   /* No result */
397         return VLC_SUCCESS;
398     if( i_cols < 0 )
399     {
400         msg_Err( p_ml, "negative number of columns in result ?" );
401         return VLC_EGENERIC;
402     }
403
404     if( i_cols == 1 )
405     {
406         for( int i = 1; i <= i_rows; i++ )
407         {
408             ml_result_t *res = ( ml_result_t* )
409                                     calloc( 1, sizeof( ml_result_t ) );
410             if( !res )
411                 return VLC_ENOMEM;
412             StringToResult( res, pp_results[ i ], NULL, result_type );
413             vlc_array_append( p_result_array, res );
414         }
415     }
416     /* FIXME?: Assuming all double column results are id - result pairs */
417     else if( ( i_cols == 2 ) )
418     {
419         for( int i = 1; i <= i_rows; i++ )
420         {
421             ml_result_t *res = ( ml_result_t* )
422                                     calloc( 1, sizeof( ml_result_t ) );
423             if( !res )
424                 return VLC_ENOMEM;
425             StringToResult( res, pp_results[ i * 2 + 1], pp_results[ i * 2 ],
426                             result_type );
427             vlc_array_append( p_result_array, res );
428         }
429     }
430     else if( result_type == ML_TYPE_MEDIA )
431     {
432         return SQLToMediaArray( p_ml, p_result_array,
433                                 pp_results, i_rows, i_cols );
434     }
435     else
436     {
437         msg_Err( p_ml, "unable to convert SQL result to a ml_result_t array" );
438         return VLC_EGENERIC;
439     }
440     return VLC_SUCCESS;
441 }
442
443
444 /**
445  * @brief fills a vlc_array_t with results of an SQL query
446  *        medias in ml_result_t
447  *
448  * @param p_ml the media library object
449  * @param p_array array to fill with ml_media_t elements (might be initialized)
450  * @param pp_results result of sql query
451  * @param i_rows row number
452  * @param i_cols column number
453  * @return VLC_SUCCESS or a VLC error code
454  * Warning: this returns VLC_EGENERIC if i_rows == 0 (empty result)
455  **/
456 int SQLToMediaArray( media_library_t *p_ml, vlc_array_t *p_result_array,
457                      char **pp_results, int i_rows, int i_cols )
458 {
459     int i_ret = VLC_SUCCESS;
460     assert( p_ml );
461
462     #define res( i, j ) ( pp_results[ i * i_cols + j ] )
463     #define atoinull( a ) ( (a) ? atoi( a ) : 0 )
464     #define strdupnull( a ) ( (a) ? strdup( a ) : NULL )
465
466     if( i_rows == 0 )
467         return VLC_EGENERIC;
468
469     if( !p_result_array || !pp_results || i_rows < 0 || i_cols <= 0 )
470     {
471         msg_Warn( p_ml, "bad arguments (%s:%d)", __FILE__, __LINE__ );
472         return VLC_EGENERIC;
473     }
474
475     vlc_array_t* p_intermediate_array = vlc_array_new();
476
477     /* Analyze first row */
478     int *indexes = ( int* ) calloc( i_cols + 1, sizeof( int ) );
479     if( !indexes )
480     {
481         vlc_array_destroy( p_intermediate_array );
482         return VLC_ENOMEM;
483     }
484
485     const int count = sizeof( ml_table_map )/ sizeof( struct ml_table_elt );
486     for( int col = 0; col < i_cols; col++ )
487     {
488         struct ml_table_elt key, *result = NULL;
489         key.column_name = res( 0, col );
490         result = bsearch( &key, ml_table_map, count,
491                 sizeof( struct ml_table_elt ), compare_ml_elts );
492
493         if( !result )
494             msg_Warn( p_ml, "unknown column: %s", res( 0, col ) );
495         else
496             indexes[col] = result->column_id;
497     }
498
499     /* Read rows 1 to i_rows */
500     ml_media_t  *p_media  = NULL;
501     ml_result_t *p_result = NULL;
502
503     for( int row = 1; ( row <= i_rows ) && ( i_ret == VLC_SUCCESS ); row++ )
504     {
505         p_media = media_New( p_ml, 0, ML_MEDIA, false );
506         if( !p_media )
507         {
508             free( indexes );
509             i_ret = VLC_ENOMEM;
510             goto quit_sqlmediaarray;
511         }
512         p_result = ( ml_result_t * ) calloc( 1, sizeof( ml_result_t ) );
513         if( !p_result )
514         {
515             ml_gc_decref( p_media );
516             free( indexes );
517             i_ret = VLC_ENOMEM;
518             goto quit_sqlmediaarray;
519         }
520
521         char* psz_append_pname = NULL;
522         char* psz_append_prole = NULL;
523         int i_append_pid = 0;
524
525 #define SWITCH_INT( key, value ) case key: \
526         p_media-> value = atoinull( res( row, col ) );
527 #define SWITCH_PSZ( key, value ) case key: \
528         p_media-> value = strdupnull( res( row, col ) );
529
530         ml_LockMedia( p_media );
531         for( int col = 0; ( col < i_cols ) && ( i_ret == VLC_SUCCESS ); col++ )
532         {
533             switch( indexes[ col ] )
534             {
535                 SWITCH_INT( ML_ALBUM_ID, i_album_id );
536                 SWITCH_PSZ( ML_ALBUM, psz_album );
537                 SWITCH_PSZ( ML_COMMENT, psz_comment );
538                 SWITCH_INT( ML_DISC_NUMBER, i_disc_number );
539                 SWITCH_INT( ML_DURATION, i_duration );
540                 SWITCH_PSZ( ML_EXTRA, psz_extra );
541                 SWITCH_INT( ML_FILESIZE, i_filesize );
542                 SWITCH_INT( ML_FIRST_PLAYED, i_first_played );
543                 SWITCH_PSZ( ML_GENRE, psz_genre);
544                 SWITCH_INT( ML_IMPORT_TIME, i_import_time );
545                 SWITCH_PSZ( ML_LANGUAGE, psz_language );
546                 SWITCH_INT( ML_LAST_PLAYED, i_last_played );
547                 SWITCH_INT( ML_LAST_SKIPPED, i_last_skipped );
548                 SWITCH_PSZ( ML_ORIGINAL_TITLE, psz_orig_title );
549                 SWITCH_INT( ML_PLAYED_COUNT, i_played_count );
550                 SWITCH_PSZ( ML_PREVIEW, psz_preview );
551                 SWITCH_INT( ML_SCORE, i_score );
552                 SWITCH_INT( ML_SKIPPED_COUNT, i_skipped_count );
553                 SWITCH_PSZ( ML_TITLE, psz_title );
554                 SWITCH_INT( ML_TRACK_NUMBER, i_track_number );
555                 SWITCH_INT( ML_TYPE, i_type );
556                 SWITCH_INT( ML_VOTE, i_vote);
557                 SWITCH_INT( ML_YEAR, i_year );
558             case ML_ALBUM_COVER:
559                 /* See ML_COVER */
560                 // Discard attachment://
561                 if( !p_media->psz_cover || !*p_media->psz_cover
562                  || !strncmp( p_media->psz_cover, "attachment://", 13 ) )
563                 {
564                     free( p_media->psz_cover );
565                     p_media->psz_cover = strdupnull( res( row, col ) );
566                 }
567                 break;
568             case ML_PEOPLE:
569                 psz_append_pname = strdupnull( res( row, col ) );
570                 break;
571             case ML_PEOPLE_ID:
572                 i_append_pid = atoinull( res( row, col ) );
573                 break;
574             case ML_PEOPLE_ROLE:
575                 psz_append_prole = strdupnull( res( row, col ) );
576                 break;
577             case ML_COVER:
578                 /* See ML_ALBUM_COVER */
579                 if( !p_media->psz_cover || !*p_media->psz_cover
580                      || !strncmp( p_media->psz_cover, "attachment://", 13 ) )
581                 {
582                     free( p_media->psz_cover );
583                     p_media->psz_cover = strdupnull( res( row, col ) );
584                 }
585                 break;
586             case ML_ID:
587                 p_media->i_id = atoinull( res( row, col ) );
588                 if( p_media->i_id <= 0 )
589                     msg_Warn( p_ml, "entry with id null or inferior to zero" );
590                 break;
591             case ML_URI:
592                 p_media->psz_uri = strdupnull( res( row, col ) );
593                 if( !p_media->psz_uri )
594                     msg_Warn( p_ml, "entry without uri" );
595                 break;
596             case ML_DIRECTORY:
597                 break; // The column directory_id is'nt part of the media model
598             default:
599                 msg_Warn( p_ml, "unknown element, row %d column %d (of %d) - %s - %s",
600                         row, col, i_cols, res( 0 , col ), res( row, col ) );
601                 break;
602             }
603         }
604
605 #undef SWITCH_INT
606 #undef SWITCH_PSZ
607         int i_appendrow;
608         ml_result_t* p_append = NULL;
609         for( i_appendrow = 0; i_appendrow < vlc_array_count( p_intermediate_array ); i_appendrow++ )
610         {
611             p_append = ( ml_result_t* )
612                 vlc_array_item_at_index( p_intermediate_array, i_appendrow );
613             if( p_append->id == p_media->i_id )
614                 break;
615         }
616         if( i_appendrow == vlc_array_count( p_intermediate_array ) )
617         {
618             p_result->id   = p_media->i_id;
619             p_result->type = ML_TYPE_MEDIA;
620             p_result->value.p_media = p_media;
621             if( psz_append_pname && i_append_pid && psz_append_prole )
622                 ml_CreateAppendPersonAdv( &(p_result->value.p_media->p_people),
623                         psz_append_prole, psz_append_pname, i_append_pid );
624             vlc_array_append( p_intermediate_array, p_result );
625             ml_UnlockMedia( p_media );
626         }
627         else /* This is a repeat row and the people need to be put together */
628         {
629             free( p_result );
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