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