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;
)
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_MUSEPACK, test x$use_musepack = 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_OSS4, 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.
gl_LIBUNISTRING

View File

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

View File

@ -127,6 +127,13 @@ static cfg_opt_t sec_sqlite[] =
CFG_END()
};
/* MPD section structure */
static cfg_opt_t sec_mpd[] =
{
CFG_INT("port", 6600, CFGF_NONE),
CFG_END()
};
/* Config file structure */
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("spotify", sec_spotify, CFGF_NONE),
CFG_SEC("sqlite", sec_sqlite, CFGF_NONE),
CFG_SEC("mpd", sec_mpd, CFGF_NONE),
CFG_END()
};

688
src/db.c
View File

@ -33,6 +33,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <limits.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(composer_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
@ -151,6 +153,7 @@ static const struct col_type_map pli_cols_map[] =
{ pli_offsetof(path), DB_TYPE_STRING },
{ pli_offsetof(index), DB_TYPE_INT },
{ pli_offsetof(special_id), DB_TYPE_INT },
{ pli_offsetof(virtual_path), DB_TYPE_STRING },
/* items is computed on the fly */
};
@ -218,6 +221,7 @@ static const ssize_t dbmfi_cols_map[] =
dbmfi_offsetof(album_sort),
dbmfi_offsetof(composer_sort),
dbmfi_offsetof(album_artist_sort),
dbmfi_offsetof(virtual_path),
};
/* This list must be kept in sync with
@ -235,6 +239,7 @@ static const ssize_t dbpli_cols_map[] =
dbpli_offsetof(path),
dbpli_offsetof(index),
dbpli_offsetof(special_id),
dbmfi_offsetof(virtual_path),
/* items is computed on the fly */
};
@ -316,6 +321,22 @@ db_escape_string(const char *str)
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
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)
free(mfi->album_artist_sort);
if (mfi->virtual_path)
free(mfi->virtual_path);
if (!content_only)
free(mfi);
else
@ -742,9 +766,13 @@ db_purge_cruft(time_t ref)
char *errmsg;
int i;
int ret;
char *queries[3] = { NULL, NULL, NULL };
char *queries_tmpl[3] =
int changes;
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 playlists WHERE type <> 1 AND 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));
}
// 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:
for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
{
@ -792,13 +843,14 @@ db_purge_cruft(time_t ref)
void
db_purge_all(void)
{
char *queries[5] =
char *queries[6] =
{
"DELETE FROM inotify;",
"DELETE FROM playlistitems;",
"DELETE FROM playlists WHERE type <> 1;",
"DELETE FROM files;",
"DELETE FROM groups;",
"DELETE FROM filelist;"
};
char *errmsg;
int i;
@ -1852,6 +1904,222 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst
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 */
int
@ -2359,6 +2627,30 @@ db_file_fetch_byid(int id)
#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
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," \
" media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \
" 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)," \
" 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)," \
" %d, TRIM(%Q), TRIM(%Q), TRIM(%Q), %d, %d," \
" 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 *errmsg;
@ -2408,10 +2700,10 @@ db_file_add(struct media_file_info *mfi)
(int64_t)mfi->time_played, (int64_t)mfi->db_timestamp, mfi->disabled, mfi->sample_count,
mfi->codectype, mfi->index, mfi->has_video,
mfi->contentrating, mfi->bits_per_sample, mfi->album_artist,
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->media_kind, mfi->tv_series_name, mfi->tv_episode_num_str,
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->composer_sort, mfi->album_artist_sort);
mfi->composer_sort, mfi->album_artist_sort, mfi->virtual_path);
if (!query)
{
@ -2433,6 +2725,8 @@ db_file_add(struct media_file_info *mfi)
sqlite3_free(query);
db_filelist_add(mfi->virtual_path, F_FILE);
cache_daap_trigger();
return 0;
@ -2444,19 +2738,22 @@ int
db_file_update(struct media_file_info *mfi)
{
#define Q_TMPL "UPDATE files SET path = '%q', fname = '%q', title = TRIM(%Q), artist = TRIM(%Q), album = TRIM(%Q), genre = TRIM(%Q)," \
" comment = TRIM(%Q), type = %Q, composer = TRIM(%Q), orchestra = TRIM(%Q), conductor = TRIM(%Q), grouping = TRIM(%Q)," \
" url = %Q, bitrate = %d, samplerate = %d, song_length = %d, file_size = %" PRIi64 "," \
" year = %d, track = %d, total_tracks = %d, disc = %d, total_discs = %d, bpm = %d," \
" compilation = %d, artwork = %d, rating = %d, seek = %d, data_kind = %d, item_kind = %d," \
" description = %Q, time_modified = %" PRIi64 "," \
" db_timestamp = %" PRIi64 ", disabled = %" PRIi64 ", sample_count = %" PRIi64 "," \
" codectype = %Q, idx = %d, has_video = %d," \
" bits_per_sample = %d, album_artist = 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," \
" 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)" \
" WHERE id = %d;"
" comment = TRIM(%Q), type = %Q, composer = TRIM(%Q), orchestra = TRIM(%Q), conductor = TRIM(%Q), grouping = TRIM(%Q)," \
" url = %Q, bitrate = %d, samplerate = %d, song_length = %d, file_size = %" PRIi64 "," \
" year = %d, track = %d, total_tracks = %d, disc = %d, total_discs = %d, bpm = %d," \
" compilation = %d, artwork = %d, rating = %d, seek = %d, data_kind = %d, item_kind = %d," \
" description = %Q, time_modified = %" PRIi64 "," \
" db_timestamp = %" PRIi64 ", disabled = %" PRIi64 ", sample_count = %" PRIi64 "," \
" codectype = %Q, idx = %d, has_video = %d," \
" bits_per_sample = %d, album_artist = 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," \
" 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)," \
" virtual_path = TRIM(%Q)" \
" WHERE id = %d;"
// struct media_file_info *oldmfi;
char *query;
char *errmsg;
int ret;
@ -2467,6 +2764,18 @@ db_file_update(struct media_file_info *mfi)
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);
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->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,
mfi->id);
if (!query)
@ -2509,6 +2818,8 @@ db_file_update(struct media_file_info *mfi)
sqlite3_free(query);
db_filelist_add(mfi->virtual_path, F_FILE);
cache_daap_trigger();
return 0;
@ -2865,6 +3176,30 @@ db_pl_fetch_bypath(char *path)
#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 *
db_pl_fetch_byid(int id)
{
@ -2914,11 +3249,11 @@ db_pl_fetch_bytitlepath(char *title, char *path)
}
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 QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id)" \
" VALUES ('%q', 0, NULL, %" PRIi64 ", 0, '%q', 0, 0);"
#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, '%q');"
char *query;
char *errmsg;
int ret;
@ -2942,7 +3277,7 @@ db_pl_add(char *title, char *path, int *id)
}
/* 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)
{
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);
db_filelist_add(virtual_path, F_PLAYLIST);
return 0;
#undef QDUP_TMPL
@ -3003,14 +3340,22 @@ db_pl_add_item_byid(int plid, int fileid)
}
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;
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
}
@ -3108,6 +3453,7 @@ db_pl_enable_bycookie(uint32_t cookie, char *path)
}
/* Groups */
int
db_groups_clear(void)
@ -3314,11 +3660,12 @@ db_pairing_fetch_byguid(struct pairing_info *pi)
void
db_spotify_purge(void)
{
char *queries[3] =
char *queries[4] =
{
"DELETE FROM files WHERE path LIKE 'spotify:%%';",
"DELETE FROM playlistitems WHERE filepath LIKE 'spotify:%%';",
"DELETE FROM playlists WHERE path LIKE 'spotify:%%';",
"DELETE FROM filelist WHERE path LIKE '/spotify:%%';",
};
int i;
int ret;
@ -3336,11 +3683,13 @@ db_spotify_purge(void)
void
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 playlistitems WHERE playlistid = %d;",
"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;
int i;
@ -4275,7 +4624,8 @@ db_perthread_deinit(void)
" artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album_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 \
@ -4288,7 +4638,8 @@ db_perthread_deinit(void)
" disabled INTEGER DEFAULT 0," \
" path VARCHAR(4096)," \
" idx INTEGER NOT NULL," \
" special_id INTEGER DEFAULT 0" \
" special_id INTEGER DEFAULT 0," \
" virtual_path VARCHAR(4096)" \
");"
#define T_PLITEMS \
@ -4328,6 +4679,15 @@ db_perthread_deinit(void)
" 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 \
"CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
" BEGIN" \
@ -4372,15 +4732,15 @@ db_perthread_deinit(void)
" VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);"
*/
#define SCHEMA_VERSION_MAJOR 15
#define SCHEMA_VERSION_MINOR 01
#define SCHEMA_VERSION_MAJOR 16
#define SCHEMA_VERSION_MINOR 00
// Q_SCVER should be deprecated/removed at v16
#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 \
"INSERT INTO admin (key, value) VALUES ('schema_version_major', '15');"
"INSERT INTO admin (key, value) VALUES ('schema_version_major', '16');"
#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 {
char *query;
@ -4398,6 +4758,8 @@ static const struct db_init_query db_init_table_queries[] =
{ T_SPEAKERS, "create table speakers" },
{ T_INOTIFY, "create table inotify" },
{ T_FILELIST, "create table filelist" },
{ TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_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 \
"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[] =
{
{ 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_PAIRING, "create pairing guid index" },
{ I_FILELIST, "create filelist index" },
};
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" },
};
/* 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
db_check_version(void)
{
@ -5539,6 +6140,15 @@ db_check_version(void)
case 1500:
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)
return -1;

View File

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

View File

@ -77,6 +77,22 @@
#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_RESCAN (1 << 1)
#define F_SCAN_FAST (1 << 2)
@ -108,6 +124,7 @@ struct stacked_dir {
};
static int cmd_pipe[2];
#ifdef USE_EVENTFD
static int exit_efd;
#else
@ -118,6 +135,7 @@ static int inofd;
static struct event_base *evbase_scan;
static struct event inoev;
static struct event exitev;
static struct event cmdev;
static pthread_t tid_scan;
static struct deferred_pl *playlists;
static struct stacked_dir *dirstack;
@ -132,6 +150,46 @@ static int
inofd_event_set(void);
static 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
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;
time_t stamp;
int id;
char virtual_path[PATH_MAX];
int ret;
filename = strrchr(path, '/');
@ -678,6 +737,22 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
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)
db_file_add(mfi);
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);
inofd_event_unset(); // Clears all inotify watches
db_watch_clear();
inofd_event_set();
bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN);
filescanner_initscan(NULL);
break;
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);
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);
filescanner_fullrescan(NULL);
break;
default:
@ -1210,7 +1275,6 @@ filescanner(void *arg)
DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n");
db_perthread_deinit();
//artworkcache_perthread_deinit();
pthread_exit(NULL);
}
@ -1872,6 +1936,111 @@ exit_cb(int fd, short event, void *arg)
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 */
int
filescanner_init(void)
@ -1924,6 +2093,21 @@ filescanner_init(void)
event_base_set(evbase_scan, &exitev);
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);
if (ret != 0)
{
@ -1935,6 +2119,9 @@ filescanner_init(void)
return 0;
thread_fail:
cmd_fail:
close(cmd_pipe[0]);
close(cmd_pipe[1]);
close(inofd);
ino_fail:
#ifdef USE_EVENTFD
@ -1993,5 +2180,7 @@ filescanner_deinit(void)
close(exit_pipe[0]);
close(exit_pipe[1]);
#endif
close(cmd_pipe[0]);
close(cmd_pipe[1]);
event_base_free(evbase_scan);
}

View File

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

View File

@ -680,6 +680,7 @@ process_pls(plist_t playlists, char *file)
int pl_id;
uint32_t alen;
uint32_t i;
char virtual_path[PATH_MAX];
int ret;
alen = plist_array_get_size(playlists);
@ -735,7 +736,8 @@ process_pls(plist_t playlists, char *file)
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)
{
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 mfi_id;
int ret;
char virtual_path[PATH_MAX];
int i;
DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file);
@ -158,7 +159,9 @@ scan_playlist(char *file, time_t mtime)
if (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)
{
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 *param;
uint32_t id;
uint32_t pos;
int clear;
int ret;
@ -773,19 +774,21 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
dacp_propset_shufflestate(param, NULL);
id = 0;
pos = 0;
param = evhttp_find_header(query, "index");
if (param)
{
ret = safe_atou32(param, &id);
ret = safe_atou32(param, &pos);
if (ret < 0)
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 */
//TODO playnow for mode = 1 (index is relativ to the history queue
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)
{
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;
uint32_t plid;
uint32_t id;
uint32_t pos;
int ret;
/* /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" : "");
ps = player_queue_make_pl(plid, &id);
ps = player_queue_make_pl(plid, &pos);
if (!ps)
{
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;
}
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);
@ -956,7 +960,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
if (shuffle)
dacp_propset_shufflestate(shuffle, NULL);
ret = player_playback_start(&id);
ret = player_playback_startpos(pos, &id);
if (ret < 0)
{
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);
}
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
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 evbuffer *songlist;
struct evbuffer *playlists;
struct player_source *ps;
struct player_source *head;
struct player_status status;
struct player_history *history;
struct player_queue *queue;
const char *param;
int span;
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 */
if (status.status != PLAY_STOPPED)
{
/* Fast forward to song currently being played */
head = player_queue_get();
if (head)
queue = player_queue_get(-1, abs(span), status.shuffle);
if (queue)
{
ps = head;
while ((ps->id != status.id) && (ps = next_ps(ps, status.shuffle)) && (ps != head))
i++;
while ((n < abs(span)) && (ps = next_ps(ps, status.shuffle)) && (ps != head))
i = queue->start_pos;
for (n = 0; (n < queue->count) && (n < abs(span)); n++)
{
n++;
ret = playqueuecontents_add_source(songlist, ps->id, (n + i + 1), status.plid);
ret = playqueuecontents_add_source(songlist, queue->queue[n], (n + i + 1), status.plid);
if (ret < 0)
{
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);
ret = player_playback_start(&idx);
ret = player_playback_startpos(idx, NULL);
if (ret < 0)
{
DPRINTF(E_LOG, L_DACP, "Could not start playback\n");

View File

@ -43,7 +43,7 @@ static int threshold;
static int console;
static char *logfilename;
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" };

View File

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

View File

@ -61,6 +61,7 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
#include "cache.h"
#include "filescanner.h"
#include "httpd.h"
#include "mpd.h"
#include "mdns.h"
#include "remote_pairing.h"
#include "player.h"
@ -724,6 +725,18 @@ main(int argc, char **argv)
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 */
ret = remote_pairing_init();
if (ret != 0)
@ -808,6 +821,13 @@ main(int argc, char **argv)
httpd_deinit();
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");
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;
};
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
{
pthread_mutex_t lck;
@ -121,11 +143,14 @@ struct player_command
uint32_t id;
int intval;
int ps_pos[2];
struct item_range item_range;
} arg;
int ret;
int raop_pending;
struct player_queue *queue;
};
/* 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 *ps;
uint32_t id;
uint32_t song_length;
int ret;
qp->idx_type = I_NONE;
@ -673,6 +699,14 @@ player_queue_make(struct query_params *qp, const char *sort)
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));
if (!ps)
{
@ -685,6 +719,7 @@ player_queue_make(struct query_params *qp, const char *sort)
memset(ps, 0, sizeof(struct player_source));
ps->id = id;
ps->song_length = song_length;
if (!q_head)
q_head = ps;
@ -979,6 +1014,38 @@ player_queue_make_pl(int plid, uint32_t *id)
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
source_free(struct player_source *ps)
{
@ -1414,19 +1481,46 @@ source_prev(void)
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
source_position(struct player_source *ps)
source_position(struct player_source *ps, char shuffle)
{
struct player_source *p;
int ret;
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++;
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
source_check(void)
{
@ -2248,6 +2342,8 @@ get_status(struct player_command *cmd)
status = cmd->arg.status;
memset(status, 0, sizeof(struct player_status));
status->shuffle = shuffle;
status->repeat = repeat;
@ -2258,27 +2354,29 @@ get_status(struct player_command *cmd)
switch (player_state)
{
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;
break;
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->id = cur_streaming->id;
pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - cur_streaming->stream_start;
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;
case PLAY_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;
ps = cur_streaming;
@ -2288,7 +2386,7 @@ get_status(struct player_command *cmd)
}
else
{
DPRINTF(E_DBG, L_PLAYER, "Player status: playing\n");
DPRINTF(E_SPAM, L_PLAYER, "Player status: playing\n");
status->status = PLAY_PLAYING;
ps = cur_playing;
@ -2308,9 +2406,16 @@ get_status(struct player_command *cmd)
}
status->pos_ms = (pos * 1000) / 44100;
status->songlength_ms = ps->song_length;
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;
}
@ -2445,6 +2550,8 @@ playback_start(struct player_command *cmd)
{
struct raop_device *rd;
uint32_t *idx_id;
uint32_t pos;
uint32_t id;
int ret;
if (!source_head)
@ -2454,10 +2561,15 @@ playback_start(struct player_command *cmd)
return -1;
}
idx_id = cmd->arg.id_ptr;
idx_id = cmd->arg.item_range.id_ptr;
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 (cur_playing)
@ -2471,10 +2583,15 @@ playback_start(struct player_command *cmd)
return 0;
}
// Update global playback position
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)
source_stop(cur_playing);
else if (cur_streaming)
@ -2491,26 +2608,50 @@ playback_start(struct player_command *cmd)
else
cur_streaming = source_head;
if (*idx_id > 0)
if (cmd->arg.item_range.type == RANGEARG_POS)
{
cur_streaming = source_head;
for (; *idx_id > 0; (*idx_id)--)
cur_streaming = cur_streaming->pl_next;
if (shuffle)
shuffle_head = cur_streaming;
/*
* Find start song by position in playqueue
*/
pos = cmd->arg.item_range.start_pos;
if (pos > 0)
{
cur_streaming = source_head;
for (; pos > 0; pos--)
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)
shuffle_head = cur_streaming;
ret = source_open(cur_streaming, 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();
return -1;
}
*idx_id = cur_streaming->id;
if (idx_id)
*idx_id = cur_streaming->id;
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
cur_streaming->output_start = cur_streaming->stream_start;
}
@ -3362,6 +3503,101 @@ shuffle_set(struct player_command *cmd)
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
queue_add(struct player_command *cmd)
{
@ -3522,19 +3758,10 @@ static int
queue_remove(struct player_command *cmd)
{
struct player_source *ps;
int pos;
uint32_t pos;
uint32_t id;
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;
if (!ps)
{
@ -3542,10 +3769,44 @@ queue_remove(struct player_command *cmd)
return -1;
}
for (i = 0; i < pos; i++)
{
ps = shuffle ? ps->shuffle_next : ps->pl_next;
}
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++)
{
ps = shuffle ? ps->shuffle_next : ps->pl_next;
}
}
ps->shuffle_prev->shuffle_next = ps->shuffle_next;
ps->shuffle_next->shuffle_prev = ps->shuffle_prev;
@ -3807,7 +4068,7 @@ player_now_playing(uint32_t *id)
}
int
player_playback_start(uint32_t *idx_id)
player_playback_start(uint32_t *itemid)
{
struct player_command cmd;
int ret;
@ -3816,7 +4077,8 @@ player_playback_start(uint32_t *idx_id)
cmd.func = playback_start;
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);
@ -3825,6 +4087,46 @@ player_playback_start(uint32_t *idx_id)
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
player_playback_stop(void)
{
@ -4055,13 +4357,38 @@ player_shuffle_set(int enable)
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 *
player_queue_get(void)
next_ps(struct player_source *ps, char shuffle)
{
if (shuffle)
return shuffle_head;
return ps->shuffle_next;
else
return source_head;
return ps->pl_next;
}
int
@ -4122,7 +4449,8 @@ player_queue_move(int ps_pos_from, int ps_pos_to)
return ret;
}
int player_queue_remove(int ps_pos_remove)
int
player_queue_remove(int ps_pos_remove)
{
struct player_command cmd;
int ret;
@ -4131,7 +4459,28 @@ int player_queue_remove(int ps_pos_remove)
cmd.func = queue_remove;
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);

View File

@ -51,10 +51,22 @@ struct player_status {
int volume;
/* Playlist id */
uint32_t plid;
/* Playlist length */
uint32_t playlistlength;
/* Playing song id*/
uint32_t id;
/* Elapsed time in ms of playing song */
uint32_t pos_ms;
/* Song length in ms of playing song */
uint32_t songlength_ms;
/* Playlist position of playing song*/
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);
@ -63,6 +75,7 @@ typedef void (*player_status_handler)(void);
struct player_source
{
uint32_t id;
uint32_t song_length;
enum source_type type;
int setup_done;
@ -82,6 +95,16 @@ struct player_source
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
{
/* Buffer index of the oldest remembered song */
@ -113,6 +136,12 @@ player_speaker_set(uint64_t *ids);
int
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
player_playback_stop(void);
@ -151,7 +180,16 @@ struct player_source *
player_queue_make_pl(int plid, uint32_t *id);
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
player_queue_add(struct player_source *ps);
@ -165,6 +203,9 @@ player_queue_move(int ps_pos_from, int ps_pos_to);
int
player_queue_remove(int ps_pos_remove);
int
player_queue_removeid(uint32_t id);
void
player_queue_clear(void);

View File

@ -561,6 +561,7 @@ spotify_playlist_save(sp_playlist *pl)
char title[512];
int plid;
int num_tracks;
char virtual_path[PATH_MAX];
int ret;
int i;
@ -594,6 +595,8 @@ spotify_playlist_save(sp_playlist *pl)
pli = db_pl_fetch_bypath(url);
snprintf(title, sizeof(title), "[s] %s", name);
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", title);
if (pli)
{
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);
ret = db_pl_update(title, url, plid);
ret = db_pl_update(title, url, virtual_path, plid);
if (ret < 0)
{
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);
ret = db_pl_add(title, url, &plid);
ret = db_pl_add(title, url, virtual_path, &plid);
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);