Initial support for mpd protocol
This commit is contained in:
parent
3aa5a4df30
commit
830054bd71
|
@ -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
|
||||
|
|
|
@ -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 = \
|
||||
|
|
|
@ -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
688
src/db.c
|
@ -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;
|
||||
|
||||
|
|
42
src/db.h
42
src/db.h
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -36,4 +36,10 @@ void
|
|||
scan_itunes_itml(char *file);
|
||||
#endif
|
||||
|
||||
void
|
||||
filescanner_trigger_initscan(void);
|
||||
|
||||
void
|
||||
filescanner_trigger_fullrescan(void);
|
||||
|
||||
#endif /* !__FILESCANNER_H__ */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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" };
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
20
src/main.c
20
src/main.c
|
@ -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();
|
||||
|
||||
|
|
|
@ -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__ */
|
431
src/player.c
431
src/player.c
|
@ -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);
|
||||
|
||||
|
|
43
src/player.h
43
src/player.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue