Initial support for mpd protocol

This commit is contained in:
chme 2014-12-21 20:41:44 +01:00
parent 3aa5a4df30
commit 830054bd71
18 changed files with 4061 additions and 131 deletions

View File

@ -88,6 +88,10 @@ AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa], [use ALSA (default Linux=yes, Fr
use_oss4=false; use_oss4=false;
) )
AC_ARG_ENABLE(mpd, AS_HELP_STRING([--enable-mpd], [enable MPD client protocol support (default=no)]),
use_mpd=true;
CPPFLAGS="${CPPFLAGS} -DMPD")
AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue) AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue)
AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue) AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue)
AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = xtrue) AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = xtrue)
@ -95,6 +99,7 @@ AM_CONDITIONAL(COND_SPOTIFY, test x$use_spotify = xtrue)
AM_CONDITIONAL(COND_LASTFM, test x$use_lastfm = xtrue) AM_CONDITIONAL(COND_LASTFM, test x$use_lastfm = xtrue)
AM_CONDITIONAL(COND_OSS4, test x$use_oss4 = xtrue) AM_CONDITIONAL(COND_OSS4, test x$use_oss4 = xtrue)
AM_CONDITIONAL(COND_ALSA, test x$use_oss4 != xtrue) AM_CONDITIONAL(COND_ALSA, test x$use_oss4 != xtrue)
AM_CONDITIONAL(COND_MPD, test x$use_mpd = xtrue)
dnl Checks for libraries. dnl Checks for libraries.
gl_LIBUNISTRING gl_LIBUNISTRING

View File

@ -21,6 +21,10 @@ if COND_LASTFM
LASTFM_SRC=lastfm.c lastfm.h LASTFM_SRC=lastfm.c lastfm.h
endif endif
if COND_MPD
MPD_SRC=mpd.c mpd.h
endif
if COND_ALSA if COND_ALSA
ALSA_SRC=laudio_alsa.c ALSA_SRC=laudio_alsa.c
endif endif
@ -120,6 +124,7 @@ forked_daapd_SOURCES = main.c \
$(RTSP_SRC) \ $(RTSP_SRC) \
scan-wma.c \ scan-wma.c \
$(SPOTIFY_SRC) $(LASTFM_SRC) \ $(SPOTIFY_SRC) $(LASTFM_SRC) \
$(MPD_SRC) \
$(FLAC_SRC) $(MUSEPACK_SRC) $(FLAC_SRC) $(MUSEPACK_SRC)
nodist_forked_daapd_SOURCES = \ nodist_forked_daapd_SOURCES = \

View File

@ -127,6 +127,13 @@ static cfg_opt_t sec_sqlite[] =
CFG_END() CFG_END()
}; };
/* MPD section structure */
static cfg_opt_t sec_mpd[] =
{
CFG_INT("port", 6600, CFGF_NONE),
CFG_END()
};
/* Config file structure */ /* Config file structure */
static cfg_opt_t toplvl_cfg[] = static cfg_opt_t toplvl_cfg[] =
{ {
@ -136,6 +143,7 @@ static cfg_opt_t toplvl_cfg[] =
CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE), CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
CFG_SEC("spotify", sec_spotify, CFGF_NONE), CFG_SEC("spotify", sec_spotify, CFGF_NONE),
CFG_SEC("sqlite", sec_sqlite, CFGF_NONE), CFG_SEC("sqlite", sec_sqlite, CFGF_NONE),
CFG_SEC("mpd", sec_mpd, CFGF_NONE),
CFG_END() CFG_END()
}; };

660
src/db.c
View File

@ -33,6 +33,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <limits.h>
#include <pthread.h> #include <pthread.h>
@ -134,6 +135,7 @@ static const struct col_type_map mfi_cols_map[] =
{ mfi_offsetof(album_sort), DB_TYPE_STRING }, { mfi_offsetof(album_sort), DB_TYPE_STRING },
{ mfi_offsetof(composer_sort), DB_TYPE_STRING }, { mfi_offsetof(composer_sort), DB_TYPE_STRING },
{ mfi_offsetof(album_artist_sort), DB_TYPE_STRING }, { mfi_offsetof(album_artist_sort), DB_TYPE_STRING },
{ mfi_offsetof(virtual_path), DB_TYPE_STRING },
}; };
/* This list must be kept in sync with /* This list must be kept in sync with
@ -151,6 +153,7 @@ static const struct col_type_map pli_cols_map[] =
{ pli_offsetof(path), DB_TYPE_STRING }, { pli_offsetof(path), DB_TYPE_STRING },
{ pli_offsetof(index), DB_TYPE_INT }, { pli_offsetof(index), DB_TYPE_INT },
{ pli_offsetof(special_id), DB_TYPE_INT }, { pli_offsetof(special_id), DB_TYPE_INT },
{ pli_offsetof(virtual_path), DB_TYPE_STRING },
/* items is computed on the fly */ /* items is computed on the fly */
}; };
@ -218,6 +221,7 @@ static const ssize_t dbmfi_cols_map[] =
dbmfi_offsetof(album_sort), dbmfi_offsetof(album_sort),
dbmfi_offsetof(composer_sort), dbmfi_offsetof(composer_sort),
dbmfi_offsetof(album_artist_sort), dbmfi_offsetof(album_artist_sort),
dbmfi_offsetof(virtual_path),
}; };
/* This list must be kept in sync with /* This list must be kept in sync with
@ -235,6 +239,7 @@ static const ssize_t dbpli_cols_map[] =
dbpli_offsetof(path), dbpli_offsetof(path),
dbpli_offsetof(index), dbpli_offsetof(index),
dbpli_offsetof(special_id), dbpli_offsetof(special_id),
dbmfi_offsetof(virtual_path),
/* items is computed on the fly */ /* items is computed on the fly */
}; };
@ -316,6 +321,22 @@ db_escape_string(const char *str)
return ret; return ret;
} }
void
free_fi(struct filelist_info *fi, int content_only)
{
if (fi->path)
free(fi->path);
if (fi->name)
free(fi->name);
if (fi->parentpath)
free(fi->parentpath);
if (!content_only)
free(fi);
else
memset(fi, 0, sizeof(struct filelist_info));
}
void void
free_pi(struct pairing_info *pi, int content_only) free_pi(struct pairing_info *pi, int content_only)
{ {
@ -406,6 +427,9 @@ free_mfi(struct media_file_info *mfi, int content_only)
if (mfi->album_artist_sort) if (mfi->album_artist_sort)
free(mfi->album_artist_sort); free(mfi->album_artist_sort);
if (mfi->virtual_path)
free(mfi->virtual_path);
if (!content_only) if (!content_only)
free(mfi); free(mfi);
else else
@ -742,9 +766,13 @@ db_purge_cruft(time_t ref)
char *errmsg; char *errmsg;
int i; int i;
int ret; int ret;
char *queries[3] = { NULL, NULL, NULL }; int changes;
char *queries_tmpl[3] = char *query;
char *queries[5] = { NULL, NULL, NULL, NULL, NULL };
char *queries_tmpl[5] =
{ {
"DELETE FROM filelist WHERE type = 3 AND path IN (SELECT path FROM files f WHERE f.db_timestamp < %" PRIi64 ");",
"DELETE FROM filelist WHERE type = 1 AND path IN (SELECT path FROM playlists p WHERE p.type <> 1 AND p.db_timestamp < %" PRIi64 ");",
"DELETE FROM playlistitems WHERE playlistid IN (SELECT id FROM playlists p WHERE p.type <> 1 AND p.db_timestamp < %" PRIi64 ");", "DELETE FROM playlistitems WHERE playlistid IN (SELECT id FROM playlists p WHERE p.type <> 1 AND p.db_timestamp < %" PRIi64 ");",
"DELETE FROM playlists WHERE type <> 1 AND db_timestamp < %" PRIi64 ";", "DELETE FROM playlists WHERE type <> 1 AND db_timestamp < %" PRIi64 ";",
"DELETE FROM files WHERE db_timestamp < %" PRIi64 ";" "DELETE FROM files WHERE db_timestamp < %" PRIi64 ";"
@ -781,6 +809,29 @@ db_purge_cruft(time_t ref)
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl)); DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
} }
// Remove empty directories from filelist table
query = "DELETE FROM filelist WHERE type = 2 AND 0 = (SELECT COUNT(path) FROM filelist f WHERE f.parentpath = filelist.path);";
do
{
DPRINTF(E_DBG, L_DB, "Running purge query '%s'\n", query);
ret = db_exec(query, &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Purge query %d error: %s\n", i, errmsg);
sqlite3_free(errmsg);
break;
}
else
{
changes = sqlite3_changes(hdl);
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", changes);
}
} while (changes > 0);
purge_fail: purge_fail:
for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++) for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
{ {
@ -792,13 +843,14 @@ db_purge_cruft(time_t ref)
void void
db_purge_all(void) db_purge_all(void)
{ {
char *queries[5] = char *queries[6] =
{ {
"DELETE FROM inotify;", "DELETE FROM inotify;",
"DELETE FROM playlistitems;", "DELETE FROM playlistitems;",
"DELETE FROM playlists WHERE type <> 1;", "DELETE FROM playlists WHERE type <> 1;",
"DELETE FROM files;", "DELETE FROM files;",
"DELETE FROM groups;", "DELETE FROM groups;",
"DELETE FROM filelist;"
}; };
char *errmsg; char *errmsg;
int i; int i;
@ -1852,6 +1904,222 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst
return 0; return 0;
} }
/* Filelist */
static int
db_filelist_add(const char *virtual_path, enum filelistitem_type type)
{
char path[PATH_MAX];
char parentpath[PATH_MAX];
char *name;
char *query;
char *errmsg;
int ret;
DPRINTF(E_DBG, L_DB, "Add file to filelist with type %d and virtual path '%s'\n", type, virtual_path);
strcpy(path, virtual_path);
strcpy(parentpath, path);
name = strrchr(parentpath, '/');
*name = '\0';
name++;
query = sqlite3_mprintf("INSERT INTO filelist (path, name, type, parentpath, disabled) VALUES ('%q', '%q', %d, '%q', %d);",
path, name, type, parentpath, 0);
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = db_exec(query, &errmsg);
if (ret == SQLITE_CONSTRAINT)
{
DPRINTF(E_DBG, L_DB, "Path already exists in filelist '%s'\n", path);
sqlite3_free(errmsg);
sqlite3_free(query);
return 0;
}
else if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Error '%s' while runnning '%s'\n", errmsg, query);
sqlite3_free(errmsg);
sqlite3_free(query);
return -1;
}
sqlite3_free(query);
while ((name = strrchr(parentpath, '/')))
{
strcpy(path, parentpath);
*name = '\0';
name++;
query = sqlite3_mprintf("INSERT INTO filelist (path, name, type, parentpath, disabled) VALUES ('%q', '%q', %d, '%q', %d);",
path, name, F_DIR, (*parentpath == '\0' ? "/" : parentpath), 0);
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = db_exec(query, &errmsg);
if (ret == SQLITE_CONSTRAINT)
{
DPRINTF(E_DBG, L_DB, "Path already exists in filelist '%s'\n", path);
sqlite3_free(errmsg);
sqlite3_free(query);
return 0;
}
else if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Error '%s' while runnning '%s'\n", errmsg, query);
sqlite3_free(errmsg);
sqlite3_free(query);
return -1;
}
sqlite3_free(query);
}
return 0;
}
int
db_build_query_filelist(struct query_params *qp, char *parentpath)
{
char *query;
int ret;
query = sqlite3_mprintf("SELECT * FROM filelist WHERE disabled = 0 AND parentpath = '%q' ORDER BY type, name;", parentpath);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return -1;
}
DPRINTF(E_DBG, L_DB, "Starting query '%s'\n", query);
ret = db_blocking_prepare_v2(query, -1, &qp->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;
}
sqlite3_free(query);
return 0;
}
int
db_query_fetch_filelist(struct query_params *qp, struct filelist_info *fi)
{
int ret;
memset(fi, 0, sizeof(struct filelist_info));
if (!qp->stmt)
{
DPRINTF(E_LOG, L_DB, "Query not started!\n");
return -1;
}
ret = db_blocking_step(qp->stmt);
if (ret == SQLITE_DONE)
{
DPRINTF(E_DBG, L_DB, "End of query results\n");
fi->path = NULL;
return 0;
}
else if (ret != SQLITE_ROW)
{
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
return -1;
}
fi->path = strdup((char *)sqlite3_column_text(qp->stmt, 0));
fi->name = strdup((char *)sqlite3_column_text(qp->stmt, 1));
fi->type = sqlite3_column_int(qp->stmt, 2);
fi->parentpath = strdup((char *)sqlite3_column_text(qp->stmt, 3));
fi->disabled = sqlite3_column_int(qp->stmt, 4);
return 0;
}
static struct filelist_info *
db_filelist_fetch_byquery(char *query)
{
struct filelist_info *fi;
sqlite3_stmt *stmt;
int ret;
if (!query)
return NULL;
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
fi = (struct filelist_info *)malloc(sizeof(struct filelist_info));
if (!fi)
{
DPRINTF(E_LOG, L_DB, "Could not allocate struct filelist_info, out of memory\n");
return NULL;
}
memset(fi, 0, sizeof(struct filelist_info));
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));
free(fi);
return NULL;
}
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);
free(fi);
return NULL;
}
fi->path = strdup((char *)sqlite3_column_text(stmt, 0));
fi->name = strdup((char *)sqlite3_column_text(stmt, 1));
fi->type = sqlite3_column_int(stmt, 2);
fi->parentpath = strdup((char *)sqlite3_column_text(stmt, 3));
fi->disabled = sqlite3_column_int(stmt, 4);
sqlite3_finalize(stmt);
return fi;
}
struct filelist_info *
db_filelist_fetch_bypath(const char *path)
{
#define Q_TMPL "SELECT f.* FROM filelist f WHERE f.path = %Q;"
struct filelist_info *fi;
char *query;
query = sqlite3_mprintf(Q_TMPL, path);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return NULL;
}
fi = db_filelist_fetch_byquery(query);
sqlite3_free(query);
return fi;
#undef Q_TMPL
}
/* Files */ /* Files */
int int
@ -2359,6 +2627,30 @@ db_file_fetch_byid(int id)
#undef Q_TMPL #undef Q_TMPL
} }
struct media_file_info *
db_file_fetch_byvirtualpath(char *virtual_path)
{
#define Q_TMPL "SELECT f.* FROM files f WHERE f.virtual_path = %Q;"
struct media_file_info *mfi;
char *query;
query = sqlite3_mprintf(Q_TMPL, virtual_path);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return NULL;
}
mfi = db_file_fetch_byquery(query);
sqlite3_free(query);
return mfi;
#undef Q_TMPL
}
int int
db_file_add(struct media_file_info *mfi) db_file_add(struct media_file_info *mfi)
{ {
@ -2369,7 +2661,7 @@ db_file_add(struct media_file_info *mfi)
" codectype, idx, has_video, contentrating, bits_per_sample, album_artist," \ " codectype, idx, has_video, contentrating, bits_per_sample, album_artist," \
" media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \ " media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \
" songartistid, songalbumid, " \ " songartistid, songalbumid, " \
" title_sort, artist_sort, album_sort, composer_sort, album_artist_sort" \ " title_sort, artist_sort, album_sort, composer_sort, album_artist_sort, virtual_path" \
" ) " \ " ) " \
" VALUES (NULL, '%q', '%q', TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q, TRIM(%Q)," \ " VALUES (NULL, '%q', '%q', TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q, TRIM(%Q)," \
" TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q, %d, %d, %d, %" PRIi64 ", %d, %d," \ " TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q, %d, %d, %d, %" PRIi64 ", %d, %d," \
@ -2378,7 +2670,7 @@ db_file_add(struct media_file_info *mfi)
" %Q, %d, %d, %d, %d, TRIM(%Q)," \ " %Q, %d, %d, %d, %d, TRIM(%Q)," \
" %d, TRIM(%Q), TRIM(%Q), TRIM(%Q), %d, %d," \ " %d, TRIM(%Q), TRIM(%Q), TRIM(%Q), %d, %d," \
" daap_songalbumid(LOWER(TRIM(%Q)), ''), daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q))), " \ " daap_songalbumid(LOWER(TRIM(%Q)), ''), daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q))), " \
" TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q));" " TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q));"
char *query; char *query;
char *errmsg; char *errmsg;
@ -2411,7 +2703,7 @@ db_file_add(struct media_file_info *mfi)
mfi->media_kind, mfi->tv_series_name, mfi->tv_episode_num_str, mfi->media_kind, mfi->tv_series_name, mfi->tv_episode_num_str,
mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num, mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num,
mfi->album_artist, mfi->album_artist, mfi->album, mfi->title_sort, mfi->artist_sort, mfi->album_sort, mfi->album_artist, mfi->album_artist, mfi->album, mfi->title_sort, mfi->artist_sort, mfi->album_sort,
mfi->composer_sort, mfi->album_artist_sort); mfi->composer_sort, mfi->album_artist_sort, mfi->virtual_path);
if (!query) if (!query)
{ {
@ -2433,6 +2725,8 @@ db_file_add(struct media_file_info *mfi)
sqlite3_free(query); sqlite3_free(query);
db_filelist_add(mfi->virtual_path, F_FILE);
cache_daap_trigger(); cache_daap_trigger();
return 0; return 0;
@ -2455,8 +2749,11 @@ db_file_update(struct media_file_info *mfi)
" media_kind = %d, tv_series_name = TRIM(%Q), tv_episode_num_str = TRIM(%Q)," \ " media_kind = %d, tv_series_name = TRIM(%Q), tv_episode_num_str = TRIM(%Q)," \
" tv_network_name = TRIM(%Q), tv_episode_sort = %d, tv_season_num = %d," \ " tv_network_name = TRIM(%Q), tv_episode_sort = %d, tv_season_num = %d," \
" songartistid = daap_songalbumid(LOWER(TRIM(%Q)), ''), songalbumid = daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q)))," \ " songartistid = daap_songalbumid(LOWER(TRIM(%Q)), ''), songalbumid = daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q)))," \
" title_sort = TRIM(%Q), artist_sort = TRIM(%Q), album_sort = TRIM(%Q), composer_sort = TRIM(%Q), album_artist_sort = TRIM(%Q)" \ " title_sort = TRIM(%Q), artist_sort = TRIM(%Q), album_sort = TRIM(%Q), composer_sort = TRIM(%Q), album_artist_sort = TRIM(%Q)," \
" virtual_path = TRIM(%Q)" \
" WHERE id = %d;" " WHERE id = %d;"
// struct media_file_info *oldmfi;
char *query; char *query;
char *errmsg; char *errmsg;
int ret; int ret;
@ -2467,6 +2764,18 @@ db_file_update(struct media_file_info *mfi)
return -1; return -1;
} }
/*
oldmfi = db_file_fetch_byid(mfi->id);
if (!oldmfi)
{
DPRINTF(E_WARN, L_DB, "File with id '%d' does not exist\n", mfi->id);
return -1;
}
free_mfi(oldmfi, 0);
*/
mfi->db_timestamp = (uint64_t)time(NULL); mfi->db_timestamp = (uint64_t)time(NULL);
if (mfi->time_modified == 0) if (mfi->time_modified == 0)
@ -2486,7 +2795,7 @@ db_file_update(struct media_file_info *mfi)
mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num, mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num,
mfi->album_artist, mfi->album_artist, mfi->album, mfi->album_artist, mfi->album_artist, mfi->album,
mfi->title_sort, mfi->artist_sort, mfi->album_sort, mfi->title_sort, mfi->artist_sort, mfi->album_sort,
mfi->composer_sort, mfi->album_artist_sort, mfi->composer_sort, mfi->album_artist_sort, mfi->virtual_path,
mfi->id); mfi->id);
if (!query) if (!query)
@ -2509,6 +2818,8 @@ db_file_update(struct media_file_info *mfi)
sqlite3_free(query); sqlite3_free(query);
db_filelist_add(mfi->virtual_path, F_FILE);
cache_daap_trigger(); cache_daap_trigger();
return 0; return 0;
@ -2865,6 +3176,30 @@ db_pl_fetch_bypath(char *path)
#undef Q_TMPL #undef Q_TMPL
} }
struct playlist_info *
db_pl_fetch_byvirtualpath(char *virtual_path)
{
#define Q_TMPL "SELECT p.* FROM playlists p WHERE p.virtual_path = '%q';"
struct playlist_info *pli;
char *query;
query = sqlite3_mprintf(Q_TMPL, virtual_path);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return NULL;
}
pli = db_pl_fetch_byquery(query);
sqlite3_free(query);
return pli;
#undef Q_TMPL
}
struct playlist_info * struct playlist_info *
db_pl_fetch_byid(int id) db_pl_fetch_byid(int id)
{ {
@ -2914,11 +3249,11 @@ db_pl_fetch_bytitlepath(char *title, char *path)
} }
int int
db_pl_add(char *title, char *path, int *id) db_pl_add(char *title, char *path, char *virtual_path, int *id)
{ {
#define QDUP_TMPL "SELECT COUNT(*) FROM playlists p WHERE p.title = '%q' AND p.path = '%q';" #define QDUP_TMPL "SELECT COUNT(*) FROM playlists p WHERE p.title = '%q' AND p.path = '%q';"
#define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id)" \ #define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id, virtual_path)" \
" VALUES ('%q', 0, NULL, %" PRIi64 ", 0, '%q', 0, 0);" " VALUES ('%q', 0, NULL, %" PRIi64 ", 0, '%q', 0, 0, '%q');"
char *query; char *query;
char *errmsg; char *errmsg;
int ret; int ret;
@ -2942,7 +3277,7 @@ db_pl_add(char *title, char *path, int *id)
} }
/* Add */ /* Add */
query = sqlite3_mprintf(QADD_TMPL, title, (int64_t)time(NULL), path); query = sqlite3_mprintf(QADD_TMPL, title, (int64_t)time(NULL), path, virtual_path);
if (!query) if (!query)
{ {
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
@ -2972,6 +3307,8 @@ db_pl_add(char *title, char *path, int *id)
DPRINTF(E_DBG, L_DB, "Added playlist %s (path %s) with id %d\n", title, path, *id); DPRINTF(E_DBG, L_DB, "Added playlist %s (path %s) with id %d\n", title, path, *id);
db_filelist_add(virtual_path, F_PLAYLIST);
return 0; return 0;
#undef QDUP_TMPL #undef QDUP_TMPL
@ -3003,14 +3340,22 @@ db_pl_add_item_byid(int plid, int fileid)
} }
int int
db_pl_update(char *title, char *path, int id) db_pl_update(char *title, char *path, char *virtual_path, int id)
{ {
#define Q_TMPL "UPDATE playlists SET title = '%q', db_timestamp = %" PRIi64 ", disabled = 0, path = '%q' WHERE id = %d;" #define Q_TMPL "UPDATE playlists SET title = '%q', db_timestamp = %" PRIi64 ", disabled = 0, path = '%q', virtual_path = '%q' WHERE id = %d;"
char *query; char *query;
int ret;
query = sqlite3_mprintf(Q_TMPL, title, (int64_t)time(NULL), path, id); query = sqlite3_mprintf(Q_TMPL, title, (int64_t)time(NULL), path, virtual_path, id);
return db_query_run(query, 1, 0); ret = db_query_run(query, 1, 0);
if (ret == 0)
{
db_filelist_add(virtual_path, F_PLAYLIST);
}
return ret;
#undef Q_TMPL #undef Q_TMPL
} }
@ -3108,6 +3453,7 @@ db_pl_enable_bycookie(uint32_t cookie, char *path)
} }
/* Groups */ /* Groups */
int int
db_groups_clear(void) db_groups_clear(void)
@ -3314,11 +3660,12 @@ db_pairing_fetch_byguid(struct pairing_info *pi)
void void
db_spotify_purge(void) db_spotify_purge(void)
{ {
char *queries[3] = char *queries[4] =
{ {
"DELETE FROM files WHERE path LIKE 'spotify:%%';", "DELETE FROM files WHERE path LIKE 'spotify:%%';",
"DELETE FROM playlistitems WHERE filepath LIKE 'spotify:%%';", "DELETE FROM playlistitems WHERE filepath LIKE 'spotify:%%';",
"DELETE FROM playlists WHERE path LIKE 'spotify:%%';", "DELETE FROM playlists WHERE path LIKE 'spotify:%%';",
"DELETE FROM filelist WHERE path LIKE '/spotify:%%';",
}; };
int i; int i;
int ret; int ret;
@ -3336,11 +3683,13 @@ db_spotify_purge(void)
void void
db_spotify_pl_delete(int id) db_spotify_pl_delete(int id)
{ {
char *queries_tmpl[3] = char *queries_tmpl[5] =
{ {
"DELETE FROM filelist WHERE path IN (SELECT virtual_path FROM playlists WHERE id = %d);",
"DELETE FROM playlists WHERE id = %d;", "DELETE FROM playlists WHERE id = %d;",
"DELETE FROM playlistitems WHERE playlistid = %d;", "DELETE FROM playlistitems WHERE playlistid = %d;",
"DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems);", "DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems);",
"DELETE FROM filelist WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT virtual_path FROM FILES WHERE path LIKE 'spotify:%%');",
}; };
char *query; char *query;
int i; int i;
@ -4275,7 +4624,8 @@ db_perthread_deinit(void)
" artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \ " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" virtual_path VARCHAR(4096) DEFAULT NULL" \
");" ");"
#define T_PL \ #define T_PL \
@ -4288,7 +4638,8 @@ db_perthread_deinit(void)
" disabled INTEGER DEFAULT 0," \ " disabled INTEGER DEFAULT 0," \
" path VARCHAR(4096)," \ " path VARCHAR(4096)," \
" idx INTEGER NOT NULL," \ " idx INTEGER NOT NULL," \
" special_id INTEGER DEFAULT 0" \ " special_id INTEGER DEFAULT 0," \
" virtual_path VARCHAR(4096)" \
");" ");"
#define T_PLITEMS \ #define T_PLITEMS \
@ -4328,6 +4679,15 @@ db_perthread_deinit(void)
" path VARCHAR(4096) NOT NULL" \ " path VARCHAR(4096) NOT NULL" \
");" ");"
#define T_FILELIST \
"CREATE TABLE IF NOT EXISTS filelist (" \
" path VARCHAR(4096) PRIMARY KEY NOT NULL," \
" name VARCHAR(255) NOT NULL," \
" type INTEGER NOT NULL," \
" parentpath VARCHAR(4096) NOT NULL," \
" disabled INTEGER DEFAULT 0" \
");"
#define TRG_GROUPS_INSERT_FILES \ #define TRG_GROUPS_INSERT_FILES \
"CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
" BEGIN" \ " BEGIN" \
@ -4372,15 +4732,15 @@ db_perthread_deinit(void)
" VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);" " VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);"
*/ */
#define SCHEMA_VERSION_MAJOR 15 #define SCHEMA_VERSION_MAJOR 16
#define SCHEMA_VERSION_MINOR 01 #define SCHEMA_VERSION_MINOR 00
// Q_SCVER should be deprecated/removed at v16 // Q_SCVER should be deprecated/removed at v16
#define Q_SCVER \ #define Q_SCVER \
"INSERT INTO admin (key, value) VALUES ('schema_version', '15');" "INSERT INTO admin (key, value) VALUES ('schema_version', '16');"
#define Q_SCVER_MAJOR \ #define Q_SCVER_MAJOR \
"INSERT INTO admin (key, value) VALUES ('schema_version_major', '15');" "INSERT INTO admin (key, value) VALUES ('schema_version_major', '16');"
#define Q_SCVER_MINOR \ #define Q_SCVER_MINOR \
"INSERT INTO admin (key, value) VALUES ('schema_version_minor', '01');" "INSERT INTO admin (key, value) VALUES ('schema_version_minor', '00');"
struct db_init_query { struct db_init_query {
char *query; char *query;
@ -4398,6 +4758,8 @@ static const struct db_init_query db_init_table_queries[] =
{ T_SPEAKERS, "create table speakers" }, { T_SPEAKERS, "create table speakers" },
{ T_INOTIFY, "create table inotify" }, { T_INOTIFY, "create table inotify" },
{ T_FILELIST, "create table filelist" },
{ TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" }, { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" },
{ TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" }, { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" },
@ -4472,6 +4834,9 @@ static const struct db_init_query db_init_table_queries[] =
#define I_PAIRING \ #define I_PAIRING \
"CREATE INDEX IF NOT EXISTS idx_pairingguid ON pairings(guid);" "CREATE INDEX IF NOT EXISTS idx_pairingguid ON pairings(guid);"
#define I_FILELIST \
"CREATE INDEX IF NOT EXISTS idx_parentpath_disabled ON filelist(disabled, parentpath, type, name);"
static const struct db_init_query db_init_index_queries[] = static const struct db_init_query db_init_index_queries[] =
{ {
{ I_RESCAN, "create rescan index" }, { I_RESCAN, "create rescan index" },
@ -4496,6 +4861,8 @@ static const struct db_init_query db_init_index_queries[] =
{ I_GRP_PERSIST, "create groups persistentid index" }, { I_GRP_PERSIST, "create groups persistentid index" },
{ I_PAIRING, "create pairing guid index" }, { I_PAIRING, "create pairing guid index" },
{ I_FILELIST, "create filelist index" },
}; };
static int static int
@ -5428,6 +5795,240 @@ static const struct db_init_query db_upgrade_v1501_queries[] =
{ U_V1501_SCVER_MINOR, "set schema_version_minor to 01" }, { U_V1501_SCVER_MINOR, "set schema_version_minor to 01" },
}; };
/* Upgrade from schema v15.01 to v16 */
#define U_V16_CREATE_TBL_FILELIST \
"CREATE TABLE IF NOT EXISTS filelist (" \
" path VARCHAR(4096) PRIMARY KEY NOT NULL,"\
" name VARCHAR(255) NOT NULL," \
" type INTEGER NOT NULL," \
" parentpath VARCHAR(4096) NOT NULL," \
" disabled INTEGER DEFAULT 0" \
");"
#define U_V16_CREATE_IDX_FILELIST \
"CREATE INDEX IF NOT EXISTS idx_parentpath_disabled ON filelist(disabled, parentpath, type, name);"
#define U_V16_ALTER_TBL_FILES_ADD_COL \
"ALTER TABLE files ADD COLUMN virtual_path VARCHAR(4096) DEFAULT NULL;"
#define U_V16_ALTER_TBL_PL_ADD_COL \
"ALTER TABLE playlists ADD COLUMN virtual_path VARCHAR(4096) DEFAULT NULL;"
#define U_V16_SCVER \
"UPDATE admin SET value = '16' WHERE key = 'schema_version';"
#define U_V1600_SCVER_MAJOR \
"UPDATE admin SET value = '16' WHERE key = 'schema_version_major';"
#define U_V1600_SCVER_MINOR \
"UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';"
static const struct db_init_query db_upgrade_v16_queries[] =
{
{ U_V16_CREATE_TBL_FILELIST, "create new table filelist" },
{ U_V16_CREATE_IDX_FILELIST, "create index filelist" },
{ U_V16_ALTER_TBL_FILES_ADD_COL, "alter table files add column virtual_path" },
{ U_V16_ALTER_TBL_PL_ADD_COL, "alter table playlists add column virtual_path" },
{ U_V16_SCVER, "set schema_version to 16" },
{ U_V1600_SCVER_MAJOR, "set schema_version_major to 16" },
{ U_V1600_SCVER_MINOR, "set schema_version_minor to 00" },
};
static int
db_upgrade_v16_filelist_add(const char *virtual_path, int type)
{
char path[PATH_MAX];
char parentpath[PATH_MAX];
char *name;
char *query;
char *errmsg;
int ret;
strcpy(path, virtual_path);
strcpy(parentpath, path);
name = strrchr(parentpath, '/');
*name = '\0';
name++;
query = sqlite3_mprintf("INSERT INTO filelist (path, name, type, parentpath, disabled) VALUES ('%q', '%q', %d, '%q', %d);", path, name, type, parentpath, 0);
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
if (ret == SQLITE_CONSTRAINT)
{
DPRINTF(E_DBG, L_DB, "Path already exists in filelist '%s'\n", path);
sqlite3_free(errmsg);
sqlite3_free(query);
return 0;
}
else if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Error '%s' while runnning '%s'\n", errmsg, query);
sqlite3_free(errmsg);
sqlite3_free(query);
return -1;
}
sqlite3_free(query);
while ((name = strrchr(parentpath, '/')))
{
strcpy(path, parentpath);
*name = '\0';
name++;
query = sqlite3_mprintf("INSERT INTO filelist (path, name, type, parentpath, disabled) VALUES ('%q', '%q', %d, '%q', %d);", path, name, F_DIR, (*parentpath == '\0' ? "/" : parentpath), 0);
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
if (ret == SQLITE_CONSTRAINT)
{
DPRINTF(E_DBG, L_DB, "Path already exists in filelist '%s'\n", path);
sqlite3_free(errmsg);
sqlite3_free(query);
return 0;
}
else if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Error '%s' while runnning '%s'\n", errmsg, query);
sqlite3_free(errmsg);
sqlite3_free(query);
return -1;
}
sqlite3_free(query);
}
return 0;
}
static int
db_upgrade_v16(void)
{
sqlite3_stmt *stmt;
char *query;
char *uquery;
char *errmsg;
char *artist;
char *album;
char *title;
int id;
char *path;
int data_kind;
char virtual_path[PATH_MAX];
int ret;
query = "SELECT id, album_artist, album, title, path, data_kind FROM files;";
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
return -1;
}
while ((ret = sqlite3_step(stmt)) == SQLITE_ROW)
{
id = sqlite3_column_int(stmt, 0);
artist = (char *)sqlite3_column_text(stmt, 1);
album = (char *)sqlite3_column_text(stmt, 2);
title = (char *)sqlite3_column_text(stmt, 3);
path = (char *)sqlite3_column_text(stmt, 4);
data_kind = sqlite3_column_int(stmt, 5);
if (strncmp(path, "http:", strlen("http:")) == 0)
{
snprintf(virtual_path, PATH_MAX, "/http:/%s", title);
}
else if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", artist, album, title);
}
else
{
snprintf(virtual_path, PATH_MAX, "/file:%s", path);
}
uquery = sqlite3_mprintf("UPDATE files SET virtual_path = '%q' WHERE id = %d;", virtual_path, id);
ret = sqlite3_exec(hdl, uquery, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Error updating files: %s\n", errmsg);
}
if (data_kind == 0 /* Real file*/
|| data_kind == 1 /* URL */
|| data_kind == 2 /* Spotify */)
{
ret = db_upgrade_v16_filelist_add(virtual_path, 3); /* Real file, spotify url, http url */
if (ret < 0)
{
DPRINTF(E_LOG, L_DB, "Error updating filelist for file %d\n", id);
}
}
sqlite3_free(uquery);
sqlite3_free(errmsg);
}
sqlite3_finalize(stmt);
query = "SELECT id, title, path, type FROM playlists;";
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
return -1;
}
while ((ret = sqlite3_step(stmt)) == SQLITE_ROW)
{
id = sqlite3_column_int(stmt, 0);
title = (char *)sqlite3_column_text(stmt, 1);
path = (char *)sqlite3_column_text(stmt, 2);
data_kind = sqlite3_column_int(stmt, 3);
if (data_kind == 0) /* Excludes default playlists */
{
if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", title);
}
else
{
snprintf(virtual_path, PATH_MAX, "/file:%s", path);
}
uquery = sqlite3_mprintf("UPDATE playlists SET virtual_path = '%q' WHERE id = %d;", virtual_path, id);
ret = sqlite3_exec(hdl, uquery, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Error updating playlists: %s\n", errmsg);
}
sqlite3_free(uquery);
sqlite3_free(errmsg);
ret = db_upgrade_v16_filelist_add(virtual_path, 1); /* Playlists */
if (ret < 0)
{
DPRINTF(E_LOG, L_DB, "Error updating filelist for playlist %d\n", id);
}
}
}
sqlite3_free(errmsg);
sqlite3_finalize(stmt);
return 0;
}
static int static int
db_check_version(void) db_check_version(void)
{ {
@ -5539,6 +6140,15 @@ db_check_version(void)
case 1500: case 1500:
ret = db_generic_upgrade(db_upgrade_v1501_queries, sizeof(db_upgrade_v1501_queries) / sizeof(db_upgrade_v1501_queries[0])); ret = db_generic_upgrade(db_upgrade_v1501_queries, sizeof(db_upgrade_v1501_queries) / sizeof(db_upgrade_v1501_queries[0]));
/* FALLTHROUGH */
case 1501:
ret = db_generic_upgrade(db_upgrade_v16_queries, sizeof(db_upgrade_v16_queries) / sizeof(db_upgrade_v16_queries[0]));
if (ret < 0)
return -1;
ret = db_upgrade_v16();
if (ret < 0) if (ret < 0)
return -1; return -1;

View File

@ -48,6 +48,13 @@ enum query_type {
#define ARTWORK_PARENTDIR 5 #define ARTWORK_PARENTDIR 5
#define ARTWORK_SPOTIFY 6 #define ARTWORK_SPOTIFY 6
enum filelistitem_type {
F_PLAYLIST = 1,
F_DIR = 2,
F_FILE = 3,
F_URL = 4,
};
struct query_params { struct query_params {
/* Query parameters, filled in by caller */ /* Query parameters, filled in by caller */
enum query_type type; enum query_type type;
@ -149,6 +156,8 @@ struct media_file_info {
char *album_sort; char *album_sort;
char *composer_sort; char *composer_sort;
char *album_artist_sort; char *album_artist_sort;
char *virtual_path;
}; };
#define mfi_offsetof(field) offsetof(struct media_file_info, field) #define mfi_offsetof(field) offsetof(struct media_file_info, field)
@ -170,6 +179,7 @@ struct playlist_info {
char *path; /* path of underlying playlist */ char *path; /* path of underlying playlist */
uint32_t index; /* index of playlist for paths with multiple playlists */ uint32_t index; /* index of playlist for paths with multiple playlists */
uint32_t special_id; /* iTunes identifies certain 'special' playlists with special meaning */ uint32_t special_id; /* iTunes identifies certain 'special' playlists with special meaning */
char *virtual_path; /* virtual path of underlying playlist */
}; };
#define pli_offsetof(field) offsetof(struct playlist_info, field) #define pli_offsetof(field) offsetof(struct playlist_info, field)
@ -273,10 +283,19 @@ struct db_media_file_info {
char *album_sort; char *album_sort;
char *composer_sort; char *composer_sort;
char *album_artist_sort; char *album_artist_sort;
char *virtual_path;
}; };
#define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field) #define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field)
struct filelist_info {
char *path;
char *name;
enum filelistitem_type type;
char *parentpath;
int disabled;
};
struct watch_info { struct watch_info {
int wd; int wd;
char *path; char *path;
@ -300,6 +319,9 @@ db_escape_string(const char *str);
void void
free_pi(struct pairing_info *pi, int content_only); free_pi(struct pairing_info *pi, int content_only);
void
free_fi(struct filelist_info *fi, int content_only);
void void
free_mfi(struct media_file_info *mfi, int content_only); free_mfi(struct media_file_info *mfi, int content_only);
@ -394,6 +416,9 @@ db_file_stamp_bypath(char *path, time_t *stamp, int *id);
struct media_file_info * struct media_file_info *
db_file_fetch_byid(int id); db_file_fetch_byid(int id);
struct media_file_info *
db_file_fetch_byvirtualpath(char *path);
int int
db_file_add(struct media_file_info *mfi); db_file_add(struct media_file_info *mfi);
@ -425,11 +450,14 @@ db_pl_ping_bymatch(char *path, int isdir);
struct playlist_info * struct playlist_info *
db_pl_fetch_bypath(char *path); db_pl_fetch_bypath(char *path);
struct playlist_info *
db_pl_fetch_byvirtualpath(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);
int int
db_pl_add(char *title, char *path, int *id); db_pl_add(char *title, char *path, char *virtual_path, int *id);
int int
db_pl_add_item_bypath(int plid, char *path); db_pl_add_item_bypath(int plid, char *path);
@ -441,7 +469,7 @@ void
db_pl_clear_items(int id); db_pl_clear_items(int id);
int int
db_pl_update(char *title, char *path, int id); db_pl_update(char *title, char *path, char *virtual_path, int id);
void void
db_pl_delete(int id); db_pl_delete(int id);
@ -465,6 +493,16 @@ db_groups_clear(void);
int int
db_group_persistentid_byid(int id, int64_t *persistentid); db_group_persistentid_byid(int id, int64_t *persistentid);
/* Filelist */
int
db_build_query_filelist(struct query_params *qp, char *path);
int
db_query_fetch_filelist(struct query_params *qp, struct filelist_info *fi);
struct filelist_info *
db_filelist_fetch_bypath(const char *path);
/* Remotes */ /* Remotes */
int int
db_pairing_add(struct pairing_info *pi); db_pairing_add(struct pairing_info *pi);

View File

@ -77,6 +77,22 @@
#endif #endif
struct filescanner_command;
typedef int (*cmd_func)(struct filescanner_command *cmd);
struct filescanner_command
{
pthread_mutex_t lck;
pthread_cond_t cond;
cmd_func func;
int nonblock;
int ret;
};
#define F_SCAN_BULK (1 << 0) #define F_SCAN_BULK (1 << 0)
#define F_SCAN_RESCAN (1 << 1) #define F_SCAN_RESCAN (1 << 1)
#define F_SCAN_FAST (1 << 2) #define F_SCAN_FAST (1 << 2)
@ -108,6 +124,7 @@ struct stacked_dir {
}; };
static int cmd_pipe[2];
#ifdef USE_EVENTFD #ifdef USE_EVENTFD
static int exit_efd; static int exit_efd;
#else #else
@ -118,6 +135,7 @@ static int inofd;
static struct event_base *evbase_scan; static struct event_base *evbase_scan;
static struct event inoev; static struct event inoev;
static struct event exitev; static struct event exitev;
static struct event cmdev;
static pthread_t tid_scan; static pthread_t tid_scan;
static struct deferred_pl *playlists; static struct deferred_pl *playlists;
static struct stacked_dir *dirstack; static struct stacked_dir *dirstack;
@ -132,6 +150,46 @@ static int
inofd_event_set(void); inofd_event_set(void);
static void static void
inofd_event_unset(void); inofd_event_unset(void);
static int
filescanner_initscan(struct filescanner_command *cmd);
static int
filescanner_fullrescan(struct filescanner_command *cmd);
/* ---------------------------- COMMAND EXECUTION -------------------------- */
static int
send_command(struct filescanner_command *cmd)
{
int ret;
if (!cmd->func)
{
DPRINTF(E_LOG, L_CACHE, "BUG: cmd->func is NULL!\n");
return -1;
}
ret = write(cmd_pipe[1], &cmd, sizeof(cmd));
if (ret != sizeof(cmd))
{
DPRINTF(E_LOG, L_CACHE, "Could not send command: %s\n", strerror(errno));
return -1;
}
return 0;
}
static int
nonblock_command(struct filescanner_command *cmd)
{
int ret;
ret = send_command(cmd);
if (ret < 0)
return -1;
return 0;
}
static int static int
push_dir(struct stacked_dir **s, char *path) push_dir(struct stacked_dir **s, char *path)
@ -573,6 +631,7 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
char *filename; char *filename;
time_t stamp; time_t stamp;
int id; int id;
char virtual_path[PATH_MAX];
int ret; int ret;
filename = strrchr(path, '/'); filename = strrchr(path, '/');
@ -678,6 +737,22 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
fixup_tags(mfi); fixup_tags(mfi);
if (type & F_SCAN_TYPE_FILE)
{
snprintf(virtual_path, PATH_MAX, "/file:%s", mfi->path);
mfi->virtual_path = strdup(virtual_path);
}
else if (type & F_SCAN_TYPE_URL)
{
snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
else if (type & F_SCAN_TYPE_SPOTIFY)
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi->album_artist, mfi->album, mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
if (mfi->id == 0) if (mfi->id == 0)
db_file_add(mfi); db_file_add(mfi);
else else
@ -815,11 +890,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file); DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file);
inofd_event_unset(); // Clears all inotify watches filescanner_initscan(NULL);
db_watch_clear();
inofd_event_set();
bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN);
break; break;
case FILE_CTRL_FULLSCAN: case FILE_CTRL_FULLSCAN:
@ -828,13 +899,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file); DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file);
player_playback_stop(); filescanner_fullrescan(NULL);
player_queue_clear();
inofd_event_unset(); // Clears all inotify watches
db_purge_all(); // Clears files, playlists, playlistitems, inotify and groups
inofd_event_set();
bulk_scan(F_SCAN_BULK);
break; break;
default: default:
@ -1210,7 +1275,6 @@ filescanner(void *arg)
DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n"); DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n");
db_perthread_deinit(); db_perthread_deinit();
//artworkcache_perthread_deinit();
pthread_exit(NULL); pthread_exit(NULL);
} }
@ -1872,6 +1936,111 @@ exit_cb(int fd, short event, void *arg)
scan_exit = 1; scan_exit = 1;
} }
static void
command_cb(int fd, short what, void *arg)
{
struct filescanner_command *cmd;
int ret;
ret = read(cmd_pipe[0], &cmd, sizeof(cmd));
if (ret != sizeof(cmd))
{
DPRINTF(E_LOG, L_CACHE, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
goto readd;
}
if (cmd->nonblock)
{
cmd->func(cmd);
free(cmd);
goto readd;
}
pthread_mutex_lock(&cmd->lck);
ret = cmd->func(cmd);
cmd->ret = ret;
pthread_cond_signal(&cmd->cond);
pthread_mutex_unlock(&cmd->lck);
readd:
event_add(&cmdev, NULL);
}
static int
filescanner_initscan(struct filescanner_command *cmd)
{
DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered\n");
inofd_event_unset(); // Clears all inotify watches
db_watch_clear();
inofd_event_set();
bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN);
return 0;
}
static int
filescanner_fullrescan(struct filescanner_command *cmd)
{
DPRINTF(E_LOG, L_SCAN, "Full rescan triggered\n");
player_playback_stop();
player_queue_clear();
inofd_event_unset(); // Clears all inotify watches
db_purge_all(); // Clears files, playlists, playlistitems, inotify and groups
inofd_event_set();
bulk_scan(F_SCAN_BULK);
return 0;
}
void
filescanner_trigger_initscan(void)
{
struct filescanner_command *cmd;
cmd = (struct filescanner_command *)malloc(sizeof(struct filescanner_command));
if (!cmd)
{
DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
return;
}
memset(cmd, 0, sizeof(struct filescanner_command));
cmd->nonblock = 1;
cmd->func = filescanner_initscan;
nonblock_command(cmd);
}
void
filescanner_trigger_fullrescan(void)
{
struct filescanner_command *cmd;
cmd = (struct filescanner_command *)malloc(sizeof(struct filescanner_command));
if (!cmd)
{
DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
return;
}
memset(cmd, 0, sizeof(struct filescanner_command));
cmd->nonblock = 1;
cmd->func = filescanner_fullrescan;
nonblock_command(cmd);
}
/* Thread: main */ /* Thread: main */
int int
filescanner_init(void) filescanner_init(void)
@ -1924,6 +2093,21 @@ filescanner_init(void)
event_base_set(evbase_scan, &exitev); event_base_set(evbase_scan, &exitev);
event_add(&exitev, NULL); event_add(&exitev, NULL);
# if defined(__linux__)
ret = pipe2(cmd_pipe, O_CLOEXEC);
# else
ret = pipe(cmd_pipe);
# endif
if (ret < 0)
{
DPRINTF(E_LOG, L_CACHE, "Could not create command pipe: %s\n", strerror(errno));
goto cmd_fail;
}
event_set(&cmdev, cmd_pipe[0], EV_READ, command_cb, NULL);
event_base_set(evbase_scan, &exitev);
event_add(&cmdev, NULL);
ret = pthread_create(&tid_scan, NULL, filescanner, NULL); ret = pthread_create(&tid_scan, NULL, filescanner, NULL);
if (ret != 0) if (ret != 0)
{ {
@ -1935,6 +2119,9 @@ filescanner_init(void)
return 0; return 0;
thread_fail: thread_fail:
cmd_fail:
close(cmd_pipe[0]);
close(cmd_pipe[1]);
close(inofd); close(inofd);
ino_fail: ino_fail:
#ifdef USE_EVENTFD #ifdef USE_EVENTFD
@ -1993,5 +2180,7 @@ filescanner_deinit(void)
close(exit_pipe[0]); close(exit_pipe[0]);
close(exit_pipe[1]); close(exit_pipe[1]);
#endif #endif
close(cmd_pipe[0]);
close(cmd_pipe[1]);
event_base_free(evbase_scan); event_base_free(evbase_scan);
} }

View File

@ -36,4 +36,10 @@ void
scan_itunes_itml(char *file); scan_itunes_itml(char *file);
#endif #endif
void
filescanner_trigger_initscan(void);
void
filescanner_trigger_fullrescan(void);
#endif /* !__FILESCANNER_H__ */ #endif /* !__FILESCANNER_H__ */

View File

@ -680,6 +680,7 @@ process_pls(plist_t playlists, char *file)
int pl_id; int pl_id;
uint32_t alen; uint32_t alen;
uint32_t i; uint32_t i;
char virtual_path[PATH_MAX];
int ret; int ret;
alen = plist_array_get_size(playlists); alen = plist_array_get_size(playlists);
@ -735,7 +736,8 @@ process_pls(plist_t playlists, char *file)
if (pl_id == 0) if (pl_id == 0)
{ {
ret = db_pl_add(name, file, &pl_id); snprintf(virtual_path, PATH_MAX, "/file:%s", file);
ret = db_pl_add(name, file, virtual_path, &pl_id);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Error adding iTunes playlist '%s' (%s)\n", name, file); DPRINTF(E_LOG, L_SCAN, "Error adding iTunes playlist '%s' (%s)\n", name, file);

View File

@ -91,6 +91,7 @@ scan_playlist(char *file, time_t mtime)
int pl_format; int pl_format;
int mfi_id; int mfi_id;
int ret; int ret;
char virtual_path[PATH_MAX];
int i; int i;
DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file); DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file);
@ -158,7 +159,9 @@ scan_playlist(char *file, time_t mtime)
if (ptr) if (ptr)
*ptr = '.'; *ptr = '.';
ret = db_pl_add(buf, file, &pl_id); snprintf(virtual_path, PATH_MAX, "/file:%s", file);
ret = db_pl_add(buf, file, virtual_path, &pl_id);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file);

View File

@ -725,6 +725,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
const char *cuequery; const char *cuequery;
const char *param; const char *param;
uint32_t id; uint32_t id;
uint32_t pos;
int clear; int clear;
int ret; int ret;
@ -773,19 +774,21 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
dacp_propset_shufflestate(param, NULL); dacp_propset_shufflestate(param, NULL);
id = 0; id = 0;
pos = 0;
param = evhttp_find_header(query, "index"); param = evhttp_find_header(query, "index");
if (param) if (param)
{ {
ret = safe_atou32(param, &id); ret = safe_atou32(param, &pos);
if (ret < 0) if (ret < 0)
DPRINTF(E_LOG, L_DACP, "Invalid index (%s) in cue request\n", param); DPRINTF(E_LOG, L_DACP, "Invalid index (%s) in cue request\n", param);
} }
/* If selection was from Up Next queue (command will be playnow), then index is relative */ /* If selection was from Up Next queue (command will be playnow), then index is relative */
//TODO playnow for mode = 1 (index is relativ to the history queue
if ((param = evhttp_find_header(query, "command")) && (strcmp(param, "playnow") == 0)) if ((param = evhttp_find_header(query, "command")) && (strcmp(param, "playnow") == 0))
id += status.pos_pl; pos += status.pos_pl;
ret = player_playback_start(&id); ret = player_playback_startpos(pos, &id);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
@ -859,6 +862,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
const char *shuffle; const char *shuffle;
uint32_t plid; uint32_t plid;
uint32_t id; uint32_t id;
uint32_t pos;
int ret; int ret;
/* /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&container-item-spec='dmap.containeritemid:0x9' /* /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&container-item-spec='dmap.containeritemid:0x9'
@ -934,7 +938,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
DPRINTF(E_DBG, L_DACP, "Playspec request for playlist %d, start song id %d%s\n", plid, id, (shuffle) ? ", shuffle" : ""); DPRINTF(E_DBG, L_DACP, "Playspec request for playlist %d, start song id %d%s\n", plid, id, (shuffle) ? ", shuffle" : "");
ps = player_queue_make_pl(plid, &id); ps = player_queue_make_pl(plid, &pos);
if (!ps) if (!ps)
{ {
DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid); DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid);
@ -942,7 +946,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
goto out_fail; goto out_fail;
} }
DPRINTF(E_DBG, L_DACP, "Playspec start song index is %d\n", id); DPRINTF(E_DBG, L_DACP, "Playspec start song index is %d\n", pos);
player_get_status(&status); player_get_status(&status);
@ -956,7 +960,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
if (shuffle) if (shuffle)
dacp_propset_shufflestate(shuffle, NULL); dacp_propset_shufflestate(shuffle, NULL);
ret = player_playback_start(&id); ret = player_playback_startpos(pos, &id);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
@ -1129,15 +1133,6 @@ dacp_reply_playresume(struct evhttp_request *req, struct evbuffer *evbuf, char *
evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf); evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
} }
static struct player_source *
next_ps(struct player_source *ps, char shuffle)
{
if (shuffle)
return ps->shuffle_next;
else
return ps->pl_next;
}
static int static int
playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int pos_in_queue, uint32_t plid) playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int pos_in_queue, uint32_t plid)
{ {
@ -1197,10 +1192,9 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
struct daap_session *s; struct daap_session *s;
struct evbuffer *songlist; struct evbuffer *songlist;
struct evbuffer *playlists; struct evbuffer *playlists;
struct player_source *ps;
struct player_source *head;
struct player_status status; struct player_status status;
struct player_history *history; struct player_history *history;
struct player_queue *queue;
const char *param; const char *param;
int span; int span;
int i; int i;
@ -1271,19 +1265,13 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
/* Get queue and make songlist only if playing or paused */ /* Get queue and make songlist only if playing or paused */
if (status.status != PLAY_STOPPED) if (status.status != PLAY_STOPPED)
{ {
/* Fast forward to song currently being played */ queue = player_queue_get(-1, abs(span), status.shuffle);
head = player_queue_get(); if (queue)
if (head)
{ {
ps = head; i = queue->start_pos;
while ((ps->id != status.id) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) for (n = 0; (n < queue->count) && (n < abs(span)); n++)
i++;
while ((n < abs(span)) && (ps = next_ps(ps, status.shuffle)) && (ps != head))
{ {
n++; ret = playqueuecontents_add_source(songlist, queue->queue[n], (n + i + 1), status.plid);
ret = playqueuecontents_add_source(songlist, ps->id, (n + i + 1), status.plid);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n");
@ -1293,6 +1281,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
} }
} }
} }
queue_free(queue);
} }
} }
@ -1513,7 +1502,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf,
} }
DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx); DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx);
ret = player_playback_start(&idx); ret = player_playback_startpos(idx, NULL);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); DPRINTF(E_LOG, L_DACP, "Could not start playback\n");

View File

@ -43,7 +43,7 @@ static int threshold;
static int console; static int console;
static char *logfilename; static char *logfilename;
static FILE *logfile; static FILE *logfile;
static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache" }; static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd" };
static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" }; static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" };

View File

@ -29,8 +29,9 @@
#define L_SPOTIFY 20 #define L_SPOTIFY 20
#define L_LASTFM 21 #define L_LASTFM 21
#define L_CACHE 22 #define L_CACHE 22
#define L_MPD 23
#define N_LOGDOMAINS 23 #define N_LOGDOMAINS 24
/* Severities */ /* Severities */
#define E_FATAL 0 #define E_FATAL 0

View File

@ -61,6 +61,7 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
#include "cache.h" #include "cache.h"
#include "filescanner.h" #include "filescanner.h"
#include "httpd.h" #include "httpd.h"
#include "mpd.h"
#include "mdns.h" #include "mdns.h"
#include "remote_pairing.h" #include "remote_pairing.h"
#include "player.h" #include "player.h"
@ -724,6 +725,18 @@ main(int argc, char **argv)
goto httpd_fail; goto httpd_fail;
} }
#ifdef MPD
/* Spawn MPD thread */
ret = mpd_init();
if (ret != 0)
{
DPRINTF(E_FATAL, L_MAIN, "MPD thread failed to start\n");
ret = EXIT_FAILURE;
goto mpd_fail;
}
#endif
/* Start Remote pairing service */ /* Start Remote pairing service */
ret = remote_pairing_init(); ret = remote_pairing_init();
if (ret != 0) if (ret != 0)
@ -808,6 +821,13 @@ main(int argc, char **argv)
httpd_deinit(); httpd_deinit();
httpd_fail: httpd_fail:
DPRINTF(E_LOG, L_MAIN, "TCPd deinit\n");
#ifdef MPD
DPRINTF(E_LOG, L_MAIN, "MPD deinit\n");
mpd_deinit();
#endif
mpd_fail:
DPRINTF(E_LOG, L_MAIN, "Player deinit\n"); DPRINTF(E_LOG, L_MAIN, "Player deinit\n");
player_deinit(); player_deinit();

