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