2016-12-31 01:28:18 -05:00
/*
* Copyright ( C ) 2015 Christian Meffert < christian . meffert @ googlemail . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <errno.h>
2017-11-11 19:38:36 -05:00
# include <sys/stat.h>
2016-12-31 01:28:18 -05:00
# include <fcntl.h>
# include <limits.h>
# include <pthread.h>
2017-01-18 14:12:14 -05:00
# include <stdbool.h>
2016-12-31 01:28:18 -05:00
# include <stdlib.h>
# include <string.h>
2017-01-27 17:23:25 -05:00
# include <event2/event.h>
# include "library.h"
2017-01-01 05:05:18 -05:00
# include "cache.h"
2016-12-31 01:28:18 -05:00
# include "commands.h"
# include "conffile.h"
# include "db.h"
# include "logger.h"
# include "misc.h"
2017-01-27 17:23:25 -05:00
# include "listener.h"
2017-01-06 05:08:47 -05:00
# include "player.h"
2016-12-31 01:28:18 -05:00
2020-03-21 17:16:57 -04:00
# define LIBRARY_MAX_CALLBACKS 16
struct library_callback_register
{
library_cb cb ;
void * arg ;
2020-03-23 18:17:26 -04:00
struct event * ev ;
2020-03-21 17:16:57 -04:00
} ;
2020-01-20 17:13:47 -05:00
struct playlist_item_add_param
2017-08-09 12:19:20 -04:00
{
const char * vp_playlist ;
const char * vp_item ;
} ;
2020-01-20 17:13:47 -05:00
struct queue_item_add_param
{
const char * path ;
int position ;
char reshuffle ;
uint32_t item_id ;
int * count ;
int * new_item_id ;
} ;
2024-01-24 17:30:02 -05:00
struct item_param
{
const char * path ;
uint32_t id ;
enum library_attrib attrib ;
uint32_t value ;
} ;
2016-12-31 01:28:18 -05:00
static struct commands_base * cmdbase ;
static pthread_t tid_library ;
struct event_base * evbase_lib ;
extern struct library_source filescanner ;
2021-03-28 14:00:15 -04:00
# ifdef SPOTIFY
2016-12-31 01:28:18 -05:00
extern struct library_source spotifyscanner ;
# endif
2020-03-08 16:06:21 -04:00
extern struct library_source rssscanner ;
2016-12-31 01:28:18 -05:00
static struct library_source * sources [ ] = {
& filescanner ,
2021-03-28 14:00:15 -04:00
# ifdef SPOTIFY
2016-12-31 01:28:18 -05:00
& spotifyscanner ,
# endif
2020-03-08 16:06:21 -04:00
& rssscanner ,
2016-12-31 01:28:18 -05:00
NULL
} ;
2017-01-27 17:23:25 -05:00
/* Flag for aborting scan on exit */
static bool scan_exit ;
/* Flag for scan in progress */
static bool scanning ;
2017-11-11 18:06:27 -05:00
// After being told by db that the library was updated through
// library_update_trigger(), wait 5 seconds before notifying listeners
// of LISTENER_DATABASE. This is to catch bulk updates like automated
// tag editing, music file imports/renames. This way multiple updates
// are collected for a single update notification (useful to avoid
// repeated library reads from clients).
//
// Note: this update delay does not apply to library scans. The scans
// use the flag `scanning` for deferring update notifcations.
static struct timeval library_update_wait = { 5 , 0 } ;
2017-01-27 17:23:25 -05:00
static struct event * updateev ;
2017-11-12 03:28:25 -05:00
// Counts the number of changes made to the database between to DATABASE
// event notifications
2017-12-15 10:52:06 -05:00
static unsigned int deferred_update_notifications ;
static short deferred_update_events ;
2017-11-11 19:38:36 -05:00
2020-03-21 17:16:57 -04:00
// Stores callbacks that backends may have requested
static struct library_callback_register library_cb_register [ LIBRARY_MAX_CALLBACKS ] ;
2017-11-11 19:38:36 -05:00
2020-01-20 17:13:47 -05:00
/* ------------------- CALLED BY LIBRARY SOURCE MODULES -------------------- */
2016-12-31 01:28:18 -05:00
2020-01-31 17:38:05 -05:00
int
2020-01-20 17:13:47 -05:00
library_media_save ( struct media_file_info * mfi )
2016-12-31 01:28:18 -05:00
{
2024-01-24 17:30:02 -05:00
int ret ;
2021-12-28 03:19:44 -05:00
if ( ! mfi - > path | | ! mfi - > fname | | ! mfi - > scan_kind )
2016-12-31 01:28:18 -05:00
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_LOG , L_LIB , " Ignoring media file with missing values (path='%s', fname='%s', scan_kind='%d', data_kind='%d') \n " ,
mfi - > path , mfi - > fname , mfi - > scan_kind , mfi - > data_kind ) ;
2020-01-31 17:38:05 -05:00
return - 1 ;
2016-12-31 01:28:18 -05:00
}
2017-02-18 00:55:51 -05:00
if ( ! mfi - > directory_id | | ! mfi - > virtual_path )
2016-12-31 01:28:18 -05:00
{
2017-01-29 04:01:05 -05:00
// Missing informations for virtual_path and directory_id (may) lead to misplaced appearance in mpd clients
DPRINTF ( E_WARN , L_LIB , " Media file with missing values (path='%s', directory='%d', virtual_path='%s') \n " ,
mfi - > path , mfi - > directory_id , mfi - > virtual_path ) ;
2016-12-31 01:28:18 -05:00
}
if ( mfi - > id = = 0 )
2024-01-24 17:30:02 -05:00
ret = db_file_add ( mfi ) ;
2016-12-31 01:28:18 -05:00
else
2024-01-24 17:30:02 -05:00
ret = db_file_update ( mfi ) ;
return ret ;
2016-12-31 01:28:18 -05:00
}
2017-02-18 00:55:51 -05:00
int
2020-01-20 17:13:47 -05:00
library_playlist_save ( struct playlist_info * pli )
2017-02-18 00:55:51 -05:00
{
2021-12-28 03:19:44 -05:00
if ( ! pli - > path | | ! pli - > scan_kind )
2017-02-18 00:55:51 -05:00
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_LOG , L_LIB , " Ignoring playlist with missing values (path='%s', scan_kind='%d') \n " ,
pli - > path , pli - > scan_kind ) ;
2020-01-20 17:13:47 -05:00
return - 1 ;
2017-02-18 00:55:51 -05:00
}
2023-11-25 16:07:50 -05:00
// Missing virtual_path and directory_id (may) lead to misplaced appearance in
// mpd clients, but for e.g. spotify:playlistfolder they will not be set
2016-12-31 01:28:18 -05:00
2020-01-20 17:13:47 -05:00
if ( pli - > id = = 0 )
2020-02-02 17:40:37 -05:00
return db_pl_add ( pli ) ;
2020-01-20 17:13:47 -05:00
else
return db_pl_update ( pli ) ;
}
2016-12-31 01:28:18 -05:00
2021-12-28 03:19:44 -05:00
int
library_directory_save ( char * virtual_path , char * path , int disabled , int parent_id , enum scan_kind scan_kind )
{
struct directory_info di = { 0 } ;
int id ;
int ret ;
id = db_directory_id_byvirtualpath ( virtual_path ) ;
di . id = id ;
di . parent_id = parent_id ;
di . virtual_path = safe_strdup ( virtual_path ) ;
di . path = safe_strdup ( path ) ;
di . disabled = disabled ;
di . db_timestamp = ( uint64_t ) time ( NULL ) ;
di . scan_kind = scan_kind ;
if ( di . id = = 0 )
ret = db_directory_add ( & di , & id ) ;
else
ret = db_directory_update ( & di ) ;
free_di ( & di , 1 ) ;
if ( ret < 0 | | id < = 0 )
{
DPRINTF ( E_LOG , L_DB , " Insert or update of directory failed '%s' \n " , virtual_path ) ;
return - 1 ;
}
return id ;
}
2020-03-21 17:16:57 -04:00
static void
scheduled_cb ( int fd , short what , void * arg )
{
struct library_callback_register * cbreg = arg ;
2020-03-23 18:17:26 -04:00
library_cb cb = cbreg - > cb ;
void * cb_arg = cbreg - > arg ;
2020-03-21 17:16:57 -04:00
2020-03-23 18:17:26 -04:00
// Must reset the register before calling back, otherwise it won't work if the
// callback reschedules by calling library_callback_schedule()
event_free ( cbreg - > ev ) ;
2020-03-21 17:16:57 -04:00
memset ( cbreg , 0 , sizeof ( struct library_callback_register ) ) ;
2020-03-23 18:17:26 -04:00
DPRINTF ( E_DBG , L_LIB , " Executing library callback to %p \n " , cb ) ;
cb ( cb_arg ) ;
2020-03-21 17:16:57 -04:00
}
int
2020-03-23 18:17:26 -04:00
library_callback_schedule ( library_cb cb , void * arg , struct timeval * wait , enum library_cb_action action )
2020-03-21 17:16:57 -04:00
{
struct library_callback_register * cbreg ;
2020-03-23 18:17:26 -04:00
bool replace_done ;
int idx_available ;
int i ;
2020-03-21 17:16:57 -04:00
2020-03-23 18:17:26 -04:00
for ( i = 0 , idx_available = - 1 , replace_done = false ; i < ARRAY_SIZE ( library_cb_register ) ; i + + )
2020-03-21 17:16:57 -04:00
{
2020-03-23 18:17:26 -04:00
if ( idx_available = = - 1 & & library_cb_register [ i ] . cb = = NULL )
idx_available = i ;
if ( library_cb_register [ i ] . cb ! = cb )
continue ;
if ( action = = LIBRARY_CB_REPLACE | | action = = LIBRARY_CB_ADD_OR_REPLACE )
{
event_add ( library_cb_register [ i ] . ev , wait ) ;
library_cb_register [ i ] . arg = arg ;
replace_done = true ;
}
else if ( action = = LIBRARY_CB_DELETE )
{
event_free ( library_cb_register [ i ] . ev ) ;
memset ( & library_cb_register [ i ] , 0 , sizeof ( struct library_callback_register ) ) ;
}
2020-03-21 17:16:57 -04:00
}
2020-03-23 18:17:26 -04:00
if ( action = = LIBRARY_CB_REPLACE | | action = = LIBRARY_CB_DELETE | | ( action = = LIBRARY_CB_ADD_OR_REPLACE & & replace_done ) )
{
return 0 ; // All done
}
else if ( idx_available = = - 1 )
2020-03-21 17:16:57 -04:00
{
2020-03-23 18:17:26 -04:00
DPRINTF ( E_LOG , L_LIB , " Error scheduling callback, register full (size=%d, action=%d) \n " , LIBRARY_MAX_CALLBACKS , action ) ;
2020-03-21 17:16:57 -04:00
return - 1 ;
}
2020-03-23 18:17:26 -04:00
cbreg = & library_cb_register [ idx_available ] ;
2020-03-21 17:16:57 -04:00
cbreg - > cb = cb ;
cbreg - > arg = arg ;
2020-03-23 18:17:26 -04:00
if ( ! cbreg - > ev )
cbreg - > ev = evtimer_new ( evbase_lib , scheduled_cb , cbreg ) ;
CHECK_NULL ( L_LIB , cbreg - > ev ) ;
2020-03-21 17:16:57 -04:00
2020-03-23 18:17:26 -04:00
event_add ( cbreg - > ev , wait ) ;
2020-03-21 17:16:57 -04:00
2021-10-01 13:00:31 -04:00
DPRINTF ( E_DBG , L_LIB , " Added library callback to %p (id %d), wait %ld.%06ld \n " , cbreg - > cb , idx_available , ( long ) wait - > tv_sec , ( long ) wait - > tv_usec ) ;
2020-03-23 18:17:26 -04:00
return idx_available ;
2020-03-21 17:16:57 -04:00
}
2016-12-31 01:28:18 -05:00
2020-01-20 17:13:47 -05:00
/* ---------------------- LIBRARY ABSTRACTION --------------------- */
/* thread: library */
2016-12-31 01:28:18 -05:00
2020-01-20 17:13:47 -05:00
static bool
handle_deferred_update_notifications ( void )
{
time_t update_time ;
bool ret = ( deferred_update_notifications > 0 ) ;
2016-12-31 01:28:18 -05:00
2020-01-20 17:13:47 -05:00
if ( ret )
{
DPRINTF ( E_DBG , L_LIB , " Database changed (%d changes) \n " , deferred_update_notifications ) ;
2016-12-31 01:28:18 -05:00
2020-01-20 17:13:47 -05:00
deferred_update_notifications = 0 ;
update_time = time ( NULL ) ;
db_admin_setint64 ( DB_ADMIN_DB_UPDATE , ( int64_t ) update_time ) ;
db_admin_setint64 ( DB_ADMIN_DB_MODIFIED , ( int64_t ) update_time ) ;
2016-12-31 01:28:18 -05:00
}
2020-01-20 17:13:47 -05:00
return ret ;
2016-12-31 01:28:18 -05:00
}
2017-01-01 05:05:18 -05:00
static void
2021-12-28 03:19:44 -05:00
purge_cruft ( time_t start , enum scan_kind scan_kind )
2017-01-01 05:05:18 -05:00
{
DPRINTF ( E_DBG , L_LIB , " Purging old library content \n " ) ;
2021-12-28 03:19:44 -05:00
if ( scan_kind > 0 )
db_purge_cruft_bysource ( start , scan_kind ) ;
else
db_purge_cruft ( start ) ;
2017-01-01 05:05:18 -05:00
db_groups_cleanup ( ) ;
db_queue_cleanup ( ) ;
2021-12-28 03:19:44 -05:00
if ( scan_kind < = 0 )
{
DPRINTF ( E_DBG , L_LIB , " Purging old artwork content \n " ) ;
cache_artwork_purge_cruft ( start ) ;
}
2017-01-01 05:05:18 -05:00
}
2016-12-31 01:28:18 -05:00
static enum command_state
rescan ( void * arg , int * ret )
{
2021-12-28 03:19:44 -05:00
enum scan_kind * scan_kind ;
2016-12-31 01:28:18 -05:00
time_t starttime ;
time_t endtime ;
int i ;
2017-01-06 05:08:47 -05:00
DPRINTF ( E_LOG , L_LIB , " Library rescan triggered \n " ) ;
2017-08-06 11:39:32 -04:00
listener_notify ( LISTENER_UPDATE ) ;
2016-12-31 01:28:18 -05:00
starttime = time ( NULL ) ;
2021-12-28 03:19:44 -05:00
scan_kind = arg ;
2016-12-31 01:28:18 -05:00
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( ! sources [ i ] - > disabled & & sources [ i ] - > rescan )
2017-01-06 05:08:47 -05:00
{
2021-12-28 03:19:44 -05:00
if ( * scan_kind > 0 & & * scan_kind ! = sources [ i ] - > scan_kind )
{
DPRINTF ( E_DBG , L_LIB , " Skipping library source '%s' \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
}
else
{
DPRINTF ( E_INFO , L_LIB , " Rescan library source '%s' \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
sources [ i ] - > rescan ( ) ;
}
2017-01-06 05:08:47 -05:00
}
else
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_INFO , L_LIB , " Library source '%s' is disabled \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2017-01-06 05:08:47 -05:00
}
2016-12-31 01:28:18 -05:00
}
2021-12-28 03:19:44 -05:00
purge_cruft ( starttime , * scan_kind ) ;
2017-01-01 10:51:21 -05:00
2018-02-23 13:44:40 -05:00
DPRINTF ( E_DBG , L_LIB , " Running post library scan jobs \n " ) ;
db_hook_post_scan ( ) ;
2016-12-31 01:28:18 -05:00
endtime = time ( NULL ) ;
2017-11-12 03:28:25 -05:00
DPRINTF ( E_LOG , L_LIB , " Library rescan completed in %.f sec (%d changes) \n " , difftime ( endtime , starttime ) , deferred_update_notifications ) ;
2016-12-31 01:28:18 -05:00
scanning = false ;
2017-11-12 03:28:25 -05:00
if ( handle_deferred_update_notifications ( ) )
listener_notify ( LISTENER_UPDATE | LISTENER_DATABASE ) ;
else
listener_notify ( LISTENER_UPDATE ) ;
2017-08-06 11:39:32 -04:00
2016-12-31 01:28:18 -05:00
* ret = 0 ;
return COMMAND_END ;
}
2019-06-08 10:10:55 -04:00
static enum command_state
metarescan ( void * arg , int * ret )
{
2021-12-28 03:19:44 -05:00
enum scan_kind * scan_kind ;
2019-06-08 10:10:55 -04:00
time_t starttime ;
time_t endtime ;
int i ;
DPRINTF ( E_LOG , L_LIB , " Library meta rescan triggered \n " ) ;
listener_notify ( LISTENER_UPDATE ) ;
starttime = time ( NULL ) ;
2021-12-28 03:19:44 -05:00
scan_kind = arg ;
2019-06-08 10:10:55 -04:00
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( ! sources [ i ] - > disabled & & sources [ i ] - > metarescan )
{
2021-12-28 03:19:44 -05:00
if ( * scan_kind > 0 & & * scan_kind ! = sources [ i ] - > scan_kind )
{
DPRINTF ( E_DBG , L_LIB , " Skipping library source '%s' \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
}
else
{
DPRINTF ( E_INFO , L_LIB , " Meta rescan library source '%s' \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
sources [ i ] - > metarescan ( ) ;
}
2019-06-08 10:10:55 -04:00
}
else
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_INFO , L_LIB , " Library source '%s' is disabled \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2019-06-08 10:10:55 -04:00
}
}
2021-12-28 03:19:44 -05:00
purge_cruft ( starttime , * scan_kind ) ;
2019-06-08 10:10:55 -04:00
DPRINTF ( E_DBG , L_LIB , " Running post library scan jobs \n " ) ;
db_hook_post_scan ( ) ;
endtime = time ( NULL ) ;
DPRINTF ( E_LOG , L_LIB , " Library meta rescan completed in %.f sec (%d changes) \n " , difftime ( endtime , starttime ) , deferred_update_notifications ) ;
scanning = false ;
if ( handle_deferred_update_notifications ( ) )
listener_notify ( LISTENER_UPDATE | LISTENER_DATABASE ) ;
else
listener_notify ( LISTENER_UPDATE ) ;
* ret = 0 ;
return COMMAND_END ;
}
2016-12-31 01:28:18 -05:00
static enum command_state
fullrescan ( void * arg , int * ret )
{
2017-01-06 05:08:47 -05:00
time_t starttime ;
time_t endtime ;
2016-12-31 01:28:18 -05:00
int i ;
2017-01-06 05:08:47 -05:00
DPRINTF ( E_LOG , L_LIB , " Library full-rescan triggered \n " ) ;
2017-08-06 11:39:32 -04:00
listener_notify ( LISTENER_UPDATE ) ;
2017-01-06 05:08:47 -05:00
starttime = time ( NULL ) ;
player_playback_stop ( ) ;
2017-02-11 03:28:35 -05:00
db_queue_clear ( 0 ) ;
2020-03-08 16:06:21 -04:00
db_purge_all ( ) ; // Clears files, playlists, playlistitems, inotify and groups, incl RSS
2017-01-06 05:08:47 -05:00
2016-12-31 01:28:18 -05:00
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( ! sources [ i ] - > disabled & & sources [ i ] - > fullrescan )
2017-01-06 05:08:47 -05:00
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_INFO , L_LIB , " Full-rescan library source '%s' \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2017-01-06 05:08:47 -05:00
sources [ i ] - > fullrescan ( ) ;
}
else
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_INFO , L_LIB , " Library source '%s' is disabled \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2017-01-06 05:08:47 -05:00
}
2016-12-31 01:28:18 -05:00
}
2017-01-06 05:08:47 -05:00
endtime = time ( NULL ) ;
2017-11-12 03:28:25 -05:00
DPRINTF ( E_LOG , L_LIB , " Library full-rescan completed in %.f sec (%d changes) \n " , difftime ( endtime , starttime ) , deferred_update_notifications ) ;
2016-12-31 01:28:18 -05:00
scanning = false ;
2017-11-12 03:28:25 -05:00
if ( handle_deferred_update_notifications ( ) )
listener_notify ( LISTENER_UPDATE | LISTENER_DATABASE ) ;
else
listener_notify ( LISTENER_UPDATE ) ;
2017-08-06 11:39:32 -04:00
2016-12-31 01:28:18 -05:00
* ret = 0 ;
return COMMAND_END ;
}
2020-01-20 17:13:47 -05:00
static enum command_state
playlist_item_add ( void * arg , int * retval )
{
struct playlist_item_add_param * param = arg ;
int i ;
int ret = LIBRARY_ERROR ;
DPRINTF ( E_DBG , L_LIB , " Adding item '%s' to playlist '%s' \n " , param - > vp_item , param - > vp_playlist ) ;
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( sources [ i ] - > disabled | | ! sources [ i ] - > playlist_item_add )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Library source '%s' is disabled or does not support playlist_item_add \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
continue ;
}
ret = sources [ i ] - > playlist_item_add ( param - > vp_playlist , param - > vp_item ) ;
if ( ret = = LIBRARY_OK )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Adding item '%s' to playlist '%s' with library source '%s' \n " , param - > vp_item , param - > vp_playlist , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
listener_notify ( LISTENER_STORED_PLAYLIST ) ;
break ;
}
}
* retval = ret ;
return COMMAND_END ;
}
static enum command_state
playlist_remove ( void * arg , int * retval )
{
const char * virtual_path = arg ;
int i ;
int ret = LIBRARY_ERROR ;
DPRINTF ( E_DBG , L_LIB , " Removing playlist at path '%s' \n " , virtual_path ) ;
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( sources [ i ] - > disabled | | ! sources [ i ] - > playlist_remove )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Library source '%s' is disabled or does not support playlist_remove \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
continue ;
}
ret = sources [ i ] - > playlist_remove ( virtual_path ) ;
if ( ret = = LIBRARY_OK )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Removing playlist '%s' with library source '%s' \n " , virtual_path , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
listener_notify ( LISTENER_STORED_PLAYLIST ) ;
break ;
}
}
* retval = ret ;
return COMMAND_END ;
}
static enum command_state
queue_item_add ( void * arg , int * retval )
{
struct queue_item_add_param * param = arg ;
int i ;
int ret ;
DPRINTF ( E_DBG , L_LIB , " Add items for path '%s' to the queue \n " , param - > path ) ;
ret = LIBRARY_PATH_INVALID ;
for ( i = 0 ; sources [ i ] & & ret = = LIBRARY_PATH_INVALID ; i + + )
{
if ( sources [ i ] - > disabled | | ! sources [ i ] - > queue_item_add )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Library source '%s' is disabled or does not support queue_add \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
continue ;
}
ret = sources [ i ] - > queue_item_add ( param - > path , param - > position , param - > reshuffle , param - > item_id , param - > count , param - > new_item_id ) ;
if ( ret = = LIBRARY_OK )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Items for path '%s' from library source '%s' added to the queue \n " , param - > path , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
break ;
}
}
if ( ret ! = LIBRARY_OK )
DPRINTF ( E_LOG , L_LIB , " Failed to add items for path '%s' to the queue (%d) \n " , param - > path , ret ) ;
* retval = ret ;
return COMMAND_END ;
}
static enum command_state
queue_save ( void * arg , int * retval )
{
const char * virtual_path = arg ;
int i ;
int ret = LIBRARY_ERROR ;
DPRINTF ( E_DBG , L_LIB , " Saving queue to path '%s' \n " , virtual_path ) ;
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( sources [ i ] - > disabled | | ! sources [ i ] - > queue_save )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Library source '%s' is disabled or does not support queue_save \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
continue ;
}
ret = sources [ i ] - > queue_save ( virtual_path ) ;
if ( ret = = LIBRARY_OK )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Saving queue to path '%s' with library source '%s' \n " , virtual_path , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-01-20 17:13:47 -05:00
listener_notify ( LISTENER_STORED_PLAYLIST ) ;
break ;
}
}
* retval = ret ;
return COMMAND_END ;
}
2020-03-08 16:06:21 -04:00
static enum command_state
item_add ( void * arg , int * retval )
{
2024-01-24 17:30:02 -05:00
struct item_param * param = arg ;
2020-03-08 16:06:21 -04:00
int i ;
int ret = LIBRARY_ERROR ;
2024-01-24 17:30:02 -05:00
DPRINTF ( E_DBG , L_LIB , " Adding item to library '%s' \n " , param - > path ) ;
2020-03-08 16:06:21 -04:00
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( sources [ i ] - > disabled | | ! sources [ i ] - > item_add )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_DBG , L_LIB , " Library source '%s' is disabled or does not support add_item \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-03-08 16:06:21 -04:00
continue ;
}
2024-01-24 17:30:02 -05:00
ret = sources [ i ] - > item_add ( param - > path ) ;
2020-03-08 16:06:21 -04:00
if ( ret = = LIBRARY_OK )
{
2024-01-24 17:30:02 -05:00
DPRINTF ( E_DBG , L_LIB , " Add item to path '%s' with library source '%s' \n " , param - > path , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-03-08 16:06:21 -04:00
listener_notify ( LISTENER_DATABASE ) ;
break ;
}
}
2020-11-22 05:08:55 -05:00
scanning = false ;
if ( ret = = LIBRARY_OK )
{
if ( handle_deferred_update_notifications ( ) )
listener_notify ( LISTENER_UPDATE | LISTENER_DATABASE ) ;
else
listener_notify ( LISTENER_UPDATE ) ;
}
2020-03-08 16:06:21 -04:00
* retval = ret ;
return COMMAND_END ;
}
2024-01-24 17:30:02 -05:00
static int
write_metadata ( struct media_file_info * mfi )
{
int ret ;
int i ;
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( sources [ i ] - > disabled | | ! sources [ i ] - > write_metadata )
continue ;
ret = sources [ i ] - > write_metadata ( mfi ) ;
if ( ret = = LIBRARY_OK )
return ret ;
}
return LIBRARY_PATH_INVALID ;
}
static enum command_state
item_attrib_save ( void * arg , int * retval )
{
struct item_param * param = arg ;
struct media_file_info * mfi = NULL ;
int ret ;
if ( scanning )
goto error ;
mfi = db_file_fetch_byid ( param - > id ) ;
if ( ! mfi )
goto error ;
* retval = LIBRARY_OK ;
switch ( param - > attrib )
{
case LIBRARY_ATTRIB_RATING :
if ( param - > value < 0 | | param - > value > DB_FILES_RATING_MAX )
goto error ;
mfi - > rating = param - > value ;
if ( cfg_getbool ( cfg_getsec ( cfg , " library " ) , " write_rating " ) )
* retval = write_metadata ( mfi ) ;
listener_notify ( LISTENER_RATING ) ;
break ;
case LIBRARY_ATTRIB_USERMARK :
if ( param - > value < 0 )
goto error ;
mfi - > usermark = param - > value ;
break ;
case LIBRARY_ATTRIB_PLAY_COUNT :
if ( param - > value < 0 )
goto error ;
mfi - > play_count = param - > value ;
break ;
default :
goto error ;
}
ret = db_file_update ( mfi ) ;
if ( ret < 0 )
goto error ;
free_mfi ( mfi , 0 ) ;
return COMMAND_END ;
error :
DPRINTF ( E_LOG , L_LIB , " Error updating attribute %d to %d for file with id %d \n " , param - > attrib , param - > value , param - > id ) ;
* retval = LIBRARY_ERROR ;
free_mfi ( mfi , 0 ) ;
return COMMAND_END ;
}
2020-01-20 17:13:47 -05:00
// Callback to notify listeners of database changes
2017-01-27 17:23:25 -05:00
static void
update_trigger_cb ( int fd , short what , void * arg )
{
2017-11-12 03:28:25 -05:00
if ( handle_deferred_update_notifications ( ) )
{
2017-12-09 11:12:13 -05:00
listener_notify ( deferred_update_events ) ;
deferred_update_events = 0 ;
2017-11-12 03:28:25 -05:00
}
2017-01-27 17:23:25 -05:00
}
static enum command_state
update_trigger ( void * arg , int * retval )
{
2017-12-09 11:12:13 -05:00
short * events = arg ;
2017-11-12 03:28:25 -05:00
+ + deferred_update_notifications ;
2017-12-09 11:12:13 -05:00
deferred_update_events | = * events ;
2017-11-12 03:28:25 -05:00
// Only add the timer event if the update occurred outside a (init-/re-/fullre-) scan.
// The scanning functions take care of notifying clients of database changes directly
// after the scan finished.
if ( ! scanning )
evtimer_add ( updateev , & library_update_wait ) ;
2017-01-27 17:23:25 -05:00
* retval = 0 ;
return COMMAND_END ;
}
2020-01-20 17:13:47 -05:00
/* ----------------------- LIBRARY EXTERNAL INTERFACE ---------------------- */
2017-01-27 17:23:25 -05:00
2016-12-31 01:28:18 -05:00
void
2021-12-28 03:19:44 -05:00
library_rescan ( enum scan_kind scan_kind )
2016-12-31 01:28:18 -05:00
{
2021-12-28 03:19:44 -05:00
int * param ;
2016-12-31 01:28:18 -05:00
if ( scanning )
{
DPRINTF ( E_INFO , L_LIB , " Scan already running, ignoring request to trigger a new init scan \n " ) ;
return ;
}
2021-12-28 03:19:44 -05:00
scanning = true ;
param = malloc ( sizeof ( int ) ) ;
* param = scan_kind ;
commands_exec_async ( cmdbase , rescan , param ) ;
2016-12-31 01:28:18 -05:00
}
2019-06-08 10:10:55 -04:00
void
2021-12-28 03:19:44 -05:00
library_metarescan ( enum scan_kind scan_kind )
2019-06-08 10:10:55 -04:00
{
2021-12-28 03:19:44 -05:00
int * param ;
2019-06-08 10:10:55 -04:00
if ( scanning )
{
DPRINTF ( E_INFO , L_LIB , " Scan already running, ignoring request to trigger metadata scan \n " ) ;
return ;
}
2021-12-28 03:19:44 -05:00
scanning = true ;
param = malloc ( sizeof ( int ) ) ;
* param = scan_kind ;
commands_exec_async ( cmdbase , metarescan , param ) ;
2019-06-08 10:10:55 -04:00
}
2020-01-20 17:13:47 -05:00
2016-12-31 01:28:18 -05:00
void
library_fullrescan ( )
{
if ( scanning )
{
DPRINTF ( E_INFO , L_LIB , " Scan already running, ignoring request to trigger a new full rescan \n " ) ;
return ;
}
2021-12-28 03:19:44 -05:00
scanning = true ;
2016-12-31 01:28:18 -05:00
commands_exec_async ( cmdbase , fullrescan , NULL ) ;
}
static void
initscan ( )
{
time_t starttime ;
time_t endtime ;
bool clear_queue_disabled ;
int i ;
scanning = true ;
starttime = time ( NULL ) ;
2017-08-06 11:39:32 -04:00
listener_notify ( LISTENER_UPDATE ) ;
2016-12-31 01:28:18 -05:00
// Only clear the queue if enabled (default) in config
2022-03-06 04:01:15 -05:00
clear_queue_disabled = cfg_getbool ( cfg_getsec ( cfg , " library " ) , " clear_queue_on_stop_disable " ) ;
/* Handle deprecated config options */
if ( 0 < cfg_opt_size ( cfg_getopt ( cfg_getsec ( cfg , " mpd " ) , " clear_queue_on_stop_disable " ) ) )
{
DPRINTF ( E_LOG , L_MPD , " Found deprecated option 'clear_queue_on_stop_disable' in section 'mpd', please update configuration file (move option to section 'library'). \n " ) ;
clear_queue_disabled = cfg_getbool ( cfg_getsec ( cfg , " mpd " ) , " clear_queue_on_stop_disable " ) ;
}
2016-12-31 01:28:18 -05:00
if ( ! clear_queue_disabled )
{
2017-02-11 03:28:35 -05:00
db_queue_clear ( 0 ) ;
2016-12-31 01:28:18 -05:00
}
for ( i = 0 ; sources [ i ] ; i + + )
{
if ( ! sources [ i ] - > disabled & & sources [ i ] - > initscan )
sources [ i ] - > initscan ( ) ;
}
2017-01-01 10:51:21 -05:00
if ( ! ( cfg_getbool ( cfg_getsec ( cfg , " library " ) , " filescan_disable " ) ) )
{
2021-12-28 03:19:44 -05:00
purge_cruft ( starttime , 0 ) ;
2017-01-01 05:05:18 -05:00
2017-01-01 10:51:21 -05:00
DPRINTF ( E_DBG , L_LIB , " Running post library scan jobs \n " ) ;
db_hook_post_scan ( ) ;
}
2017-01-01 05:05:18 -05:00
2016-12-31 01:28:18 -05:00
endtime = time ( NULL ) ;
2017-11-12 03:28:25 -05:00
DPRINTF ( E_LOG , L_LIB , " Library init scan completed in %.f sec (%d changes) \n " , difftime ( endtime , starttime ) , deferred_update_notifications ) ;
2016-12-31 01:28:18 -05:00
scanning = false ;
2017-11-12 03:28:25 -05:00
if ( handle_deferred_update_notifications ( ) )
listener_notify ( LISTENER_UPDATE | LISTENER_DATABASE ) ;
else
listener_notify ( LISTENER_UPDATE ) ;
2016-12-31 01:28:18 -05:00
}
bool
library_is_scanning ( )
{
return scanning ;
}
void
library_set_scanning ( bool is_scanning )
{
scanning = is_scanning ;
}
bool
library_is_exiting ( )
{
return scan_exit ;
}
2017-01-27 17:23:25 -05:00
void
2017-12-09 11:12:13 -05:00
library_update_trigger ( short update_events )
2017-01-27 17:23:25 -05:00
{
2017-12-09 11:12:13 -05:00
short * events ;
2017-11-12 03:28:25 -05:00
int ret ;
2017-11-11 19:38:36 -05:00
2017-11-12 03:28:25 -05:00
pthread_t current_thread = pthread_self ( ) ;
if ( pthread_equal ( current_thread , tid_library ) )
2017-11-11 19:38:36 -05:00
{
2017-11-12 03:28:25 -05:00
// We are already running in the library thread, it is safe to directly call update_trigger
2017-12-09 11:12:13 -05:00
update_trigger ( & update_events , & ret ) ;
2017-11-12 03:28:25 -05:00
}
else
{
2017-12-09 11:12:13 -05:00
events = malloc ( sizeof ( short ) ) ;
* events = update_events ;
commands_exec_async ( cmdbase , update_trigger , events ) ;
2017-11-11 19:38:36 -05:00
}
2017-01-27 17:23:25 -05:00
}
2017-08-09 12:19:20 -04:00
int
2020-01-20 17:13:47 -05:00
library_playlist_item_add ( const char * vp_playlist , const char * vp_item )
2017-08-09 12:19:20 -04:00
{
2020-01-20 17:13:47 -05:00
struct playlist_item_add_param param ;
2017-08-09 12:19:20 -04:00
if ( library_is_scanning ( ) )
return - 1 ;
param . vp_playlist = vp_playlist ;
param . vp_item = vp_item ;
2020-01-20 17:13:47 -05:00
return commands_exec_sync ( cmdbase , playlist_item_add , NULL , & param ) ;
2017-08-09 12:19:20 -04:00
}
int
library_playlist_remove ( char * virtual_path )
{
if ( library_is_scanning ( ) )
return - 1 ;
return commands_exec_sync ( cmdbase , playlist_remove , NULL , virtual_path ) ;
}
2020-11-28 02:27:52 -05:00
int
library_playlist_remove_byid ( int pl_id )
{
if ( scanning )
{
DPRINTF ( E_INFO , L_LIB , " Scan already running, ignoring request to remove playlist '%d' \n " , pl_id ) ;
return - 1 ;
}
db_pl_delete ( pl_id ) ;
if ( handle_deferred_update_notifications ( ) )
listener_notify ( LISTENER_UPDATE | LISTENER_DATABASE ) ;
else
listener_notify ( LISTENER_UPDATE ) ;
return 0 ;
}
2020-01-20 17:13:47 -05:00
int
library_queue_save ( char * path )
2017-08-09 12:19:20 -04:00
{
2020-01-20 17:13:47 -05:00
if ( library_is_scanning ( ) )
return - 1 ;
2017-08-09 12:19:20 -04:00
2020-01-20 17:13:47 -05:00
return commands_exec_sync ( cmdbase , queue_save , NULL , path ) ;
2017-08-09 12:19:20 -04:00
}
int
2020-01-20 17:13:47 -05:00
library_queue_item_add ( const char * path , int position , char reshuffle , uint32_t item_id , int * count , int * new_item_id )
2017-08-09 12:19:20 -04:00
{
2020-01-20 17:13:47 -05:00
struct queue_item_add_param param ;
2023-12-03 14:55:37 -05:00
int count_internal ;
int new_item_id_internal ;
2020-01-20 17:13:47 -05:00
2017-08-09 12:19:20 -04:00
if ( library_is_scanning ( ) )
return - 1 ;
2020-01-20 17:13:47 -05:00
param . path = path ;
param . position = position ;
param . reshuffle = reshuffle ;
param . item_id = item_id ;
2023-12-03 14:55:37 -05:00
param . count = count ? count : & count_internal ;
param . new_item_id = new_item_id ? new_item_id : & new_item_id_internal ;
2020-01-20 17:13:47 -05:00
return commands_exec_sync ( cmdbase , queue_item_add , NULL , & param ) ;
2017-08-09 12:19:20 -04:00
}
2020-03-08 16:06:21 -04:00
int
2020-03-21 17:16:57 -04:00
library_item_add ( const char * path )
2020-03-08 16:06:21 -04:00
{
2024-01-24 17:30:02 -05:00
struct item_param param ;
2020-11-22 05:08:55 -05:00
if ( scanning )
{
DPRINTF ( E_INFO , L_LIB , " Scan already running, ignoring request to add item '%s' \n " , path ) ;
return - 1 ;
}
2021-12-28 03:19:44 -05:00
scanning = true ;
2020-03-08 16:06:21 -04:00
2024-01-24 17:30:02 -05:00
param . path = path ;
return commands_exec_sync ( cmdbase , item_add , NULL , & param ) ;
}
void
library_item_attrib_save ( uint32_t id , enum library_attrib attrib , uint32_t value )
{
struct item_param * param ;
param = malloc ( sizeof ( struct item_param ) ) ;
param - > id = id ;
param - > attrib = attrib ;
param - > value = value ;
commands_exec_async ( cmdbase , item_attrib_save , param ) ;
2020-03-08 16:06:21 -04:00
}
2021-12-28 03:19:44 -05:00
struct library_source * *
library_sources ( void )
{
return sources ;
}
2017-01-04 14:27:55 -05:00
int
library_exec_async ( command_function func , void * arg )
{
return commands_exec_async ( cmdbase , func , arg ) ;
}
2016-12-31 01:28:18 -05:00
static void *
library ( void * arg )
{
int ret ;
# ifdef __linux__
struct sched_param param ;
2021-04-09 14:21:20 -04:00
/* Lower the priority of the thread so the server may still respond
2016-12-31 01:28:18 -05:00
* during library scan on low power devices . Param must be 0 for the SCHED_BATCH
* policy .
*/
memset ( & param , 0 , sizeof ( struct sched_param ) ) ;
ret = pthread_setschedparam ( pthread_self ( ) , SCHED_BATCH , & param ) ;
if ( ret ! = 0 )
{
DPRINTF ( E_LOG , L_LIB , " Warning: Could not set thread priority to SCHED_BATCH \n " ) ;
}
# endif
ret = db_perthread_init ( ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_LIB , " Error: DB init failed \n " ) ;
pthread_exit ( NULL ) ;
}
initscan ( ) ;
event_base_dispatch ( evbase_lib ) ;
if ( ! scan_exit )
DPRINTF ( E_FATAL , L_LIB , " Scan event loop terminated ahead of time! \n " ) ;
2018-02-23 13:44:40 -05:00
db_hook_post_scan ( ) ;
2016-12-31 01:28:18 -05:00
db_perthread_deinit ( ) ;
pthread_exit ( NULL ) ;
}
/* Thread: main */
int
library_init ( void )
{
int i ;
int ret ;
scan_exit = false ;
scanning = false ;
2017-01-27 17:23:25 -05:00
CHECK_NULL ( L_LIB , evbase_lib = event_base_new ( ) ) ;
CHECK_NULL ( L_LIB , updateev = evtimer_new ( evbase_lib , update_trigger_cb , NULL ) ) ;
2016-12-31 01:28:18 -05:00
for ( i = 0 ; sources [ i ] ; i + + )
{
2020-02-08 04:53:59 -05:00
if ( ! sources [ i ] - > initscan | | ! sources [ i ] - > rescan | | ! sources [ i ] - > metarescan | | ! sources [ i ] - > fullrescan )
{
2021-12-28 03:19:44 -05:00
DPRINTF ( E_FATAL , L_LIB , " BUG: library source '%s' is missing a scanning method \n " , db_scan_kind_label ( sources [ i ] - > scan_kind ) ) ;
2020-02-08 04:53:59 -05:00
return - 1 ;
}
2017-01-18 14:12:14 -05:00
2020-03-21 17:16:57 -04:00
if ( sources [ i ] - > init & & ! sources [ i ] - > disabled )
{
ret = sources [ i ] - > init ( ) ;
if ( ret < 0 )
sources [ i ] - > disabled = 1 ;
}
2016-12-31 01:28:18 -05:00
}
2017-01-27 17:23:25 -05:00
CHECK_NULL ( L_LIB , cmdbase = commands_base_new ( evbase_lib , NULL ) ) ;
2016-12-31 01:28:18 -05:00
2017-01-27 17:23:25 -05:00
CHECK_ERR ( L_LIB , pthread_create ( & tid_library , NULL , library , NULL ) ) ;
2016-12-31 01:28:18 -05:00
2021-07-05 15:40:31 -04:00
thread_setname ( tid_library , " library " ) ;
2016-12-31 01:28:18 -05:00
return 0 ;
}
/* Thread: main */
void
library_deinit ( )
{
int i ;
int ret ;
scan_exit = true ;
commands_base_destroy ( cmdbase ) ;
ret = pthread_join ( tid_library , NULL ) ;
if ( ret ! = 0 )
{
DPRINTF ( E_FATAL , L_LIB , " Could not join library thread: %s \n " , strerror ( errno ) ) ;
return ;
}
for ( i = 0 ; sources [ i ] ; i + + )
{
2017-01-18 14:12:14 -05:00
if ( sources [ i ] - > deinit & & ! sources [ i ] - > disabled )
2020-03-21 17:16:57 -04:00
sources [ i ] - > deinit ( ) ;
2016-12-31 01:28:18 -05:00
}
2020-03-23 18:17:26 -04:00
for ( i = 0 ; i < ARRAY_SIZE ( library_cb_register ) ; i + + )
{
if ( library_cb_register [ i ] . ev )
event_free ( library_cb_register [ i ] . ev ) ;
}
2019-09-09 16:21:23 -04:00
event_free ( updateev ) ;
2016-12-31 01:28:18 -05:00
event_base_free ( evbase_lib ) ;
}