2641
src/mpd.c Normal file

File diff suppressed because it is too large Load Diff

20
src/mpd.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __MPD_H__
#define __MPD_H__
#ifdef HAVE_LIBEVENT2
# include <event2/event.h>
# include <event2/buffer.h>
# include <event2/bufferevent.h>
# include <event2/listener.h>
#else
# include <event.h>
#endif
int
mpd_init(void);
void
mpd_deinit(void);
#endif /* !__MPD_H__ */

View File

@ -97,6 +97,28 @@ struct spk_enum
void *arg; void *arg;
}; };
enum range_type
{
RANGEARG_NONE,
RANGEARG_ID,
RANGEARG_POS,
RANGEARG_RANGE
};
struct item_range
{
enum range_type type;
uint32_t id;
int start_pos;
int end_pos;
int to_pos;
char shuffle;
uint32_t *id_ptr;
};
struct player_command struct player_command
{ {
pthread_mutex_t lck; pthread_mutex_t lck;
@ -121,11 +143,14 @@ struct player_command
uint32_t id; uint32_t id;
int intval; int intval;
int ps_pos[2]; int ps_pos[2];
struct item_range item_range;
} arg; } arg;
int ret; int ret;
int raop_pending; int raop_pending;
struct player_queue *queue;
}; };
/* Keep in sync with enum raop_devtype */ /* Keep in sync with enum raop_devtype */
@ -637,6 +662,7 @@ player_queue_make(struct query_params *qp, const char *sort)
struct player_source *q_tail; struct player_source *q_tail;
struct player_source *ps; struct player_source *ps;
uint32_t id; uint32_t id;
uint32_t song_length;
int ret; int ret;
qp->idx_type = I_NONE; qp->idx_type = I_NONE;
@ -673,6 +699,14 @@ player_queue_make(struct query_params *qp, const char *sort)
continue; continue;
} }
ret = safe_atou32(dbmfi.song_length, &song_length);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n");
continue;
}
ps = (struct player_source *)malloc(sizeof(struct player_source)); ps = (struct player_source *)malloc(sizeof(struct player_source));
if (!ps) if (!ps)
{ {
@ -685,6 +719,7 @@ player_queue_make(struct query_params *qp, const char *sort)
memset(ps, 0, sizeof(struct player_source)); memset(ps, 0, sizeof(struct player_source));
ps->id = id; ps->id = id;
ps->song_length = song_length;
if (!q_head) if (!q_head)
q_head = ps; q_head = ps;
@ -979,6 +1014,38 @@ player_queue_make_pl(int plid, uint32_t *id)
return ps; return ps;
} }
struct player_source *
player_queue_make_mpd(char *path, int recursive)
{
struct query_params qp;
struct player_source *ps;
int ret;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.idx_type = I_NONE;
qp.sort = S_ALBUM;
if (recursive)
{
ret = asprintf(&(qp.filter), "f.virtual_path LIKE '/%s%%'", path);
if (ret < 0)
DPRINTF(E_DBG, L_PLAYER, "Out of memory\n");
}
else
{
ret = asprintf(&(qp.filter), "f.virtual_path LIKE '/%s'", path);
if (ret < 0)
DPRINTF(E_DBG, L_PLAYER, "Out of memory\n");
}
ps = player_queue_make(&qp, NULL);
free(qp.filter);
return ps;
}
static void static void
source_free(struct player_source *ps) source_free(struct player_source *ps)
{ {
@ -1414,19 +1481,46 @@ source_prev(void)
return 0; return 0;
} }
/*
* Returns the position of the given song (ps) in the playqueue or shufflequeue.
* First song in the queue has position 0. Depending on the 'shuffle' argument,
* the position is either determined in the playqueue or shufflequeue.
*
* @param ps the song to search in the queue
* @param shuffle 0 search in the playqueue, 1 search in the shufflequeue
* @return position 0-based in the queue
*/
static int static int
source_position(struct player_source *ps) source_position(struct player_source *ps, char shuffle)
{ {
struct player_source *p; struct player_source *p;
int ret; int ret;
ret = 0; ret = 0;
for (p = source_head; p != ps; p = p->pl_next) for (p = (shuffle ? shuffle_head : source_head); p != ps; p = (shuffle ? p->shuffle_next : p->pl_next))
ret++; ret++;
return ret; return ret;
} }
static uint32_t
source_count()
{
struct player_source *ps;
uint32_t ret;
ret = 0;
if (source_head)
{
ret++;
for (ps = source_head->pl_next; ps != source_head; ps = ps->pl_next)
ret++;
}
return ret;
}
static uint64_t static uint64_t
source_check(void) source_check(void)
{ {
@ -2248,6 +2342,8 @@ get_status(struct player_command *cmd)
status = cmd->arg.status; status = cmd->arg.status;
memset(status, 0, sizeof(struct player_status));
status->shuffle = shuffle; status->shuffle = shuffle;
status->repeat = repeat; status->repeat = repeat;
@ -2258,27 +2354,29 @@ get_status(struct player_command *cmd)
switch (player_state) switch (player_state)
{ {
case PLAY_STOPPED: case PLAY_STOPPED:
DPRINTF(E_DBG, L_PLAYER, "Player status: stopped\n"); DPRINTF(E_SPAM, L_PLAYER, "Player status: stopped\n");
status->status = PLAY_STOPPED; status->status = PLAY_STOPPED;
break; break;
case PLAY_PAUSED: case PLAY_PAUSED:
DPRINTF(E_DBG, L_PLAYER, "Player status: paused\n"); DPRINTF(E_SPAM, L_PLAYER, "Player status: paused\n");
status->status = PLAY_PAUSED; status->status = PLAY_PAUSED;
status->id = cur_streaming->id; status->id = cur_streaming->id;
pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - cur_streaming->stream_start; pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - cur_streaming->stream_start;
status->pos_ms = (pos * 1000) / 44100; status->pos_ms = (pos * 1000) / 44100;
status->songlength_ms = cur_streaming->song_length;
status->pos_pl = source_position(cur_streaming, 0);
status->pos_pl = source_position(cur_streaming);
break; break;
case PLAY_PLAYING: case PLAY_PLAYING:
if (!cur_playing) if (!cur_playing)
{ {
DPRINTF(E_DBG, L_PLAYER, "Player status: playing (buffering)\n"); DPRINTF(E_SPAM, L_PLAYER, "Player status: playing (buffering)\n");
status->status = PLAY_PAUSED; status->status = PLAY_PAUSED;
ps = cur_streaming; ps = cur_streaming;
@ -2288,7 +2386,7 @@ get_status(struct player_command *cmd)
} }
else else
{ {
DPRINTF(E_DBG, L_PLAYER, "Player status: playing\n"); DPRINTF(E_SPAM, L_PLAYER, "Player status: playing\n");
status->status = PLAY_PLAYING; status->status = PLAY_PLAYING;
ps = cur_playing; ps = cur_playing;
@ -2308,9 +2406,16 @@ get_status(struct player_command *cmd)
} }
status->pos_ms = (pos * 1000) / 44100; status->pos_ms = (pos * 1000) / 44100;
status->songlength_ms = ps->song_length;
status->id = ps->id; status->id = ps->id;
status->pos_pl = source_position(ps); status->pos_pl = source_position(ps, 0);
ps = next_ps(ps, shuffle);
status->nextsong_id = ps->id;
status->nextsong_pos_pl = source_position(ps, 0);
status->playlistlength = source_count();
break; break;
} }
@ -2445,6 +2550,8 @@ playback_start(struct player_command *cmd)
{ {
struct raop_device *rd; struct raop_device *rd;
uint32_t *idx_id; uint32_t *idx_id;
uint32_t pos;
uint32_t id;
int ret; int ret;
if (!source_head) if (!source_head)
@ -2454,10 +2561,15 @@ playback_start(struct player_command *cmd)
return -1; return -1;
} }
idx_id = cmd->arg.id_ptr; idx_id = cmd->arg.item_range.id_ptr;
if (player_state == PLAY_PLAYING) if (player_state == PLAY_PLAYING)
{ {
/*
* If player is already playing a song, only return current playing song id
* and do not change player state (ignores given arguments for playing a
* specified song by pos or id).
*/
if (idx_id) if (idx_id)
{ {
if (cur_playing) if (cur_playing)
@ -2471,10 +2583,15 @@ playback_start(struct player_command *cmd)
return 0; return 0;
} }
// Update global playback position
pb_pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - 88200; pb_pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - 88200;
if (idx_id) if (cmd->arg.item_range.type == RANGEARG_POS
|| cmd->arg.item_range.type == RANGEARG_ID)
{ {
/*
* A song is specified in the arguments (by id or pos)
*/
if (cur_playing) if (cur_playing)
source_stop(cur_playing); source_stop(cur_playing);
else if (cur_streaming) else if (cur_streaming)
@ -2491,26 +2608,50 @@ playback_start(struct player_command *cmd)
else else
cur_streaming = source_head; cur_streaming = source_head;
if (*idx_id > 0) if (cmd->arg.item_range.type == RANGEARG_POS)
{
/*
* Find start song by position in playqueue
*/
pos = cmd->arg.item_range.start_pos;
if (pos > 0)
{ {
cur_streaming = source_head; cur_streaming = source_head;
for (; *idx_id > 0; (*idx_id)--) for (; pos > 0; pos--)
cur_streaming = cur_streaming->pl_next; cur_streaming = cur_streaming->pl_next;
}
}
else
{
/*
* Find start song by id
*/
id = cmd->arg.item_range.id;
if (id > 0)
{
cur_streaming = source_head->pl_next;
while (cur_streaming->id != id && cur_streaming != source_head)
{
cur_streaming = cur_streaming->pl_next;
}
}
}
if (shuffle) if (shuffle)
shuffle_head = cur_streaming; shuffle_head = cur_streaming;
}
ret = source_open(cur_streaming, 0); ret = source_open(cur_streaming, 0);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Couldn't jump to queue position %d\n", *idx_id); DPRINTF(E_LOG, L_PLAYER, "Couldn't jump to queue position %d\n", pos);
playback_abort(); playback_abort();
return -1; return -1;
} }
if (idx_id)
*idx_id = cur_streaming->id; *idx_id = cur_streaming->id;
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES; cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
cur_streaming->output_start = cur_streaming->stream_start; cur_streaming->output_start = cur_streaming->stream_start;
} }
@ -3362,6 +3503,101 @@ shuffle_set(struct player_command *cmd)
return 0; return 0;
} }
static unsigned int
queue_count()
{
struct player_source *ps;
int count;
if (!source_head)
return 0;
count = 1;
ps = source_head->pl_next;
while (ps != source_head)
{
count++;
ps = ps->pl_next;
}
return count;
}
static int
queue_get(struct player_command *cmd)
{
int start_pos;
int end_pos;
struct player_queue *queue;
uint32_t *ids;
unsigned int qlength;
unsigned int count;
struct player_source *ps;
int i;
int pos;
char qshuffle;
queue = malloc(sizeof(struct player_queue));
qlength = queue_count();
qshuffle = cmd->arg.item_range.shuffle;
start_pos = cmd->arg.item_range.start_pos;
if (start_pos < 0)
{
ps = cur_playing ? cur_playing : cur_streaming;
start_pos = ps ? source_position(ps, qshuffle) + 1 : 0;
}
end_pos = cmd->arg.item_range.end_pos;
if (cmd->arg.item_range.start_pos < 0)
end_pos += start_pos;
if (end_pos <= 0 || end_pos > qlength)
end_pos = qlength;
if (end_pos > start_pos)
count = end_pos - start_pos;
else
count = 0;
ids = malloc(count * sizeof(uint32_t));
pos = 0;
ps = qshuffle ? shuffle_head : source_head;
for (i = 0; i < end_pos; i++)
{
if (i >= start_pos)
{
ids[pos] = ps->id;
pos++;
}
ps = qshuffle ? ps->shuffle_next : ps->pl_next;
}
queue->start_pos = start_pos;
queue->count = count;
queue->queue = ids;
queue->length = qlength;
queue->playingid = 0;
if (cur_playing)
queue->playingid = cur_playing->id;
else if (cur_streaming)
queue->playingid = cur_streaming->id;
cmd->queue = queue;
return 0;
}
void
queue_free(struct player_queue *queue)
{
free(queue->queue);
free(queue);
}
static int static int
queue_add(struct player_command *cmd) queue_add(struct player_command *cmd)
{ {
@ -3522,19 +3758,10 @@ static int
queue_remove(struct player_command *cmd) queue_remove(struct player_command *cmd)
{ {
struct player_source *ps; struct player_source *ps;
int pos; uint32_t pos;
uint32_t id;
int i; int i;
pos = cmd->arg.ps_pos[0];
DPRINTF(E_DBG, L_PLAYER, "Removing song from position %d\n", pos);
if (pos < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove song, invalid position %d\n", pos);
return -1;
}
ps = cur_playing ? cur_playing : cur_streaming; ps = cur_playing ? cur_playing : cur_streaming;
if (!ps) if (!ps)
{ {
@ -3542,10 +3769,44 @@ queue_remove(struct player_command *cmd)
return -1; return -1;
} }
if (cmd->arg.item_range.type == RANGEARG_ID)
{
id = cmd->arg.item_range.id;
DPRINTF(E_DBG, L_PLAYER, "Removing song with id %d\n", id);
if (id < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove song, invalid id %d\n", id);
return -1;
}
else if (id == ps->id)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove current playing song, id %d\n", id);
return -1;
}
ps = source_head->pl_next;
while (ps->id != id && ps != source_head)
{
ps = ps->pl_next;
}
}
else
{
pos = cmd->arg.item_range.start_pos;
DPRINTF(E_DBG, L_PLAYER, "Removing song from position %d\n", pos);
if (pos < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove song, invalid position %d\n", pos);
return -1;
}
for (i = 0; i < pos; i++) for (i = 0; i < pos; i++)
{ {
ps = shuffle ? ps->shuffle_next : ps->pl_next; ps = shuffle ? ps->shuffle_next : ps->pl_next;
} }
}
ps->shuffle_prev->shuffle_next = ps->shuffle_next; ps->shuffle_prev->shuffle_next = ps->shuffle_next;
ps->shuffle_next->shuffle_prev = ps->shuffle_prev; ps->shuffle_next->shuffle_prev = ps->shuffle_prev;
@ -3807,7 +4068,7 @@ player_now_playing(uint32_t *id)
} }
int int
player_playback_start(uint32_t *idx_id) player_playback_start(uint32_t *itemid)
{ {
struct player_command cmd; struct player_command cmd;
int ret; int ret;
@ -3816,7 +4077,8 @@ player_playback_start(uint32_t *idx_id)
cmd.func = playback_start; cmd.func = playback_start;
cmd.func_bh = playback_start_bh; cmd.func_bh = playback_start_bh;
cmd.arg.id_ptr = idx_id; cmd.arg.item_range.type = RANGEARG_NONE;
cmd.arg.item_range.id_ptr = itemid;
ret = sync_command(&cmd); ret = sync_command(&cmd);
@ -3825,6 +4087,46 @@ player_playback_start(uint32_t *idx_id)
return ret; return ret;
} }
int
player_playback_startpos(int pos, uint32_t *itemid)
{
struct player_command cmd;
int ret;
command_init(&cmd);
cmd.func = playback_start;
cmd.func_bh = playback_start_bh;
cmd.arg.item_range.type = RANGEARG_POS;
cmd.arg.item_range.start_pos = pos;
cmd.arg.item_range.id_ptr = itemid;
ret = sync_command(&cmd);
command_deinit(&cmd);
return ret;
}
int
player_playback_startid(uint32_t id, uint32_t *itemid)
{
struct player_command cmd;
int ret;
command_init(&cmd);
cmd.func = playback_start;
cmd.func_bh = playback_start_bh;
cmd.arg.item_range.type = RANGEARG_ID;
cmd.arg.item_range.id = id;
cmd.arg.item_range.id_ptr = itemid;
ret = sync_command(&cmd);
command_deinit(&cmd);
return ret;
}
int int
player_playback_stop(void) player_playback_stop(void)
{ {
@ -4055,13 +4357,38 @@ player_shuffle_set(int enable)
return ret; return ret;
} }
struct player_queue *
player_queue_get(int start_pos, int end_pos, char shuffle)
{
struct player_command cmd;
int ret;
command_init(&cmd);
cmd.func = queue_get;
cmd.func_bh = NULL;
cmd.arg.item_range.start_pos = start_pos;
cmd.arg.item_range.end_pos = end_pos;
cmd.arg.item_range.shuffle = shuffle;
cmd.queue = NULL;
ret = sync_command(&cmd);
command_deinit(&cmd);
if (ret != 0)
return NULL;
return cmd.queue;
}
struct player_source * struct player_source *
player_queue_get(void) next_ps(struct player_source *ps, char shuffle)
{ {
if (shuffle) if (shuffle)
return shuffle_head; return ps->shuffle_next;
else else
return source_head; return ps->pl_next;
} }
int int
@ -4122,7 +4449,8 @@ player_queue_move(int ps_pos_from, int ps_pos_to)
return ret; return ret;
} }
int player_queue_remove(int ps_pos_remove) int
player_queue_remove(int ps_pos_remove)
{ {
struct player_command cmd; struct player_command cmd;
int ret; int ret;
@ -4131,7 +4459,28 @@ int player_queue_remove(int ps_pos_remove)
cmd.func = queue_remove; cmd.func = queue_remove;
cmd.func_bh = NULL; cmd.func_bh = NULL;
cmd.arg.ps_pos[0] = ps_pos_remove; cmd.arg.item_range.type = RANGEARG_POS;
cmd.arg.item_range.start_pos = ps_pos_remove;
ret = sync_command(&cmd);
command_deinit(&cmd);
return ret;
}
int
player_queue_removeid(uint32_t id)
{
struct player_command cmd;
int ret;
command_init(&cmd);
cmd.func = queue_remove;
cmd.func_bh = NULL;
cmd.arg.item_range.type = RANGEARG_ID;
cmd.arg.item_range.id = id;
ret = sync_command(&cmd); ret = sync_command(&cmd);

View File

@ -51,10 +51,22 @@ struct player_status {
int volume; int volume;
/* Playlist id */
uint32_t plid; uint32_t plid;
/* Playlist length */
uint32_t playlistlength;
/* Playing song id*/
uint32_t id; uint32_t id;
/* Elapsed time in ms of playing song */
uint32_t pos_ms; uint32_t pos_ms;
/* Song length in ms of playing song */
uint32_t songlength_ms;
/* Playlist position of playing song*/
int pos_pl; int pos_pl;
/* Song id of next song in playlist */
uint32_t nextsong_id;
/* Playlist position of next song */
int nextsong_pos_pl;
}; };
typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg); typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg);
@ -63,6 +75,7 @@ typedef void (*player_status_handler)(void);
struct player_source struct player_source
{ {
uint32_t id; uint32_t id;
uint32_t song_length;
enum source_type type; enum source_type type;
int setup_done; int setup_done;
@ -82,6 +95,16 @@ struct player_source
struct player_source *play_next; struct player_source *play_next;
}; };
struct player_queue
{
uint32_t playingid;
unsigned int length;
unsigned int start_pos;
unsigned int count;
uint32_t *queue;
};
struct player_history struct player_history
{ {
/* Buffer index of the oldest remembered song */ /* Buffer index of the oldest remembered song */
@ -113,6 +136,12 @@ player_speaker_set(uint64_t *ids);
int int
player_playback_start(uint32_t *idx_id); player_playback_start(uint32_t *idx_id);
int
player_playback_startpos(int pos, uint32_t *itemid);
int
player_playback_startid(uint32_t id, uint32_t *itemid);
int int
player_playback_stop(void); player_playback_stop(void);
@ -151,7 +180,16 @@ struct player_source *
player_queue_make_pl(int plid, uint32_t *id); player_queue_make_pl(int plid, uint32_t *id);
struct player_source * struct player_source *
player_queue_get(void); player_queue_make_mpd(char *path, int recursive);
struct player_queue *
player_queue_get(int start_pos, int end_pos, char shuffle);
void
queue_free(struct player_queue *queue);
struct player_source *
next_ps(struct player_source *ps, char shuffle);
int int
player_queue_add(struct player_source *ps); player_queue_add(struct player_source *ps);
@ -165,6 +203,9 @@ player_queue_move(int ps_pos_from, int ps_pos_to);
int int
player_queue_remove(int ps_pos_remove); player_queue_remove(int ps_pos_remove);
int
player_queue_removeid(uint32_t id);
void void
player_queue_clear(void); player_queue_clear(void);

View File

@ -561,6 +561,7 @@ spotify_playlist_save(sp_playlist *pl)
char title[512]; char title[512];
int plid; int plid;
int num_tracks; int num_tracks;
char virtual_path[PATH_MAX];
int ret; int ret;
int i; int i;
@ -594,6 +595,8 @@ spotify_playlist_save(sp_playlist *pl)
pli = db_pl_fetch_bypath(url); pli = db_pl_fetch_bypath(url);
snprintf(title, sizeof(title), "[s] %s", name); snprintf(title, sizeof(title), "[s] %s", name);
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", title);
if (pli) if (pli)
{ {
DPRINTF(E_DBG, L_SPOTIFY, "Playlist found ('%s', link %s), updating\n", name, url); DPRINTF(E_DBG, L_SPOTIFY, "Playlist found ('%s', link %s), updating\n", name, url);
@ -602,7 +605,7 @@ spotify_playlist_save(sp_playlist *pl)
free_pli(pli, 0); free_pli(pli, 0);
ret = db_pl_update(title, url, plid); ret = db_pl_update(title, url, virtual_path, plid);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Error updating playlist ('%s', link %s)\n", name, url); DPRINTF(E_LOG, L_SPOTIFY, "Error updating playlist ('%s', link %s)\n", name, url);
@ -616,7 +619,7 @@ spotify_playlist_save(sp_playlist *pl)
{ {
DPRINTF(E_DBG, L_SPOTIFY, "Adding playlist ('%s', link %s)\n", name, url); DPRINTF(E_DBG, L_SPOTIFY, "Adding playlist ('%s', link %s)\n", name, url);
ret = db_pl_add(title, url, &plid); ret = db_pl_add(title, url, virtual_path, &plid);
if ((ret < 0) || (plid < 1)) if ((ret < 0) || (plid < 1))
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist ('%s', link %s, ret %d, plid %d)\n", name, url, ret, plid); DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist ('%s', link %s, ret %d, plid %d)\n", name, url, ret, plid);