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