]> git.sesse.net Git - vlc/blob - modules/media_library/sql_media_library.c
wasapi: do not assume mdate() returns the performance counter
[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     if ( InitDatabase( p_ml ) != VLC_SUCCESS )
161     {
162         vlc_mutex_destroy( &p_ml->p_sys->lock );
163         free( p_ml->p_sys );
164         return VLC_EGENERIC;
165     }
166
167     /* Initialise the media pool */
168     ARRAY_INIT( p_ml->p_sys->mediapool );
169     vlc_mutex_init( &p_ml->p_sys->pool_mutex );
170
171     /* Create variables system */
172     var_Create( p_ml, "media-added", VLC_VAR_INTEGER );
173     var_Create( p_ml, "media-deleted", VLC_VAR_INTEGER );
174     var_Create( p_ml, "media-meta-change", VLC_VAR_INTEGER );
175
176     /* Launching the directory monitoring thread */
177     monitoring_thread_t *p_mon =
178             vlc_object_create( p_ml, sizeof( monitoring_thread_t ) );
179     if( !p_mon )
180     {
181         vlc_mutex_destroy( &p_ml->p_sys->lock );
182         sql_Destroy( p_ml->p_sys->p_sql );
183         free( p_ml->p_sys );
184         return VLC_ENOMEM;
185     }
186     p_ml->p_sys->p_mon = p_mon;
187
188     p_mon->p_ml = p_ml;
189
190     if( vlc_clone( &p_mon->thread, RunMonitoringThread, p_mon,
191                 VLC_THREAD_PRIORITY_LOW ) )
192     {
193         msg_Err( p_ml, "cannot spawn the media library monitoring thread" );
194         vlc_mutex_destroy( &p_ml->p_sys->lock );
195         sql_Destroy( p_ml->p_sys->p_sql );
196         free( p_ml->p_sys );
197         vlc_object_release( p_mon );
198         return VLC_EGENERIC;
199     }
200     /* Starting the watching system (starts a thread) */
201     watch_Init( p_ml );
202
203     msg_Dbg( p_ml, "Media library module loaded successfully" );
204
205     return VLC_SUCCESS;
206 }
207
208
209 /**
210  * @brief Unload module
211  *
212  * @param obj the media library object
213  * @return Nothing
214  */
215 static void unload( vlc_object_t *obj )
216 {
217     media_library_t *p_ml = ( media_library_t* ) obj;
218
219     /* Stopping the watching system */
220     watch_Close( p_ml );
221
222     /* Stop the monitoring thread */
223     vlc_cancel( p_ml->p_sys->p_mon->thread );
224     vlc_join( p_ml->p_sys->p_mon->thread, NULL );
225     vlc_object_release( p_ml->p_sys->p_mon );
226
227     /* Destroy the variable */
228     var_Destroy( p_ml, "media-meta-change" );
229     var_Destroy( p_ml, "media-deleted" );
230     var_Destroy( p_ml, "media-added" );
231
232     /* Empty the media pool */
233     ml_media_t* item;
234     FOREACH_ARRAY( item, p_ml->p_sys->mediapool )
235         ml_gc_decref( item );
236     FOREACH_END()
237     vlc_mutex_destroy( &p_ml->p_sys->pool_mutex );
238
239     sql_Destroy( p_ml->p_sys->p_sql );
240
241     vlc_mutex_destroy( &p_ml->p_sys->lock );
242
243     free( p_ml->p_sys );
244 }
245
246 /**
247  * @brief Get results of an SQL-Query on the database (please : free the result)
248  *
249  * @param p_ml the media library object
250  * @param ppp_res char *** in which to store the table of results (allocated)
251  * @param pi_rows resulting row number in table
252  * @param pi_cols resulting column number in table
253  * @param psz_fmt query command with printf-like format enabled
254  * @param va_args format the command
255  * @return VLC_SUCCESS or a VLC error code
256  */
257 int Query( media_library_t *p_ml,
258               char ***ppp_res, int *pi_rows, int *pi_cols,
259               const char *psz_fmt, ... )
260 {
261     va_list argp;
262     va_start( argp, psz_fmt );
263
264     int i_ret = QueryVa( p_ml, ppp_res, pi_rows, pi_cols, psz_fmt, argp );
265
266     va_end( argp );
267     return i_ret;
268 }
269
270 /**
271  * @brief Get results of an SQL-Query on the database (please : free the result)
272  *
273  * @param p_ml the media library object
274  * @param ppp_res char *** in which to store the table of results (allocated)
275  * @param pi_rows resulting row number in table
276  * @param pi_cols resulting column number in table
277  * @param psz_fmt query command with printf-like format enabled
278  * @param va_args format the command
279  * @return VLC_SUCCESS or a VLC error code
280  */
281 int QueryVa( media_library_t *p_ml, char ***ppp_res,
282                       int *pi_rows, int *pi_cols, const char *psz_fmt,
283                       va_list argp )
284 {
285     assert( p_ml );
286     if( !ppp_res || !psz_fmt ) return VLC_EGENERIC;
287
288     char *psz_query = sql_VPrintf( p_ml->p_sys->p_sql, psz_fmt, argp );
289     if( !psz_query )
290         return VLC_ENOMEM;
291
292     int i_ret = sql_Query( p_ml->p_sys->p_sql, psz_query,
293                            ppp_res, pi_rows, pi_cols );
294
295     free( psz_query );
296     return i_ret;
297 }
298
299 /**
300  * @brief Do a SQL-query without any data coming back
301  *
302  * @param p_ml the media library object
303  * @param psz_fmt query command with printf-like format enabled
304  * @param va_args format the command
305  * @return VLC_SUCCESS or a VLC error code
306  */
307 int QuerySimple( media_library_t *p_ml,
308                     const char *psz_fmt, ... )
309 {
310     va_list argp;
311     va_start( argp, psz_fmt );
312
313     int i_ret = QuerySimpleVa( p_ml, psz_fmt, argp );
314
315     va_end( argp );
316     return i_ret;
317 }
318
319 /**
320  * @brief Do a SQL-query without any data coming back
321  *
322  * @param p_ml the media library object
323  * @param psz_fmt query command with printf-like format enabled
324  * @param argp format the command
325  * @return VLC_SUCCESS or a VLC error code
326  */
327 int QuerySimpleVa( media_library_t *p_ml,
328                       const char *psz_fmt, va_list argp )
329 {
330     assert( p_ml );
331
332     int i_ret = VLC_SUCCESS;
333     int i_rows, i_cols;
334     char **pp_results = NULL;
335
336     i_ret = QueryVa( p_ml, &pp_results, &i_rows, &i_cols, psz_fmt, argp );
337
338     FreeSQLResult( p_ml, pp_results );
339     va_end( argp );
340
341     return i_ret;
342 }
343
344 /**
345  * @brief Transforms a string to a ml_result_t, with given type and id (as psz)
346  *
347  * @param res the result of the function
348  * @param psz string to transform into a result
349  * @param psz_id id as a string
350  * @param result_type type of the result
351  * @return ID or a VLC error code
352  */
353 int StringToResult( ml_result_t *p_result, const char *psz,
354                     const char *psz_id, ml_result_type_e result_type )
355 {
356     memset( &p_result->value, 0, sizeof( p_result->value ) );
357
358     p_result->id = psz_id ? atoi( psz_id ) : 0;
359     p_result->type = result_type;
360
361     switch( result_type )
362     {
363         case ML_TYPE_INT:
364             p_result->value.i = psz ? atoi( psz ) : 0;
365             break;
366
367         case ML_TYPE_TIME:
368             p_result->value.time = psz ? ( mtime_t ) atoi( psz )
369                                        : ( mtime_t ) 0LL;
370             break;
371
372         case ML_TYPE_PSZ:
373             p_result->value.psz = psz ? strdup( psz ) : NULL;
374             break;
375
376         case ML_TYPE_MEDIA:
377         default:
378             /* This is an error */
379             return VLC_EGENERIC;
380     }
381
382     return p_result->id;
383 }
384
385
386 /**
387  * @brief fills an ml_result_array_t with result of an SQL query
388  *
389  * @param p_ml the media library object
390  * @param p_media ml_result_array_t object to fill
391  * @param pp_results result of sql query
392  * @param i_rows row number
393  * @param i_cols column number
394  * @param result_type type of the result
395  * @return VLC_SUCCESS or a VLC error code
396  **/
397 int SQLToResultArray( media_library_t *p_ml, vlc_array_t *p_result_array,
398                       char **pp_results, int i_rows, int i_cols,
399                       ml_result_type_e result_type )
400 {
401     assert( p_ml );
402     if( !p_result_array )
403         return VLC_EGENERIC;
404     if( i_cols == 0 )   /* No result */
405         return VLC_SUCCESS;
406     if( i_cols < 0 )
407     {
408         msg_Err( p_ml, "negative number of columns in result ?" );
409         return VLC_EGENERIC;
410     }
411
412     if( i_cols == 1 )
413     {
414         for( int i = 1; i <= i_rows; i++ )
415         {
416             ml_result_t *res = ( ml_result_t* )
417                                     calloc( 1, sizeof( ml_result_t ) );
418             if( !res )
419                 return VLC_ENOMEM;
420             StringToResult( res, pp_results[ i ], NULL, result_type );
421             vlc_array_append( p_result_array, res );
422         }
423     }
424     /* FIXME?: Assuming all double column results are id - result pairs */
425     else if( ( i_cols == 2 ) )
426     {
427         for( int i = 1; i <= i_rows; i++ )
428         {
429             ml_result_t *res = ( ml_result_t* )
430                                     calloc( 1, sizeof( ml_result_t ) );
431             if( !res )
432                 return VLC_ENOMEM;
433             StringToResult( res, pp_results[ i * 2 + 1], pp_results[ i * 2 ],
434                             result_type );
435             vlc_array_append( p_result_array, res );
436         }
437     }
438     else if( result_type == ML_TYPE_MEDIA )
439     {
440         return SQLToMediaArray( p_ml, p_result_array,
441                                 pp_results, i_rows, i_cols );
442     }
443     else
444     {
445         msg_Err( p_ml, "unable to convert SQL result to a ml_result_t array" );
446         return VLC_EGENERIC;
447     }
448     return VLC_SUCCESS;
449 }
450
451
452 /**
453  * @brief fills a vlc_array_t with results of an SQL query
454  *        medias in ml_result_t
455  *
456  * @param p_ml the media library object
457  * @param p_array array to fill with ml_media_t elements (might be initialized)
458  * @param pp_results result of sql query
459  * @param i_rows row number
460  * @param i_cols column number
461  * @return VLC_SUCCESS or a VLC error code
462  * Warning: this returns VLC_EGENERIC if i_rows == 0 (empty result)
463  **/
464 int SQLToMediaArray( media_library_t *p_ml, vlc_array_t *p_result_array,
465                      char **pp_results, int i_rows, int i_cols )
466 {
467     int i_ret = VLC_SUCCESS;
468     assert( p_ml );
469
470     #define res( i, j ) ( pp_results[ i * i_cols + j ] )
471     #define atoinull( a ) ( (a) ? atoi( a ) : 0 )
472     #define strdupnull( a ) ( (a) ? strdup( a ) : NULL )
473
474     if( i_rows == 0 )
475         return VLC_EGENERIC;
476
477     if( !p_result_array || !pp_results || i_rows < 0 || i_cols <= 0 )
478     {
479         msg_Warn( p_ml, "bad arguments (%s:%d)", __FILE__, __LINE__ );
480         return VLC_EGENERIC;
481     }
482
483     vlc_array_t* p_intermediate_array = vlc_array_new();
484
485     /* Analyze first row */
486     int *indexes = ( int* ) calloc( i_cols + 1, sizeof( int ) );
487     if( !indexes )
488     {
489         vlc_array_destroy( p_intermediate_array );
490         return VLC_ENOMEM;
491     }
492
493     const int count = sizeof( ml_table_map )/ sizeof( struct ml_table_elt );
494     for( int col = 0; col < i_cols; col++ )
495     {
496         struct ml_table_elt key, *result = NULL;
497         key.column_name = res( 0, col );
498         result = bsearch( &key, ml_table_map, count,
499                 sizeof( struct ml_table_elt ), compare_ml_elts );
500
501         if( !result )
502             msg_Warn( p_ml, "unknown column: %s", res( 0, col ) );
503         else
504             indexes[col] = result->column_id;
505     }
506
507     /* Read rows 1 to i_rows */
508     ml_media_t  *p_media  = NULL;
509     ml_result_t *p_result = NULL;
510
511     for( int row = 1; ( row <= i_rows ) && ( i_ret == VLC_SUCCESS ); row++ )
512     {
513         p_media = media_New( p_ml, 0, ML_MEDIA, false );
514         if( !p_media )
515         {
516             free( indexes );
517             i_ret = VLC_ENOMEM;
518             goto quit_sqlmediaarray;
519         }
520         p_result = ( ml_result_t * ) calloc( 1, sizeof( ml_result_t ) );
521         if( !p_result )
522         {
523             ml_gc_decref( p_media );
524             free( indexes );
525             i_ret = VLC_ENOMEM;
526             goto quit_sqlmediaarray;
527         }
528
529         char* psz_append_pname = NULL;
530         char* psz_append_prole = NULL;
531         int i_append_pid = 0;
532
533 #define SWITCH_INT( key, value ) case key: \
534         p_media-> value = atoinull( res( row, col ) );
535 #define SWITCH_PSZ( key, value ) case key: \
536         p_media-> value = strdupnull( res( row, col ) );
537
538         ml_LockMedia( p_media );
539         for( int col = 0; ( col < i_cols ) && ( i_ret == VLC_SUCCESS ); col++ )
540         {
541             switch( indexes[ col ] )
542             {
543                 SWITCH_INT( ML_ALBUM_ID, i_album_id );
544                 SWITCH_PSZ( ML_ALBUM, psz_album );
545                 SWITCH_PSZ( ML_COMMENT, psz_comment );
546                 SWITCH_INT( ML_DISC_NUMBER, i_disc_number );
547                 SWITCH_INT( ML_DURATION, i_duration );
548                 SWITCH_PSZ( ML_EXTRA, psz_extra );
549                 SWITCH_INT( ML_FILESIZE, i_filesize );
550                 SWITCH_INT( ML_FIRST_PLAYED, i_first_played );
551                 SWITCH_PSZ( ML_GENRE, psz_genre);
552                 SWITCH_INT( ML_IMPORT_TIME, i_import_time );
553                 SWITCH_PSZ( ML_LANGUAGE, psz_language );
554                 SWITCH_INT( ML_LAST_PLAYED, i_last_played );
555                 SWITCH_INT( ML_LAST_SKIPPED, i_last_skipped );
556                 SWITCH_PSZ( ML_ORIGINAL_TITLE, psz_orig_title );
557                 SWITCH_INT( ML_PLAYED_COUNT, i_played_count );
558                 SWITCH_PSZ( ML_PREVIEW, psz_preview );
559                 SWITCH_INT( ML_SCORE, i_score );
560                 SWITCH_INT( ML_SKIPPED_COUNT, i_skipped_count );
561                 SWITCH_PSZ( ML_TITLE, psz_title );
562                 SWITCH_INT( ML_TRACK_NUMBER, i_track_number );
563                 SWITCH_INT( ML_TYPE, i_type );
564                 SWITCH_INT( ML_VOTE, i_vote);
565                 SWITCH_INT( ML_YEAR, i_year );
566             case ML_ALBUM_COVER:
567                 /* See ML_COVER */
568                 // Discard attachment://
569                 if( !p_media->psz_cover || !*p_media->psz_cover
570                  || !strncmp( p_media->psz_cover, "attachment://", 13 ) )
571                 {
572                     free( p_media->psz_cover );
573                     p_media->psz_cover = strdupnull( res( row, col ) );
574                 }
575                 break;
576             case ML_PEOPLE:
577                 psz_append_pname = strdupnull( res( row, col ) );
578                 break;
579             case ML_PEOPLE_ID:
580                 i_append_pid = atoinull( res( row, col ) );
581                 break;
582             case ML_PEOPLE_ROLE:
583                 psz_append_prole = strdupnull( res( row, col ) );
584                 break;
585             case ML_COVER:
586                 /* See ML_ALBUM_COVER */
587                 if( !p_media->psz_cover || !*p_media->psz_cover
588                      || !strncmp( p_media->psz_cover, "attachment://", 13 ) )
589                 {
590                     free( p_media->psz_cover );
591                     p_media->psz_cover = strdupnull( res( row, col ) );
592                 }
593                 break;
594             case ML_ID:
595                 p_media->i_id = atoinull( res( row, col ) );
596                 if( p_media->i_id <= 0 )
597                     msg_Warn( p_ml, "entry with id null or inferior to zero" );
598                 break;
599             case ML_URI:
600                 p_media->psz_uri = strdupnull( res( row, col ) );
601                 if( !p_media->psz_uri )
602                     msg_Warn( p_ml, "entry without uri" );
603                 break;
604             case ML_DIRECTORY:
605                 break; // The column directory_id is'nt part of the media model
606             default:
607                 msg_Warn( p_ml, "unknown element, row %d column %d (of %d) - %s - %s",
608                         row, col, i_cols, res( 0 , col ), res( row, col ) );
609                 break;
610             }
611         }
612
613 #undef SWITCH_INT
614 #undef SWITCH_PSZ
615         int i_appendrow;
616         ml_result_t* p_append = NULL;
617         for( i_appendrow = 0; i_appendrow < vlc_array_count( p_intermediate_array ); i_appendrow++ )
618         {
619             p_append = ( ml_result_t* )
620                 vlc_array_item_at_index( p_intermediate_array, i_appendrow );
621             if( p_append->id == p_media->i_id )
622                 break;
623         }
624         if( i_appendrow == vlc_array_count( p_intermediate_array ) )
625         {
626             p_result->id   = p_media->i_id;
627             p_result->type = ML_TYPE_MEDIA;
628             p_result->value.p_media = p_media;
629             if( psz_append_pname && i_append_pid && psz_append_prole )
630                 ml_CreateAppendPersonAdv( &(p_result->value.p_media->p_people),
631                         psz_append_prole, psz_append_pname, i_append_pid );
632             vlc_array_append( p_intermediate_array, p_result );
633             ml_UnlockMedia( p_media );
634         }
635         else /* This is a repeat row and the people need to be put together */
636         {
637             free( p_result );
638             ml_LockMedia( p_append->value.p_media );
639             if( psz_append_pname && i_append_pid && psz_append_prole )
640                 ml_CreateAppendPersonAdv( &(p_append->value.p_media->p_people),
641                         psz_append_prole, psz_append_pname, i_append_pid );
642             ml_UnlockMedia( p_append->value.p_media );
643             ml_UnlockMedia( p_media );
644             ml_gc_decref( p_media );
645         }
646         FREENULL( psz_append_prole );
647         FREENULL( psz_append_pname );
648         i_append_pid = 0;
649     }
650     p_media = NULL;
651     free( indexes );
652
653     /* Now check if these medias are already on the pool, and sync */
654     for( int i = 0; i < vlc_array_count( p_intermediate_array ); i++ )
655     {
656         p_result =
657             ( ml_result_t* )vlc_array_item_at_index( p_intermediate_array, i );
658         p_media = p_result->value.p_media;
659         ml_media_t* p_poolmedia = pool_GetMedia( p_ml, p_result->id );
660         /* TODO: Pool_syncMedia might be cleaner? */
661
662         p_result = ( ml_result_t* ) calloc( 1, sizeof( ml_result_t * ) );
663         if( !p_result )
664             goto quit_sqlmediaarray;
665         if( p_poolmedia )
666         {
667             /* TODO: This might cause some weird stuff to occur w/ GC? */
668             ml_CopyMedia( p_poolmedia, p_media );
669             p_result->id = p_poolmedia->i_id;
670             p_result->type = ML_TYPE_MEDIA;
671             p_result->value.p_media = p_poolmedia;
672             vlc_array_append( p_result_array, p_result );
673         }
674         else
675         {
676             i_ret = pool_InsertMedia( p_ml, p_media, false );
677             if( i_ret == VLC_SUCCESS )
678             {
679                 ml_gc_incref( p_media );
680                 p_result->id = p_media->i_id;
681                 p_result->type = ML_TYPE_MEDIA;
682                 p_result->value.p_media = p_media;
683                 vlc_array_append( p_result_array, p_result );
684             }
685         }
686     }
687
688     #undef strdupnull
689     #undef atoinull
690     #undef res
691 quit_sqlmediaarray:
692     for( int k = 0; k < vlc_array_count( p_intermediate_array ); k++ )
693     {
694         ml_result_t* temp = ((ml_result_t*)vlc_array_item_at_index( p_intermediate_array, k ));
695         ml_FreeResult( temp );
696     }
697     vlc_array_destroy( p_intermediate_array );
698     return i_ret;
699 }
700
701
702 /**
703  * @brief Returns (unique) ID of media with specified URI
704  *
705  * @param p_ml the media library object
706  * @param psz_uri URI to look for
707  * @return i_id: (first) ID found, VLC_EGENERIC in case of error
708  * NOTE: Normally, there should not be more than one ID with one URI
709  */
710 int GetMediaIdOfURI( media_library_t *p_ml, const char *psz_uri )
711 {
712     int i_ret = VLC_EGENERIC;
713     vlc_array_t *p_array = vlc_array_new();
714     i_ret = Find( p_ml, p_array, ML_ID, ML_URI, psz_uri, ML_LIMIT, 1, ML_END );
715     if( ( i_ret == VLC_SUCCESS )
716         && ( vlc_array_count( p_array ) > 0 )
717         && vlc_array_item_at_index( p_array, 0 ) )
718     {
719         i_ret = ( (ml_result_t*)vlc_array_item_at_index( p_array, 0 ) )
720                         ->value.i;
721     }
722     else
723     {
724         i_ret = VLC_EGENERIC;
725     }
726     vlc_array_destroy( p_array );
727     return i_ret;
728 }
729
730
731 /**
732  * @brief Control function for media library
733  *
734  * @param p_ml Media library handle
735  * @param i_query query type
736  * @param args query arguments
737  * @return VLC_SUCCESS if ok
738  */
739 int Control( media_library_t *p_ml, int i_query, va_list args )
740 {
741     switch( i_query )
742     {
743     case ML_ADD_INPUT_ITEM:
744     {
745         input_item_t *p_item = (input_item_t *)va_arg( args, input_item_t * );
746         return AddInputItem( p_ml, p_item );
747     }
748
749     case ML_ADD_PLAYLIST_ITEM:
750     {
751         playlist_item_t *p_item = (playlist_item_t *)va_arg( args, playlist_item_t * );
752         return AddPlaylistItem( p_ml, p_item );
753     }
754
755     case ML_ADD_MONITORED:
756     {
757         char *psz_dir = (char *)va_arg( args, char * );
758         return AddDirToMonitor( p_ml, psz_dir );
759     }
760
761     case ML_GET_MONITORED:
762     {
763         vlc_array_t *p_array = (vlc_array_t *)va_arg( args, vlc_array_t * );
764         return ListMonitoredDirs( p_ml, p_array );
765     }
766
767     case ML_DEL_MONITORED:
768     {
769         char *psz_dir = (char *)va_arg( args, char * );
770         return RemoveDirToMonitor( p_ml, psz_dir );
771     }
772
773     default:
774         return VLC_EGENERIC;
775     }
776 }
777
778
779 /**
780  * @brief Create a new (empty) database. The database might be initialized
781  *
782  * @param p_ml This ML
783  * @return VLC_SUCCESS or VLC_EGENERIC
784  * @note This function is transactional
785  */
786 int CreateEmptyDatabase( media_library_t *p_ml )
787 {
788     assert( p_ml );
789     int i_ret = VLC_SUCCESS;
790     msg_Dbg( p_ml, "creating a new (empty) database" );
791
792     Begin( p_ml );
793
794     /* Albums */
795     i_ret= QuerySimple( p_ml,
796                         "CREATE TABLE album ( "
797                         "id INTEGER PRIMARY KEY,"
798                         "album_artist_id INTEGER,"
799                         "title VARCHAR(1024),"
800                         "cover VARCHAR(1024) )" );
801     if( i_ret != VLC_SUCCESS )
802         goto quit_createemptydatabase;
803
804     /* Add "unknown" entry to albums */
805     i_ret = QuerySimple( p_ml,
806                         "INSERT INTO album ( id, title, cover, album_artist_id ) "
807                         "VALUES ( 0, 'Unknown', '', 0 )" );
808
809     if( i_ret != VLC_SUCCESS )
810         goto quit_createemptydatabase;
811
812     /* Main media table */
813     i_ret= QuerySimple( p_ml,
814                         "CREATE TABLE media ( "
815                         "id INTEGER PRIMARY KEY,"
816                         "timestamp INTEGER,"            /* File timestamp */
817                         "uri VARCHAR(1024),"
818                         "type INTEGER,"
819                         "title VARCHAR(1024),"
820                         "original_title VARCHAR(1024),"
821                         "album_id INTEGER,"
822                         "cover VARCHAR(1024),"
823                         "preview VARCHAR(1024),"        /* Video preview */
824                         "track INTEGER,"                /* Track number */
825                         "disc INTEGER,"                 /* Disc number */
826                         "year INTEGER,"
827                         "genre VARCHAR(1024),"
828                         "vote INTEGER,"                 /* Rating/Stars */
829                         "score INTEGER,"                /* ML score/rating */
830                         "comment VARCHAR(1024),"        /* Comment */
831                         "filesize INTEGER,"
832                         /* Dates and times */
833                         "duration INTEGER,"             /* Length of media */
834                         "played_count INTEGER,"
835                         "last_played DATE,"
836                         "first_played DATE,"
837                         "import_time DATE,"
838                         "skipped_count INTEGER,"
839                         "last_skipped DATE,"
840                         "directory_id INTEGER,"
841                         "CONSTRAINT associated_album FOREIGN KEY(album_id) "
842             "REFERENCES album(id) ON DELETE SET DEFAULT ON UPDATE RESTRICT)" );
843     if( i_ret != VLC_SUCCESS )
844         goto quit_createemptydatabase;
845
846     /* People */
847     i_ret = QuerySimple( p_ml,
848                         "CREATE TABLE people ( "
849                         "id INTEGER PRIMARY KEY,"
850                         "name VARCHAR(1024) ,"
851                         "role VARCHAR(1024) )" );
852     if( i_ret != VLC_SUCCESS )
853         goto quit_createemptydatabase;
854
855     /* Media to people */
856     i_ret = QuerySimple( p_ml,
857                         "CREATE TABLE media_to_people ( "
858                         "media_id INTEGER, "
859                         "people_id INTEGER, "
860                         "PRIMARY KEY( media_id, people_id ), "
861                         "CONSTRAINT associated_people FOREIGN KEY(people_id) "
862             "REFERENCES people(id) ON DELETE SET DEFAULT ON UPDATE RESTRICT, "
863                         "CONSTRAINT associated_media FOREIGN KEY(media_id) "
864             "REFERENCES media(id) ON DELETE CASCADE ON UPDATE RESTRICT )" );
865     if( i_ret != VLC_SUCCESS )
866         goto quit_createemptydatabase;
867
868     /* Add "unknown" entry to people */
869     i_ret = QuerySimple( p_ml,
870                         "INSERT INTO people ( id, name, role ) "
871                         "VALUES ( 0, 'Unknown', NULL )" );
872     if( i_ret != VLC_SUCCESS )
873         goto quit_createemptydatabase;
874
875     /* recursive is set to 1 if the directory is added to the database
876        by recursion and 0 if not */
877     i_ret = QuerySimple( p_ml,
878                         "CREATE TABLE directories ( "
879                         "id INTEGER PRIMARY KEY,"
880                         "uri VARCHAR(1024),"
881                         "timestamp INTEGER,"
882                         "recursive INTEGER )" );
883     if( i_ret != VLC_SUCCESS )
884         goto quit_createemptydatabase;
885
886     /* Create information table
887      * This table should have one row and the version number is the version
888      * of the database
889      * Other information may be stored here at later stages */
890     i_ret = QuerySimple( p_ml,
891                         "CREATE TABLE information ( "
892                         "version INTEGER PRIMARY KEY )" );
893     if( i_ret != VLC_SUCCESS )
894         goto quit_createemptydatabase;
895
896     /* Insert current DB version */
897     i_ret = QuerySimple( p_ml,
898                         "INSERT INTO information ( version ) "
899                         "VALUES ( %d )", ML_DBVERSION );
900     if( i_ret != VLC_SUCCESS )
901         goto quit_createemptydatabase;
902
903     /* Text data: song lyrics or subtitles */
904     i_ret = QuerySimple( p_ml,
905                         "CREATE TABLE extra ( "
906                         "id INTEGER PRIMARY KEY,"
907                         "extra TEXT,"
908                         "language VARCHAR(256),"
909                         "bitrate INTEGER,"
910                         "samplerate INTEGER,"
911                         "bpm INTEGER )" );
912     if( i_ret != VLC_SUCCESS )
913         goto quit_createemptydatabase;
914
915     /* Emulating foreign keys with triggers */
916     /* Warning: Lots of SQL */
917     if( !strcmp( module_get_name( p_ml->p_sys->p_sql->p_module, false ),
918         "SQLite" ) )
919     {
920     i_ret = QuerySimple( p_ml,
921     "\nCREATE TRIGGER genfkey1_insert_referencing BEFORE INSERT ON \"media\" WHEN\n"
922     "    new.\"album_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"album\" WHERE new.\"album_id\" == \"id\")\n"
923     "BEGIN\n"
924     "  SELECT RAISE(ABORT, 'constraint genfkey1_insert_referencing failed. Cannot insert album_id into media. Album did not exist');\n"
925     "END;\n"
926     "\n"
927     "CREATE TRIGGER genfkey1_update_referencing BEFORE\n"
928     "    UPDATE OF album_id ON \"media\" WHEN \n"
929     "    new.\"album_id\" IS NOT NULL AND \n"
930     "    NOT EXISTS (SELECT 1 FROM \"album\" WHERE new.\"album_id\" == \"id\")\n"
931     "BEGIN\n"
932     "  SELECT RAISE(ABORT, 'constraint genfkey1_update_referencing failed. Cannot update album_id in media. Album did not exist');\n"
933     "END;\n"
934     "\n"
935     "CREATE TRIGGER genfkey1_delete_referenced BEFORE DELETE ON \"album\" WHEN\n"
936     "    EXISTS (SELECT 1 FROM \"media\" WHERE old.\"id\" == \"album_id\")\n"
937     "BEGIN\n"
938     "  SELECT RAISE(ABORT, 'constraint genfkey1_delete_referenced failed. Cannot delete album, media still exist');\n"
939     "END;\n"
940     "\n"
941     "\n"
942     "CREATE TRIGGER genfkey1_update_referenced AFTER\n"
943     "    UPDATE OF id ON \"album\" WHEN \n"
944     "    EXISTS (SELECT 1 FROM \"media\" WHERE old.\"id\" == \"album_id\")\n"
945     "BEGIN\n"
946     "  SELECT RAISE(ABORT, 'constraint genfkey1_update_referenced failed. Cannot change album id in album, media still exist');\n"
947     "END;\n"
948     "\n"
949     "\n"
950     "CREATE TRIGGER genfkey2_insert_referencing BEFORE INSERT ON \"media_to_people\" WHEN \n"
951     "    new.\"media_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"media\" WHERE new.\"media_id\" == \"id\")\n"
952     "BEGIN\n"
953     "  SELECT RAISE(ABORT, 'constraint genfkey2_insert_referencing failed. Cannot insert into media_to_people, that media does not exist');\n"
954     "END;\n"
955     "\n"
956     "CREATE TRIGGER genfkey2_update_referencing BEFORE\n"
957     "    UPDATE OF media_id ON \"media_to_people\" WHEN \n"
958     "    new.\"media_id\" IS NOT NULL AND \n"
959     "    NOT EXISTS (SELECT 1 FROM \"media\" WHERE new.\"media_id\" == \"id\")\n"
960     "BEGIN\n"
961     "  SELECT RAISE(ABORT, 'constraint genfkey2_update_referencing failed. Cannot update media_to_people, that media does not exist');\n"
962     "END;\n"
963     "\n"
964     "CREATE TRIGGER genfkey2_delete_referenced BEFORE DELETE ON \"media\" WHEN\n"
965     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"media_id\")\n"
966     "BEGIN\n"
967     "  DELETE FROM \"media_to_people\" WHERE \"media_id\" = old.\"id\";\n"
968     "END;\n"
969     "\n"
970     "CREATE TRIGGER genfkey2_update_referenced AFTER\n"
971     "    UPDATE OF id ON \"media\" WHEN \n"
972     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"media_id\")\n"
973     "BEGIN\n"
974     "  SELECT RAISE(ABORT, 'constraint genfkey2_update_referenced failed. Cannot update media id, refs still exist in media_to_people');\n"
975     "END;\n"
976     "\n"
977     "CREATE TRIGGER genfkey3_insert_referencing BEFORE INSERT ON \"media_to_people\" WHEN \n"
978     "    new.\"people_id\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"people\" WHERE new.\"people_id\" == \"id\")\n"
979     "BEGIN\n"
980     "  SELECT RAISE(ABORT, 'constraint genfkey3_insert_referencing failed. Cannot insert into media_to_people, people does not exist');\n"
981     "END;\n"
982     "CREATE TRIGGER genfkey3_update_referencing BEFORE\n"
983     "    UPDATE OF people_id ON \"media_to_people\" WHEN \n"
984     "    new.\"people_id\" IS NOT NULL AND \n"
985     "    NOT EXISTS (SELECT 1 FROM \"people\" WHERE new.\"people_id\" == \"id\")\n"
986     "BEGIN\n"
987     "  SELECT RAISE(ABORT, 'constraint genfkey3_update_referencing failed. Cannot update media_to_people, people does not exist');\n"
988     "END;\n"
989     "\n"
990     "CREATE TRIGGER genfkey3_delete_referenced BEFORE DELETE ON \"people\" WHEN\n"
991     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"people_id\")\n"
992     "BEGIN\n"
993     "  UPDATE media_to_people SET people_id = 0 WHERE people_id == old.\"id\";\n"
994     "END;\n"
995     "\n"
996     "CREATE TRIGGER genfkey3_update_referenced AFTER\n"
997     "    UPDATE OF id ON \"people\" WHEN \n"
998     "    EXISTS (SELECT 1 FROM \"media_to_people\" WHERE old.\"id\" == \"people_id\")\n"
999     "BEGIN\n"
1000     "  SELECT RAISE(ABORT, 'constraint genfkey3_update_referenced failed. Cannot update people_id, people does not exist');\n"
1001     "END;\n"
1002     "\n"
1003     "CREATE TRIGGER keep_people_clean AFTER \n"
1004     "    DELETE ON \"media_to_people\"\n"
1005     "    WHEN NOT EXISTS( SELECT 1 from \"media_to_people\" WHERE old.\"people_id\" == \"people_id\" )\n"
1006     "BEGIN\n"
1007     "    DELETE FROM people WHERE people.id = old.\"people_id\" AND people.id != 0;\n"
1008     "END;\n"
1009     "\n"
1010     "CREATE TRIGGER keep_album_clean AFTER\n"
1011     "    DELETE ON \"media\"\n"
1012     "    WHEN NOT EXISTS( SELECT 1 FROM \"media\" WHERE old.\"album_id\" == \"album_id\" )\n"
1013     "BEGIN\n"
1014     "    DELETE FROM album WHERE album.id = old.\"album_id\" AND album.id != 0;\n"
1015     "END;" );
1016     if( i_ret != VLC_SUCCESS )
1017         goto quit_createemptydatabase;
1018     }
1019
1020 quit_createemptydatabase:
1021     if( i_ret == VLC_SUCCESS )
1022         Commit( p_ml );
1023     else
1024         Rollback( p_ml );
1025     return VLC_SUCCESS;
1026 }
1027
1028
1029 /**
1030  * @brief Initiates database (create the database and the tables if needed)
1031  *
1032  * @param p_ml This ML
1033  * @return VLC_SUCCESS or an error code
1034  */
1035 int InitDatabase( media_library_t *p_ml )
1036 {
1037     assert( p_ml );
1038     msg_Dbg( p_ml, "initializing database" );
1039
1040     /* Select database name */
1041     char *psz_dbhost = NULL, *psz_user = NULL, *psz_pass = NULL;
1042     int i_port = 0;
1043     psz_dbhost = config_GetPsz( p_ml, "ml-filename" );
1044     psz_user = config_GetPsz( p_ml, "ml-username" );
1045     psz_pass = config_GetPsz( p_ml, "ml-password" );
1046     i_port = config_GetInt( p_ml, "ml-port" );
1047
1048     /* Let's consider that a filename with a DIR_SEP is a full URL */
1049     if( strchr( psz_dbhost, DIR_SEP_CHAR ) == NULL )
1050     {
1051         char *psz_datadir = config_GetUserDir( VLC_DATA_DIR );
1052         char *psz_tmp = psz_dbhost;
1053         if( asprintf( &psz_dbhost, "%s" DIR_SEP "%s",
1054                       psz_datadir, psz_tmp ) == -1 )
1055         {
1056             free( psz_datadir );
1057             free( psz_tmp );
1058             return VLC_ENOMEM;
1059         }
1060         free( psz_datadir );
1061         free( psz_tmp );
1062     }
1063
1064     p_ml->p_sys->p_sql = sql_Create( p_ml, NULL, psz_dbhost, i_port, psz_user,
1065                                      psz_pass );
1066     if( !p_ml->p_sys->p_sql )
1067         return VLC_EGENERIC;
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 && 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_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 && !strncmp( p_media->psz_uri, "http", 4 ) )
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 pp_item A pointer to a new input_item (return value)
1357  * @param p_media The media to copy as an input item
1358  * @note This function is threadsafe
1359  */
1360 static int CreateInputItemFromMedia( input_item_t **pp_item,
1361                                      ml_media_t *p_media )
1362 {
1363     *pp_item = input_item_New( p_media->psz_uri, p_media->psz_title );
1364                                /* ITEM_TYPE_FILE ); */
1365     if( !*pp_item )
1366         return VLC_EGENERIC;
1367     CopyMediaToInputItem( *pp_item, p_media );
1368     return VLC_SUCCESS;
1369 }
1370
1371 /**
1372  * @brief Find the media_id associated to an input item
1373  * @param p_ml This
1374  * @param p_item Input item to look for
1375  * @return Media ID or <= 0 if not found
1376  */
1377 int GetMediaIdOfInputItem( media_library_t *p_ml, input_item_t *p_item )
1378 {
1379     int i_media_id = watch_get_mediaIdOfItem( p_ml, p_item );
1380     if( i_media_id <= 0 )
1381     {
1382         i_media_id = GetMediaIdOfURI( p_ml, p_item->psz_uri );
1383     }
1384     return i_media_id;
1385 }
1386
1387