mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-30 09:03:23 -05:00
Merge pull request #384 from chme/storedplaylist3
[mpd] Initial support for stored playlists
This commit is contained in:
commit
05b15d9679
@ -264,6 +264,16 @@ mpd {
|
|||||||
# the playlist like MPD does. Note that some dacp clients do not show
|
# the playlist like MPD does. Note that some dacp clients do not show
|
||||||
# the playqueue if playback is stopped.
|
# the playqueue if playback is stopped.
|
||||||
# clear_queue_on_stop_disable = false
|
# clear_queue_on_stop_disable = false
|
||||||
|
|
||||||
|
# Allows creating, deleting and modifying m3u playlists in the library directories.
|
||||||
|
# Defaults to being disabled.
|
||||||
|
# allow_modifying_stored_playlists = false
|
||||||
|
|
||||||
|
# A directory in one of the library directories that will be used as the default
|
||||||
|
# playlist directory. forked-dapd creates new playlists in this directory if only
|
||||||
|
# a playlist name is provided by the mpd client (requires "allow_modify_stored_playlists"
|
||||||
|
# set to true).
|
||||||
|
# default_playlist_directory = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# SQLite configuration (allows to modify the operation of the SQLite databases)
|
# SQLite configuration (allows to modify the operation of the SQLite databases)
|
||||||
|
@ -156,6 +156,8 @@ static cfg_opt_t sec_mpd[] =
|
|||||||
CFG_INT("port", 6600, CFGF_NONE),
|
CFG_INT("port", 6600, CFGF_NONE),
|
||||||
CFG_INT("http_port", 0, CFGF_NONE),
|
CFG_INT("http_port", 0, CFGF_NONE),
|
||||||
CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NONE),
|
CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NONE),
|
||||||
|
CFG_BOOL("allow_modifying_stored_playlists", cfg_false, CFGF_NONE),
|
||||||
|
CFG_STR("default_playlist_directory", NULL, CFGF_NONE),
|
||||||
CFG_END()
|
CFG_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
53
src/db.c
53
src/db.c
@ -827,7 +827,10 @@ db_get_one_int(const char *query)
|
|||||||
ret = db_blocking_step(stmt);
|
ret = db_blocking_step(stmt);
|
||||||
if (ret != SQLITE_ROW)
|
if (ret != SQLITE_ROW)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
if (ret == SQLITE_DONE)
|
||||||
|
DPRINTF(E_INFO, L_DB, "No matching row found for query: %s\n", query);
|
||||||
|
else
|
||||||
|
DPRINTF(E_LOG, L_DB, "Could not step: %s (%s)\n", sqlite3_errmsg(hdl), query);
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return -1;
|
return -1;
|
||||||
@ -2265,7 +2268,7 @@ db_file_fetch_byid(int id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct media_file_info *
|
struct media_file_info *
|
||||||
db_file_fetch_byvirtualpath(char *virtual_path)
|
db_file_fetch_byvirtualpath(const char *virtual_path)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "SELECT f.* FROM files f WHERE f.virtual_path = %Q;"
|
#define Q_TMPL "SELECT f.* FROM files f WHERE f.virtual_path = %Q;"
|
||||||
struct media_file_info *mfi;
|
struct media_file_info *mfi;
|
||||||
@ -2649,12 +2652,11 @@ db_pl_ping_bymatch(char *path, int isdir)
|
|||||||
#undef Q_TMPL_NODIR
|
#undef Q_TMPL_NODIR
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
int
|
||||||
db_pl_id_bypath(char *path, int *id)
|
db_pl_id_bypath(const char *path)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "SELECT p.id FROM playlists p WHERE p.path = '%q';"
|
#define Q_TMPL "SELECT p.id FROM playlists p WHERE p.path = '%q';"
|
||||||
char *query;
|
char *query;
|
||||||
sqlite3_stmt *stmt;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
query = sqlite3_mprintf(Q_TMPL, path);
|
query = sqlite3_mprintf(Q_TMPL, path);
|
||||||
@ -2665,41 +2667,11 @@ db_pl_id_bypath(char *path, int *id)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
ret = db_get_one_int(query);
|
||||||
|
|
||||||
ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
|
|
||||||
if (ret != SQLITE_OK)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
||||||
|
|
||||||
sqlite3_free(query);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = db_blocking_step(stmt);
|
|
||||||
if (ret != SQLITE_ROW)
|
|
||||||
{
|
|
||||||
if (ret == SQLITE_DONE)
|
|
||||||
DPRINTF(E_DBG, L_DB, "No results\n");
|
|
||||||
else
|
|
||||||
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
sqlite3_free(query);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
*id = sqlite3_column_int(stmt, 0);
|
|
||||||
|
|
||||||
#ifdef DB_PROFILE
|
|
||||||
while (db_blocking_step(stmt) == SQLITE_ROW)
|
|
||||||
; /* EMPTY */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
sqlite3_free(query);
|
sqlite3_free(query);
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
|
|
||||||
#undef Q_TMPL
|
#undef Q_TMPL
|
||||||
}
|
}
|
||||||
@ -2855,7 +2827,7 @@ db_pl_fetch_bypath(const char *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct playlist_info *
|
struct playlist_info *
|
||||||
db_pl_fetch_byvirtualpath(char *virtual_path)
|
db_pl_fetch_byvirtualpath(const char *virtual_path)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "SELECT p.* FROM playlists p WHERE p.virtual_path = '%q';"
|
#define Q_TMPL "SELECT p.* FROM playlists p WHERE p.virtual_path = '%q';"
|
||||||
struct playlist_info *pli;
|
struct playlist_info *pli;
|
||||||
@ -3072,10 +3044,9 @@ void
|
|||||||
db_pl_delete_bypath(char *path)
|
db_pl_delete_bypath(char *path)
|
||||||
{
|
{
|
||||||
int id;
|
int id;
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = db_pl_id_bypath(path, &id);
|
id = db_pl_id_bypath(path);
|
||||||
if (ret < 0)
|
if (id < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
db_pl_delete(id);
|
db_pl_delete(id);
|
||||||
|
7
src/db.h
7
src/db.h
@ -536,7 +536,7 @@ struct media_file_info *
|
|||||||
db_file_fetch_byid(int id);
|
db_file_fetch_byid(int id);
|
||||||
|
|
||||||
struct media_file_info *
|
struct media_file_info *
|
||||||
db_file_fetch_byvirtualpath(char *path);
|
db_file_fetch_byvirtualpath(const char *path);
|
||||||
|
|
||||||
int
|
int
|
||||||
db_file_add(struct media_file_info *mfi);
|
db_file_add(struct media_file_info *mfi);
|
||||||
@ -572,11 +572,14 @@ db_pl_ping(int id);
|
|||||||
void
|
void
|
||||||
db_pl_ping_bymatch(char *path, int isdir);
|
db_pl_ping_bymatch(char *path, int isdir);
|
||||||
|
|
||||||
|
int
|
||||||
|
db_pl_id_bypath(const char *path);
|
||||||
|
|
||||||
struct playlist_info *
|
struct playlist_info *
|
||||||
db_pl_fetch_bypath(const char *path);
|
db_pl_fetch_bypath(const char *path);
|
||||||
|
|
||||||
struct playlist_info *
|
struct playlist_info *
|
||||||
db_pl_fetch_byvirtualpath(char *virtual_path);
|
db_pl_fetch_byvirtualpath(const char *virtual_path);
|
||||||
|
|
||||||
struct playlist_info *
|
struct playlist_info *
|
||||||
db_pl_fetch_bytitlepath(char *title, char *path);
|
db_pl_fetch_bytitlepath(char *title, char *path);
|
||||||
|
134
src/library.c
134
src/library.c
@ -47,6 +47,12 @@
|
|||||||
#include "listener.h"
|
#include "listener.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
|
struct playlist_add_param
|
||||||
|
{
|
||||||
|
const char *vp_playlist;
|
||||||
|
const char *vp_item;
|
||||||
|
};
|
||||||
|
|
||||||
static struct commands_base *cmdbase;
|
static struct commands_base *cmdbase;
|
||||||
static pthread_t tid_library;
|
static pthread_t tid_library;
|
||||||
|
|
||||||
@ -725,6 +731,134 @@ library_update_trigger(void)
|
|||||||
commands_exec_async(cmdbase, update_trigger, NULL);
|
commands_exec_async(cmdbase, update_trigger, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum command_state
|
||||||
|
playlist_add(void *arg, int *retval)
|
||||||
|
{
|
||||||
|
struct playlist_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_add)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_add\n", sources[i]->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sources[i]->playlist_add(param->vp_playlist, param->vp_item);
|
||||||
|
|
||||||
|
if (ret == LIBRARY_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_LIB, "Adding item '%s' to playlist '%s' with library source '%s'\n", param->vp_item, param->vp_playlist, sources[i]->name);
|
||||||
|
listener_notify(LISTENER_STORED_PLAYLIST);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*retval = ret;
|
||||||
|
return COMMAND_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
library_playlist_add(const char *vp_playlist, const char *vp_item)
|
||||||
|
{
|
||||||
|
struct playlist_add_param param;
|
||||||
|
|
||||||
|
if (library_is_scanning())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
param.vp_playlist = vp_playlist;
|
||||||
|
param.vp_item = vp_item;
|
||||||
|
return commands_exec_sync(cmdbase, playlist_add, NULL, ¶m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum command_state
|
||||||
|
playlist_remove(void *arg, int *retval)
|
||||||
|
{
|
||||||
|
const char *virtual_path;
|
||||||
|
int i;
|
||||||
|
int ret = LIBRARY_ERROR;
|
||||||
|
|
||||||
|
virtual_path = arg;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_remove\n", sources[i]->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sources[i]->playlist_remove(virtual_path);
|
||||||
|
|
||||||
|
if (ret == LIBRARY_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_LIB, "Removing playlist '%s' with library source '%s'\n", virtual_path, sources[i]->name);
|
||||||
|
listener_notify(LISTENER_STORED_PLAYLIST);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*retval = ret;
|
||||||
|
return COMMAND_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
library_playlist_remove(char *virtual_path)
|
||||||
|
{
|
||||||
|
if (library_is_scanning())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return commands_exec_sync(cmdbase, playlist_remove, NULL, virtual_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum command_state
|
||||||
|
queue_save(void *arg, int *retval)
|
||||||
|
{
|
||||||
|
const char *virtual_path;
|
||||||
|
int i;
|
||||||
|
int ret = LIBRARY_ERROR;
|
||||||
|
|
||||||
|
virtual_path = arg;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support queue_save\n", sources[i]->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sources[i]->queue_save(virtual_path);
|
||||||
|
|
||||||
|
if (ret == LIBRARY_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_LIB, "Saving queue to path '%s' with library source '%s'\n", virtual_path, sources[i]->name);
|
||||||
|
listener_notify(LISTENER_STORED_PLAYLIST);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*retval = ret;
|
||||||
|
return COMMAND_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
library_queue_save(char *path)
|
||||||
|
{
|
||||||
|
if (library_is_scanning())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return commands_exec_sync(cmdbase, queue_save, NULL, path);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Execute the function 'func' with the given argument 'arg' in the library thread.
|
* Execute the function 'func' with the given argument 'arg' in the library thread.
|
||||||
*
|
*
|
||||||
|
@ -70,6 +70,21 @@ struct library_source
|
|||||||
* Scans metadata for the media file with the given path into the given mfi
|
* Scans metadata for the media file with the given path into the given mfi
|
||||||
*/
|
*/
|
||||||
int (*scan_metadata)(const char *path, struct media_file_info *mfi);
|
int (*scan_metadata)(const char *path, struct media_file_info *mfi);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save queue as a new playlist under the given virtual path
|
||||||
|
*/
|
||||||
|
int (*playlist_add)(const char *vp_playlist, const char *vp_item);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes the playlist under the given virtual path
|
||||||
|
*/
|
||||||
|
int (*playlist_remove)(const char *virtual_path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save queue as a new playlist under the given virtual path
|
||||||
|
*/
|
||||||
|
int (*queue_save)(const char *virtual_path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -103,6 +118,15 @@ library_is_exiting();
|
|||||||
void
|
void
|
||||||
library_update_trigger(void);
|
library_update_trigger(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
library_playlist_add(const char *vp_playlist, const char *vp_item);
|
||||||
|
|
||||||
|
int
|
||||||
|
library_playlist_remove(char *virtual_path);
|
||||||
|
|
||||||
|
int
|
||||||
|
library_queue_save(char *path);
|
||||||
|
|
||||||
int
|
int
|
||||||
library_exec_async(command_function func, void *arg);
|
library_exec_async(command_function func, void *arg);
|
||||||
|
|
||||||
|
@ -177,6 +177,20 @@ filename_from_path(const char *path)
|
|||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
strip_extension(const char *path)
|
||||||
|
{
|
||||||
|
char *ptr;
|
||||||
|
char *result;
|
||||||
|
|
||||||
|
result = strdup(path);
|
||||||
|
ptr = strrchr(result, '.');
|
||||||
|
if (ptr)
|
||||||
|
*ptr = '\0';
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
push_dir(struct stacked_dir **s, char *path, int parent_id)
|
push_dir(struct stacked_dir **s, char *path, int parent_id)
|
||||||
{
|
{
|
||||||
@ -1601,6 +1615,342 @@ scan_metadata(const char *path, struct media_file_info *mfi)
|
|||||||
return LIBRARY_PATH_INVALID;
|
return LIBRARY_PATH_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
virtual_path_to_path(const char *virtual_path)
|
||||||
|
{
|
||||||
|
if (strncmp(virtual_path, "/file:", strlen("/file:")) == 0)
|
||||||
|
return virtual_path + strlen("/file:");
|
||||||
|
|
||||||
|
if (strncmp(virtual_path, "file:", strlen("file:")) == 0)
|
||||||
|
return virtual_path + strlen("file:");
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
check_path_in_directories(const char *path)
|
||||||
|
{
|
||||||
|
cfg_t *lib;
|
||||||
|
int ndirs;
|
||||||
|
int i;
|
||||||
|
char *tmp_path;
|
||||||
|
char *dir;
|
||||||
|
const char *lib_dir;
|
||||||
|
bool ret;
|
||||||
|
|
||||||
|
if (strstr(path, "/../"))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
tmp_path = strdup(path);
|
||||||
|
dir = dirname(tmp_path);
|
||||||
|
if (!dir)
|
||||||
|
{
|
||||||
|
free(tmp_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = false;
|
||||||
|
lib = cfg_getsec(cfg, "library");
|
||||||
|
ndirs = cfg_size(lib, "directories");
|
||||||
|
for (i = 0; i < ndirs; i++)
|
||||||
|
{
|
||||||
|
lib_dir = cfg_getnstr(lib, "directories", i);
|
||||||
|
if (strncmp(dir, lib_dir, strlen(lib_dir)) == 0)
|
||||||
|
{
|
||||||
|
ret = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(tmp_path);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
has_suffix(const char *file, const char *suffix)
|
||||||
|
{
|
||||||
|
return (strlen(file) > 4 && !strcmp(file + strlen(file) - 4, suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if the given virtual path for a playlist is a valid path for an m3u playlist file in one
|
||||||
|
* of the configured library directories and translates it to real path.
|
||||||
|
*
|
||||||
|
* Returns NULL on error and a new allocated path on success.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
get_playlist_path(const char *vp_playlist)
|
||||||
|
{
|
||||||
|
const char *path;
|
||||||
|
char *pl_path;
|
||||||
|
struct playlist_info *pli;
|
||||||
|
|
||||||
|
path = virtual_path_to_path(vp_playlist);
|
||||||
|
if (!path)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Unsupported virtual path '%s'\n", vp_playlist);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_path = safe_asprintf("%s.m3u", path);
|
||||||
|
|
||||||
|
if (!check_path_in_directories(pl_path))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Path '%s' is not a virtual path for a configured (local) library directory.\n", pl_path);
|
||||||
|
free(pl_path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pli = db_pl_fetch_byvirtualpath(vp_playlist);
|
||||||
|
if (pli && (pli->type != PL_PLAIN || !has_suffix(pli->path, ".m3u")))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Playlist with virtual path '%s' already exists and is not a m3u playlist.\n", vp_playlist);
|
||||||
|
free_pli(pli, 0);
|
||||||
|
free(pl_path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
free_pli(pli, 0);
|
||||||
|
|
||||||
|
return pl_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
get_playlist_id(const char *pl_path, const char *vp_playlist)
|
||||||
|
{
|
||||||
|
const char *filename;
|
||||||
|
char *title;
|
||||||
|
int dir_id;
|
||||||
|
int pl_id;
|
||||||
|
|
||||||
|
pl_id = db_pl_id_bypath(pl_path);
|
||||||
|
if (pl_id < 0)
|
||||||
|
{
|
||||||
|
dir_id = get_parent_dir_id(pl_path);
|
||||||
|
filename = filename_from_path(pl_path);
|
||||||
|
title = strip_extension(filename);
|
||||||
|
pl_id = library_add_playlist_info(pl_path, title, vp_playlist, PL_PLAIN, 0, dir_id);
|
||||||
|
free(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pl_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
playlist_add_path(FILE *fp, int pl_id, const char *path)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = fprintf(fp, "%s\n", path);
|
||||||
|
if (ret >= 0)
|
||||||
|
{
|
||||||
|
ret = db_pl_add_item_bypath(pl_id, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Failed to add path '%s' to playlist (id = %d)\n", path, pl_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
playlist_add_files(FILE *fp, int pl_id, const char *virtual_path)
|
||||||
|
{
|
||||||
|
struct query_params qp;
|
||||||
|
struct db_media_file_info dbmfi;
|
||||||
|
uint32_t data_kind;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
|
qp.type = Q_ITEMS;
|
||||||
|
qp.sort = S_ARTIST;
|
||||||
|
qp.idx_type = I_NONE;
|
||||||
|
qp.filter = sqlite3_mprintf("(f.virtual_path = %Q OR f.virtual_path LIKE '%q/%%')", virtual_path, virtual_path);
|
||||||
|
|
||||||
|
ret = db_query_start(&qp);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
db_query_end(&qp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
|
||||||
|
{
|
||||||
|
if ((safe_atou32(dbmfi.data_kind, &data_kind) < 0)
|
||||||
|
|| (data_kind == DATA_KIND_PIPE))
|
||||||
|
{
|
||||||
|
DPRINTF(E_WARN, L_SCAN, "Item '%s' not added to playlist (id = %d), unsupported data kind\n", dbmfi.path, pl_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = playlist_add_path(fp, pl_id, dbmfi.path);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_SCAN, "Item '%s' added to playlist (id = %d)\n", dbmfi.path, pl_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
db_query_end(&qp);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
playlist_add(const char *vp_playlist, const char *vp_item)
|
||||||
|
{
|
||||||
|
char *pl_path;
|
||||||
|
FILE *fp;
|
||||||
|
int pl_id;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pl_path = get_playlist_path(vp_playlist);
|
||||||
|
if (!pl_path)
|
||||||
|
return LIBRARY_PATH_INVALID;
|
||||||
|
|
||||||
|
fp = fopen(pl_path, "a");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno);
|
||||||
|
free(pl_path);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_id = get_playlist_id(pl_path, vp_playlist);
|
||||||
|
free(pl_path);
|
||||||
|
if (pl_id < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not get playlist id for %s\n", vp_playlist);
|
||||||
|
fclose(fp);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = playlist_add_files(fp, pl_id, vp_item);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not add %s to playlist\n", vp_item);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_pl_ping(pl_id);
|
||||||
|
|
||||||
|
return LIBRARY_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
playlist_remove(const char *vp_playlist)
|
||||||
|
{
|
||||||
|
char *pl_path;
|
||||||
|
struct playlist_info *pli;
|
||||||
|
int pl_id;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pl_path = get_playlist_path(vp_playlist);
|
||||||
|
if (!pl_path)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Unsupported virtual path '%s'\n", vp_playlist);
|
||||||
|
return LIBRARY_PATH_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
pli = db_pl_fetch_byvirtualpath(vp_playlist);
|
||||||
|
if (!pli || pli->type != PL_PLAIN)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Playlist with virtual path '%s' does not exist or is not a plain playlist.\n", vp_playlist);
|
||||||
|
free_pli(pli, 0);
|
||||||
|
free(pl_path);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
pl_id = pli->id;
|
||||||
|
free_pli(pli, 0);
|
||||||
|
|
||||||
|
ret = unlink(pl_path);
|
||||||
|
free(pl_path);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not remove playlist \"%s\": %d\n", vp_playlist, errno);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_pl_delete(pl_id);
|
||||||
|
return LIBRARY_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
queue_save(const char *virtual_path)
|
||||||
|
{
|
||||||
|
char *pl_path;
|
||||||
|
FILE *fp;
|
||||||
|
struct query_params query_params;
|
||||||
|
struct db_queue_item queue_item;
|
||||||
|
int pl_id;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pl_path = get_playlist_path(virtual_path);
|
||||||
|
if (!pl_path)
|
||||||
|
return LIBRARY_PATH_INVALID;
|
||||||
|
|
||||||
|
fp = fopen(pl_path, "a");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno);
|
||||||
|
free(pl_path);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_id = get_playlist_id(pl_path, virtual_path);
|
||||||
|
free(pl_path);
|
||||||
|
if (pl_id < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not get playlist id for %s\n", virtual_path);
|
||||||
|
fclose(fp);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&query_params, 0, sizeof(struct query_params));
|
||||||
|
ret = db_queue_enum_start(&query_params);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Failed to start queue enum\n");
|
||||||
|
fclose(fp);
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
|
||||||
|
{
|
||||||
|
if (queue_item.data_kind == DATA_KIND_PIPE)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Unsupported data kind for playlist file '%s' ignoring item '%s'\n", virtual_path, queue_item.path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = fprintf(fp, "%s\n", queue_item.path);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Failed to write path '%s' to file '%s'\n", queue_item.path, virtual_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = db_pl_add_item_bypath(pl_id, queue_item.path);
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", queue_item.path);
|
||||||
|
else
|
||||||
|
DPRINTF(E_DBG, L_SCAN, "Item '%s' added to playlist (id = %d)\n", queue_item.path, pl_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
db_queue_enum_end(&query_params);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
db_pl_ping(pl_id);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return LIBRARY_ERROR;
|
||||||
|
|
||||||
|
return LIBRARY_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* Thread: main */
|
/* Thread: main */
|
||||||
static int
|
static int
|
||||||
filescanner_init(void)
|
filescanner_init(void)
|
||||||
@ -1634,4 +1984,7 @@ struct library_source filescanner =
|
|||||||
.rescan = filescanner_rescan,
|
.rescan = filescanner_rescan,
|
||||||
.fullrescan = filescanner_fullrescan,
|
.fullrescan = filescanner_fullrescan,
|
||||||
.scan_metadata = scan_metadata,
|
.scan_metadata = scan_metadata,
|
||||||
|
.playlist_add = playlist_add,
|
||||||
|
.playlist_remove = playlist_remove,
|
||||||
|
.queue_save = queue_save,
|
||||||
};
|
};
|
||||||
|
@ -23,4 +23,7 @@ scan_itunes_itml(char *file);
|
|||||||
const char *
|
const char *
|
||||||
filename_from_path(const char *path);
|
filename_from_path(const char *path);
|
||||||
|
|
||||||
|
char *
|
||||||
|
strip_extension(const char *path);
|
||||||
|
|
||||||
#endif /* !__FILESCANNER_H__ */
|
#endif /* !__FILESCANNER_H__ */
|
||||||
|
@ -256,22 +256,11 @@ scan_playlist(char *file, time_t mtime, int dir_id)
|
|||||||
pli->type = PL_PLAIN;
|
pli->type = PL_PLAIN;
|
||||||
|
|
||||||
/* Get only the basename, to be used as the playlist title */
|
/* Get only the basename, to be used as the playlist title */
|
||||||
ptr = strrchr(filename, '.');
|
pli->title = strip_extension(filename);
|
||||||
if (ptr)
|
|
||||||
*ptr = '\0';
|
|
||||||
|
|
||||||
pli->title = strdup(filename);
|
|
||||||
|
|
||||||
/* Restore the full filename */
|
|
||||||
if (ptr)
|
|
||||||
*ptr = '.';
|
|
||||||
|
|
||||||
pli->path = strdup(file);
|
pli->path = strdup(file);
|
||||||
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
|
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
|
||||||
ptr = strrchr(virtual_path, '.');
|
pli->virtual_path = strip_extension(virtual_path);
|
||||||
if (ptr)
|
|
||||||
*ptr = '\0';
|
|
||||||
pli->virtual_path = strdup(virtual_path);
|
|
||||||
|
|
||||||
pli->directory_id = dir_id;
|
pli->directory_id = dir_id;
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ enum listener_event_type
|
|||||||
LISTENER_OPTIONS = (1 << 4),
|
LISTENER_OPTIONS = (1 << 4),
|
||||||
/* The library has been modified */
|
/* The library has been modified */
|
||||||
LISTENER_DATABASE = (1 << 5),
|
LISTENER_DATABASE = (1 << 5),
|
||||||
|
/* A stored playlist has been modified (create, delete, add, rename) */
|
||||||
|
LISTENER_STORED_PLAYLIST = (1 << 6),
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*notify)(enum listener_event_type type);
|
typedef void (*notify)(enum listener_event_type type);
|
||||||
|
167
src/mpd.c
167
src/mpd.c
@ -70,6 +70,9 @@ static struct evhttp *evhttpd;
|
|||||||
|
|
||||||
struct evconnlistener *listener;
|
struct evconnlistener *listener;
|
||||||
|
|
||||||
|
// Virtual path to the default playlist directory
|
||||||
|
static char *default_pl_dir;
|
||||||
|
static bool allow_modifying_stored_playlists;
|
||||||
|
|
||||||
#define COMMAND_ARGV_MAX 37
|
#define COMMAND_ARGV_MAX 37
|
||||||
|
|
||||||
@ -185,6 +188,26 @@ struct idle_client
|
|||||||
struct idle_client *idle_clients;
|
struct idle_client *idle_clients;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Creates a new string for the given path that starts with a '/'.
|
||||||
|
* If 'path' already starts with a '/' the returned string is a duplicate
|
||||||
|
* of 'path'.
|
||||||
|
*
|
||||||
|
* The returned string needs to be freed by the caller.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
prepend_slash(const char *path)
|
||||||
|
{
|
||||||
|
char *result;
|
||||||
|
|
||||||
|
if (path[0] == '/')
|
||||||
|
result = strdup(path);
|
||||||
|
else
|
||||||
|
result = safe_asprintf("/%s", path);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Thread: mpd */
|
/* Thread: mpd */
|
||||||
static void *
|
static void *
|
||||||
@ -604,6 +627,10 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
|||||||
{
|
{
|
||||||
client->events |= LISTENER_OPTIONS;
|
client->events |= LISTENER_OPTIONS;
|
||||||
}
|
}
|
||||||
|
else if (0 == strcmp(argv[i], "stored_playlist"))
|
||||||
|
{
|
||||||
|
client->events |= LISTENER_STORED_PLAYLIST;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", argv[i]);
|
DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", argv[i]);
|
||||||
@ -2172,6 +2199,130 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mpd_command_playlistadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
|
{
|
||||||
|
char *vp_playlist;
|
||||||
|
char *vp_item;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!allow_modifying_stored_playlists)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Modifying stored playlists is not enabled");
|
||||||
|
return ACK_ERROR_PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc < 3)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Missing argument for command 'playlistadd'");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!default_pl_dir || strstr(argv[1], ":/"))
|
||||||
|
{
|
||||||
|
// Argument is a virtual path, make sure it starts with a '/'
|
||||||
|
vp_playlist = prepend_slash(argv[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Argument is a playlist name, prepend default playlist directory
|
||||||
|
vp_playlist = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vp_item = prepend_slash(argv[2]);
|
||||||
|
|
||||||
|
ret = library_playlist_add(vp_playlist, vp_item);
|
||||||
|
free(vp_playlist);
|
||||||
|
free(vp_item);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Error saving queue to file '%s'", argv[1]);
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mpd_command_rm(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
|
{
|
||||||
|
char *virtual_path;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!allow_modifying_stored_playlists)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Modifying stored playlists is not enabled");
|
||||||
|
return ACK_ERROR_PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Missing argument for command 'rm'");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!default_pl_dir || strstr(argv[1], ":/"))
|
||||||
|
{
|
||||||
|
// Argument is a virtual path, make sure it starts with a '/'
|
||||||
|
virtual_path = prepend_slash(argv[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Argument is a playlist name, prepend default playlist directory
|
||||||
|
virtual_path = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = library_playlist_remove(virtual_path);
|
||||||
|
free(virtual_path);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Error removing playlist '%s'", argv[1]);
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mpd_command_save(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
|
{
|
||||||
|
char *virtual_path;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!allow_modifying_stored_playlists)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Modifying stored playlists is not enabled");
|
||||||
|
return ACK_ERROR_PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Missing argument for command 'save'");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!default_pl_dir || strstr(argv[1], ":/"))
|
||||||
|
{
|
||||||
|
// Argument is a virtual path, make sure it starts with a '/'
|
||||||
|
virtual_path = prepend_slash(argv[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Argument is a playlist name, prepend default playlist directory
|
||||||
|
virtual_path = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = library_queue_save(virtual_path);
|
||||||
|
free(virtual_path);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
*errmsg = safe_asprintf("Error saving queue to file '%s'", argv[1]);
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
mpd_get_query_params_find(int argc, char **argv, struct query_params *qp)
|
mpd_get_query_params_find(int argc, char **argv, struct query_params *qp)
|
||||||
{
|
{
|
||||||
@ -3944,11 +4095,11 @@ static struct mpd_command mpd_handlers[] =
|
|||||||
.mpdcommand = "load",
|
.mpdcommand = "load",
|
||||||
.handler = mpd_command_load
|
.handler = mpd_command_load
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
.mpdcommand = "playlistadd",
|
.mpdcommand = "playlistadd",
|
||||||
.handler = mpd_command_playlistadd
|
.handler = mpd_command_playlistadd
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
{
|
{
|
||||||
.mpdcommand = "playlistclear",
|
.mpdcommand = "playlistclear",
|
||||||
.handler = mpd_command_playlistclear
|
.handler = mpd_command_playlistclear
|
||||||
@ -3965,6 +4116,7 @@ static struct mpd_command mpd_handlers[] =
|
|||||||
.mpdcommand = "rename",
|
.mpdcommand = "rename",
|
||||||
.handler = mpd_command_rename
|
.handler = mpd_command_rename
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
.mpdcommand = "rm",
|
.mpdcommand = "rm",
|
||||||
.handler = mpd_command_rm
|
.handler = mpd_command_rm
|
||||||
@ -3973,7 +4125,6 @@ static struct mpd_command mpd_handlers[] =
|
|||||||
.mpdcommand = "save",
|
.mpdcommand = "save",
|
||||||
.handler = mpd_command_save
|
.handler = mpd_command_save
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The music database
|
* The music database
|
||||||
@ -4512,6 +4663,10 @@ mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type
|
|||||||
evbuffer_add(client->evbuffer, "changed: options\n", 17);
|
evbuffer_add(client->evbuffer, "changed: options\n", 17);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case LISTENER_STORED_PLAYLIST:
|
||||||
|
evbuffer_add(client->evbuffer, "changed: stored_playlist\n", 25);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
DPRINTF(E_WARN, L_MPD, "Unsupported event type (%d) in notify idle clients.\n", type);
|
DPRINTF(E_WARN, L_MPD, "Unsupported event type (%d) in notify idle clients.\n", type);
|
||||||
return -1;
|
return -1;
|
||||||
@ -4706,6 +4861,7 @@ int mpd_init(void)
|
|||||||
unsigned short http_port;
|
unsigned short http_port;
|
||||||
const char *http_addr;
|
const char *http_addr;
|
||||||
int v6enabled;
|
int v6enabled;
|
||||||
|
const char *pl_dir;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
|
||||||
@ -4806,6 +4962,11 @@ int mpd_init(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "mpd"), "allow_modifying_stored_playlists");
|
||||||
|
pl_dir = cfg_getstr(cfg_getsec(cfg, "mpd"), "default_playlist_directory");
|
||||||
|
if (pl_dir)
|
||||||
|
default_pl_dir = safe_asprintf("/file:%s", pl_dir);
|
||||||
|
|
||||||
DPRINTF(E_INFO, L_MPD, "mpd thread init\n");
|
DPRINTF(E_INFO, L_MPD, "mpd thread init\n");
|
||||||
|
|
||||||
ret = pthread_create(&tid_mpd, NULL, mpd, NULL);
|
ret = pthread_create(&tid_mpd, NULL, mpd, NULL);
|
||||||
@ -4884,4 +5045,6 @@ void mpd_deinit(void)
|
|||||||
|
|
||||||
// Free event base (should free events too)
|
// Free event base (should free events too)
|
||||||
event_base_free(evbase_mpd);
|
event_base_free(evbase_mpd);
|
||||||
|
|
||||||
|
free(default_pl_dir);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user