Merge pull request #64 from chme/mpdprotocol

Add support for the mpd protocol
This commit is contained in:
ejurgensen 2015-02-20 22:39:03 +01:00
commit c8158805e3
21 changed files with 4605 additions and 139 deletions

View File

@ -202,6 +202,9 @@ FLAC and Musepack support are optional, and they are probably only required
if your version of libav/ffmpeg is very old (version 0.5 or 0.6). Use
--enable-flac and --enable-musepack to enable.
Support for the MPD protocol is optional. Use --enable-mpd to enable this
feature.
Recommended build settings:
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var

View File

@ -37,6 +37,7 @@ forked-daapd is a complete rewrite of mt-daapd (Firefly Media Server).
- [Command line and web interface](#command-line-and-web-interface)
- [Spotify](#spotify)
- [LastFM](#lastfm)
- [MPD clients](#mpd-clients)
## Getting started
@ -374,9 +375,9 @@ not necessary during normal operation.
forked-daapd is meant to be used with the clients mentioned above, so it does
not have a command line interface nor does it have a web interface. You can,
however, to some extent control forked-daapd from the command line by issuing
DAAP/DACP commands with a program like curl. Here is an example of how to do
that.
however, to some extent control forked-daapd with [MPD clients](#mpd-clients) or
from the command line by issuing DAAP/DACP commands with a program like curl. Here
is an example of how to do that.
Say you have a playlist with a radio station, and you want to make a script that
starts playback of that station:
@ -396,6 +397,7 @@ curl "http://localhost:3689/logout?session-id=50"
```
## Spotify
forked-daapd has *some* support for Spotify. It must be compiled with the
@ -449,3 +451,38 @@ session key. The session key does not expire.
To stop scrobbling from forked-daapd, add an empty ".lastfm" file to your
library.
## MPD clients
If forked-daapd was build with support for the [Music Player Deamon](http://musicpd.org/)
protocol (see the [INSTALL](INSTALL) file) you can - to some extent - use clients for MPD
to control forked-daapd.
By default forked-daapd listens on port 6600 for MPD clients. You can change this by
adding a section "mpd" to the forked-daapd.conf file:
```
# MPD configuration (only have effect if MPD enabled - see README/INSTALL)
mpd {
port = 8800
}
```
Currently only a subset of the commands offered by MPD (see [MPD protocol documentation](http://www.musicpd.org/doc/protocol/))
are supported by forked-daapd.
Due to some differences between forked-daapd and MPD not all commands will act the same way they would running MPD:
- consume, crossfade, mixrampdb, mixrampdelay and replaygain will have no effect
- single, repeat: unlike MPD forked-daapd does not support setting single and repeat separately
on/off, instead repeat off, repeat all and repeat single are supported. Thus setting single on
will result in repeat single, repeat on results in repeat all.
Following table shows what is working for a selection of MPD clients:
| Client | Type | Status |
| --------------------------------------------- | ------ | --------------- |
| [mpc](http://www.musicpd.org/clients/mpc/) | CLI | Working commands: mpc, add, crop, current, del (ranges are not yet supported), play, next, prev (behaves like cdprev), pause, toggle, cdprev, seek, clear, playlist, ls, load, volume, repeat, random, single, update (initiates an init-rescan, the path argument is not supported) |
| [ympd](http://www.ympd.org/) | Web | Everything except "search" should work |

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

@ -244,6 +244,112 @@ sqlext_daap_unicode_xcollation(void *notused, int llen, const void *left, int rl
return rpp;
}
static void
sqlext_daap_substring_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv)
{
const unsigned char *s1;
const unsigned char *s2;
int index;
char *start;
char *end;
char *result;
if (n < 2)
{
sqlite3_result_error(pv, "daap_substring() requires at least 2 parameters", -1);
return;
}
if (SQLITE_TEXT != sqlite3_value_type(ppv[0]) || SQLITE_TEXT != sqlite3_value_type(ppv[1]))
{
sqlite3_result_null(pv);
return;
}
s1 = sqlite3_value_text(ppv[0]);
s2 = sqlite3_value_text(ppv[1]);
if (n > 2)
index = sqlite3_value_int(ppv[2]);
else
index = 0;
if (strlen((char *) s1) < index)
{
sqlite3_result_null(pv);
return;
}
start = (char *) s1 + index;
end = strstr(start, (char *) s2);
if (!end)
{
sqlite3_result_null(pv);
return;
}
result = sqlite3_malloc(end - (char *) s1 + 1);
if (!result)
{
sqlite3_result_error_nomem(pv);
return;
}
strncpy((char*) result, (char*) s1, end - (char *) s1);
*(result + (end - (char *) s1)) = '\0';
sqlite3_result_text(pv, (char*) result, -1, SQLITE_TRANSIENT);
sqlite3_free(result);
}
static void
sqlext_daap_charindex_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv)
{
const unsigned char *s1;
const unsigned char *s2;
int index;
char *start;
char *end;
if (n < 2)
{
sqlite3_result_error(pv, "daap_charindex() requires at least 2 parameters", -1);
return;
}
if (SQLITE_TEXT != sqlite3_value_type(ppv[0]) || SQLITE_TEXT != sqlite3_value_type(ppv[1]))
{
sqlite3_result_int(pv, -1);
return;
}
s1 = sqlite3_value_text(ppv[0]);
s2 = sqlite3_value_text(ppv[1]);
if (n > 2)
index = sqlite3_value_int(ppv[2]);
else
index = 0;
if (strlen((char *) s1) < index)
{
sqlite3_result_int(pv, -1);
return;
}
start = (char *) s1 + index;
end = strstr(start, (char *) s2);
if (!end)
{
sqlite3_result_int(pv, -1);
return;
}
sqlite3_result_int(pv, end - (char *) s1);
}
int
sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi)
@ -269,5 +375,23 @@ sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines
return -1;
}
ret = sqlite3_create_function(db, "daap_substring", 3, SQLITE_UTF8, NULL, sqlext_daap_substring_xfunc, NULL, NULL);
if (ret != SQLITE_OK)
{
if (pzErrMsg)
*pzErrMsg = sqlite3_mprintf("Could not create daap_substring function: %s\n", sqlite3_errmsg(db));
return -1;
}
ret = sqlite3_create_function(db, "daap_charindex", 3, SQLITE_UTF8, NULL, sqlext_daap_charindex_xfunc, NULL, NULL);
if (ret != SQLITE_OK)
{
if (pzErrMsg)
*pzErrMsg = sqlite3_mprintf("Could not create daap_charindex function: %s\n", sqlite3_errmsg(db));
return -1;
}
return 0;
}

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
@ -123,6 +127,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()
};

442
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,18 @@ db_escape_string(const char *str)
return ret;
}
void
free_fi(struct filelist_info *fi, int content_only)
{
if (fi->virtual_path)
free(fi->virtual_path);
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 +423,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
@ -458,6 +478,9 @@ free_pli(struct playlist_info *pli, int content_only)
if (pli->path)
free(pli->path);
if (pli->virtual_path)
free(pli->virtual_path);
if (!content_only)
free(pli);
else
@ -792,7 +815,7 @@ db_purge_cruft(time_t ref)
void
db_purge_all(void)
{
char *queries[5] =
char *queries[6] =
{
"DELETE FROM inotify;",
"DELETE FROM playlistitems;",
@ -1852,6 +1875,111 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst
return 0;
}
/* Filelist */
int
db_mpd_start_query_filelist(struct query_params *qp, char *parentpath)
{
char *query;
int ret;
/*
query = sqlite3_mprintf(
"SELECT "
" CASE WHEN INSTR(SUBSTR(virtual_path, LENGTH(%Q)+1), '/') = 0 "
" THEN "
" virtual_path "
" ELSE "
" SUBSTR(virtual_path, 1, LENGTH(%Q)+INSTR(SUBSTR(virtual_path, LENGTH(%Q)+1), '/')-1) "
" END AS path, "
" MAX(time_modified), "
" CASE WHEN INSTR(SUBSTR(virtual_path, LENGTH(%Q)+1), '/') = 0 "
" THEN "
" type "
" ELSE "
" 2 "
" END AS ftype, "
" disabled "
"FROM filelist "
"WHERE virtual_path LIKE '%q%%' "
"GROUP BY ftype, path "
"ORDER BY ftype, path;", parentpath, parentpath, parentpath, parentpath, parentpath);
*/
query = sqlite3_mprintf(
"SELECT "
" CASE WHEN daap_charindex(virtual_path, '/', LENGTH(%Q)) = -1 "
" THEN "
" virtual_path "
" ELSE "
" daap_substring(virtual_path, '/', LENGTH(%Q)) "
" END AS path, "
" MAX(time_modified), "
" CASE WHEN daap_charindex(virtual_path, '/', LENGTH(%Q)) = -1 "
" THEN "
" type "
" ELSE "
" 2 "
" END AS ftype "
"FROM filelist "
"WHERE virtual_path LIKE '%q%%' "
"GROUP BY ftype, path "
"ORDER BY ftype, path;", parentpath, parentpath, parentpath, 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_mpd_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->virtual_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->virtual_path = strdup((char *)sqlite3_column_text(qp->stmt, 0));
fi->time_modified = sqlite3_column_int(qp->stmt, 1);
fi->type = sqlite3_column_int(qp->stmt, 2);
return 0;
}
/* Files */
int
@ -2359,6 +2487,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 +2521,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 +2530,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 +2560,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)
{
@ -2444,19 +2596,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 +2622,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 +2653,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)
@ -2865,6 +3032,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 +3105,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 +3133,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");
@ -3003,14 +3194,17 @@ 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);
return ret;
#undef Q_TMPL
}
@ -3108,6 +3302,7 @@ db_pl_enable_bycookie(uint32_t cookie, char *path)
}
/* Groups */
int
db_groups_clear(void)
@ -4275,7 +4470,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 +4484,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 +4525,17 @@ db_perthread_deinit(void)
" path VARCHAR(4096) NOT NULL" \
");"
#define V_FILELIST \
"CREATE VIEW IF NOT EXISTS filelist as" \
" SELECT " \
" virtual_path, time_modified, 3 as type " \
" FROM files WHERE disabled = 0" \
" UNION " \
" SELECT " \
" virtual_path, db_timestamp, 1 as type " \
" FROM playlists where disabled = 0 AND type = 0" \
";"
#define TRG_GROUPS_INSERT_FILES \
"CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
" BEGIN" \
@ -4372,15 +4580,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 +4606,8 @@ static const struct db_init_query db_init_table_queries[] =
{ T_SPEAKERS, "create table speakers" },
{ T_INOTIFY, "create table inotify" },
{ V_FILELIST, "create view filelist" },
{ TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" },
{ TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" },
@ -4454,11 +4664,14 @@ static const struct db_init_query db_init_table_queries[] =
#define I_ALBUM \
"CREATE INDEX IF NOT EXISTS idx_album ON files(album, album_sort);"
#define I_FILELIST \
"CREATE INDEX IF NOT EXISTS idx_filelist ON files(disabled, virtual_path, time_modified);"
#define I_PL_PATH \
"CREATE INDEX IF NOT EXISTS idx_pl_path ON playlists(path);"
#define I_PL_DISABLED \
"CREATE INDEX IF NOT EXISTS idx_pl_disabled ON playlists(disabled);"
"CREATE INDEX IF NOT EXISTS idx_pl_disabled ON playlists(disabled, type, virtual_path, db_timestamp);"
#define I_FILEPATH \
"CREATE INDEX IF NOT EXISTS idx_filepath ON playlistitems(filepath ASC);"
@ -4486,6 +4699,7 @@ static const struct db_init_query db_init_index_queries[] =
{ I_GENRE, "create genre index" },
{ I_TITLE, "create title index" },
{ I_ALBUM, "create album index" },
{ I_FILELIST, "create filelist index" },
{ I_PL_PATH, "create playlist path index" },
{ I_PL_DISABLED, "create playlist state index" },
@ -5428,6 +5642,153 @@ 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_VIEW_FILELIST \
"CREATE VIEW IF NOT EXISTS filelist as" \
" SELECT " \
" virtual_path, time_modified, 3 as type " \
" FROM files WHERE disabled = 0" \
" UNION " \
" SELECT " \
" virtual_path, db_timestamp, 1 as type " \
" FROM playlists WHERE disabled = 0 AND type = 0" \
";"
#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_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_CREATE_VIEW_FILELIST, "create new view filelist" },
{ 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(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);
}
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);
}
}
sqlite3_free(errmsg);
sqlite3_finalize(stmt);
return 0;
}
static int
db_check_version(void)
{
@ -5539,6 +5900,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,12 @@ enum query_type {
#define ARTWORK_PARENTDIR 5
#define ARTWORK_SPOTIFY 6
enum filelistitem_type {
F_PLAYLIST = 1,
F_DIR = 2,
F_FILE = 3,
};
struct query_params {
/* Query parameters, filled in by caller */
enum query_type type;
@ -149,6 +155,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 +178,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 +282,17 @@ 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 *virtual_path;
uint32_t time_modified;
enum filelistitem_type type;
};
struct watch_info {
int wd;
char *path;
@ -300,6 +316,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 +413,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 +447,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 +466,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 +490,13 @@ db_groups_clear(void);
int
db_group_persistentid_byid(int id, int64_t *persistentid);
/* Filelist */
int
db_mpd_start_query_filelist(struct query_params *qp, char *path);
int
db_mpd_query_fetch_filelist(struct query_params *qp, struct filelist_info *fi);
/* 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;
@ -125,6 +143,9 @@ static struct stacked_dir *dirstack;
/* Count of files scanned during a bulk scan */
static int counter;
/* Flag for scan in progress */
static int scanning;
/* Forward */
static void
bulk_scan(int flags);
@ -132,6 +153,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_SCAN, "BUG: cmd->func is NULL!\n");
return -1;
}
ret = write(cmd_pipe[1], &cmd, sizeof(cmd));
if (ret != sizeof(cmd))
{
DPRINTF(E_LOG, L_SCAN, "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 +634,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 +740,22 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
fixup_tags(mfi);
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);
}
else
{
snprintf(virtual_path, PATH_MAX, "/file:%s", mfi->path);
mfi->virtual_path = strdup(virtual_path);
}
if (mfi->id == 0)
db_file_add(mfi);
else
@ -815,11 +893,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 +902,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:
@ -1064,6 +1132,9 @@ bulk_scan(int flags)
time_t end;
int i;
// Set global flag to avoid queued scan requests
scanning = 1;
start = time(NULL);
playlists = NULL;
@ -1135,6 +1206,9 @@ bulk_scan(int flags)
DPRINTF(E_DBG, L_SCAN, "Running post library scan jobs\n");
db_hook_post_scan();
}
// Set scan in progress flag to FALSE
scanning = 0;
}
@ -1210,7 +1284,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 +1945,134 @@ 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_SCAN, "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;
if (scanning)
{
DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
cmd = (struct filescanner_command *)malloc(sizeof(struct filescanner_command));
if (!cmd)
{
DPRINTF(E_LOG, L_SCAN, "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;
if (scanning)
{
DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
cmd = (struct filescanner_command *)malloc(sizeof(struct filescanner_command));
if (!cmd)
{
DPRINTF(E_LOG, L_SCAN, "Could not allocate cache_command\n");
return;
}
memset(cmd, 0, sizeof(struct filescanner_command));
cmd->nonblock = 1;
cmd->func = filescanner_fullrescan;
nonblock_command(cmd);
}
/*
* Query the status of the filescanner
* @return 1 if scan is running, otherwise 0
*/
int
filescanner_scanning(void)
{
return scanning;
}
/* Thread: main */
int
filescanner_init(void)
@ -1879,6 +2080,7 @@ filescanner_init(void)
int ret;
scan_exit = 0;
scanning = 0;
evbase_scan = event_base_new();
if (!evbase_scan)
@ -1924,6 +2126,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_SCAN, "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 +2152,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 +2213,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,13 @@ void
scan_itunes_itml(char *file);
#endif
void
filescanner_trigger_initscan(void);
void
filescanner_trigger_fullrescan(void);
int
filescanner_scanning(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();

3140
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,37 @@ struct spk_enum
void *arg;
};
enum range_type
{
RANGEARG_NONE,
RANGEARG_ID,
RANGEARG_POS,
RANGEARG_RANGE
};
/*
* Identifies an item or a range of items
*
* Depending on item_range.type the item(s) are identified by:
* - item id (type = RANGEARG_ID) given in item_range.id
* - item position (type = RANGEARG_POS) given in item_range.start_pos
* - start and end position (type = RANGEARG_RANGE) given in item_range.start_pos to item_range.end_pos
*
* The pointer id_ptr may be set to an item id by the called function.
*/
struct item_range
{
enum range_type type;
uint32_t id;
int start_pos;
int end_pos;
char shuffle;
uint32_t *id_ptr;
};
struct player_command
{
pthread_mutex_t lck;
@ -121,11 +152,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 +671,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 +708,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 +728,7 @@ player_queue_make(struct query_params *qp, const char *sort)
memset(ps, 0, sizeof(struct player_source));
ps->id = id;
ps->len_ms = song_length;
if (!q_head)
q_head = ps;
@ -979,6 +1023,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 +1490,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)
{
@ -2235,6 +2338,14 @@ playback_abort(void)
metadata_purge();
}
static struct player_source *
next_ps(struct player_source *ps, char shuffle)
{
if (shuffle)
return ps->shuffle_next;
else
return ps->pl_next;
}
/* Actual commands, executed in the player thread */
static int
@ -2248,6 +2359,8 @@ get_status(struct player_command *cmd)
status = cmd->arg.status;
memset(status, 0, sizeof(struct player_status));
status->shuffle = shuffle;
status->repeat = repeat;
@ -2271,8 +2384,10 @@ get_status(struct player_command *cmd)
pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - cur_streaming->stream_start;
status->pos_ms = (pos * 1000) / 44100;
status->len_ms = cur_streaming->len_ms;
status->pos_pl = source_position(cur_streaming, 0);
status->pos_pl = source_position(cur_streaming);
break;
case PLAY_PLAYING:
@ -2308,9 +2423,16 @@ get_status(struct player_command *cmd)
}
status->pos_ms = (pos * 1000) / 44100;
status->len_ms = ps->len_ms;
status->id = ps->id;
status->pos_pl = source_position(ps);
status->pos_pl = source_position(ps, 0);
ps = next_ps(ps, shuffle);
status->next_id = ps->id;
status->next_pos_pl = source_position(ps, 0);
status->playlistlength = source_count();
break;
}
@ -2440,11 +2562,45 @@ playback_start_bh(struct player_command *cmd)
return -1;
}
static struct player_source *
queue_get_source_byid(uint32_t id)
{
struct player_source *ps;
if (!source_head)
return NULL;
ps = source_head->pl_next;
while (ps->id != id && ps != source_head)
{
ps = ps->pl_next;
}
return ps;
}
static struct player_source *
queue_get_source_bypos(int pos)
{
struct player_source *ps;
int i;
if (!source_head)
return NULL;
ps = source_head;
for (i = pos; i > 0; i--)
ps = ps->pl_next;
return ps;
}
static int
playback_start(struct player_command *cmd)
{
struct raop_device *rd;
uint32_t *idx_id;
struct player_source *ps;
int ret;
if (!source_head)
@ -2454,10 +2610,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 +2632,33 @@ 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 either an item id or an item position is given, get the corresponding
* player_source from the queue.
*/
if (cmd->arg.item_range.type == RANGEARG_ID)
ps = queue_get_source_byid(cmd->arg.item_range.id);
else if (cmd->arg.item_range.type == RANGEARG_POS)
ps = queue_get_source_bypos(cmd->arg.item_range.start_pos);
else
ps = NULL;
/*
* Update queue and cur_streaming depending on
* - given player_source to start playing
* - player state
*/
if (ps)
{
/*
* A song is specified in the arguments (by id or pos) and the corresponding
* player_source (ps) from the queue was found.
*
* Stop playback (if it was paused) and prepare to start playback on ps.
*/
if (cur_playing)
source_stop(cur_playing);
else if (cur_streaming)
@ -2484,38 +2668,33 @@ playback_start(struct player_command *cmd)
cur_streaming = NULL;
if (shuffle)
{
source_reshuffle();
cur_streaming = shuffle_head;
}
else
cur_streaming = source_head;
source_reshuffle();
if (*idx_id > 0)
{
cur_streaming = source_head;
for (; *idx_id > 0; (*idx_id)--)
cur_streaming = cur_streaming->pl_next;
cur_streaming = ps;
if (shuffle)
shuffle_head = cur_streaming;
}
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 source %d in queue\n", cur_streaming->id);
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;
}
else if (!cur_streaming)
{
/*
* Player was stopped, start playing the queue
*/
if (shuffle)
source_reshuffle();
@ -2533,7 +2712,10 @@ playback_start(struct player_command *cmd)
}
else
{
/* After a pause, the source is still open so source_open() doesn't get
/*
* Player was paused, resume playing cur_streaming
*
* After a pause, the source is still open so source_open() doesn't get
* called and we have to handle metadata ourselves.
*/
metadata_send(cur_streaming, 1);
@ -3362,6 +3544,102 @@ 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)
{
// Set start_pos to the position of the current item + 1
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,30 +3800,55 @@ 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)
{
DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming song not found\n");
DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming item not found\n");
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 item with id %d\n", id);
if (id < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid id %d\n", id);
return -1;
}
else if (id == ps->id)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove current playing item, 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 item from position %d\n", pos);
if (pos < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove item, 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;
@ -3806,8 +4109,21 @@ player_now_playing(uint32_t *id)
return ret;
}
/*
* Starts/resumes playback
*
* Depending on the player state, this will either resumes playing the current item (player is paused)
* or begins playing the queue from the beginning.
*
* If shuffle is set, the queue is reshuffled prior to starting playback.
*
* If a pointer is given as argument "itemid", its value will be set to the playing item id.
*
* @param *itemid if not NULL, will be set to the playing item id
* @return 0 if successful, -1 if an error occurred
*/
int
player_playback_start(uint32_t *idx_id)
player_playback_start(uint32_t *itemid)
{
struct player_command cmd;
int ret;
@ -3816,7 +4132,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 +4142,66 @@ player_playback_start(uint32_t *idx_id)
return ret;
}
/*
* Starts playback at item number "pos" of the current queue
*
* If shuffle is set, the queue is reshuffled prior to starting playback.
*
* If a pointer is given as argument "itemid", its value will be set to the playing item id.
*
* @param *itemid if not NULL, will be set to the playing item id
* @return 0 if successful, -1 if an error occurred
*/
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;
}
/*
* Starts playback at item with "id" of the current queue
*
* If shuffle is set, the queue is reshuffled prior to starting playback.
*
* If a pointer is given as argument "itemid", its value will be set to the playing item id.
*
* @param *itemid if not NULL, will be set to the playing item id
* @return 0 if successful, -1 if an error occurred
*/
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 +4432,44 @@ player_shuffle_set(int enable)
return ret;
}
struct player_source *
player_queue_get(void)
/*
* Retrieves a list of item ids in the queue from postion 'start_pos' to 'end_pos'
*
* If start_pos is -1, the list starts with the item next from the current playing item.
* If end_pos is -1, this list contains all songs starting from 'start_pos'
*
* The 'shuffle' argument determines if the items are taken from the playqueue (shuffle = 0)
* or the shufflequeue (shuffle = 1).
*
* @param start_pos Start the listing from 'start_pos'
* @param end_pos End the listing at 'end_pos'
* @param shuffle If set to 1 use the shuffle queue, otherwise the playqueue
* @return List of items (ids) in the queue
*/
struct player_queue *
player_queue_get(int start_pos, int end_pos, char shuffle)
{
if (shuffle)
return shuffle_head;
else
return source_head;
struct player_command cmd;
int ret;
command_init(&cmd);
cmd.func = queue_get;
cmd.func_bh = NULL;
cmd.arg.item_range.type = RANGEARG_POS;
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;
}
int
@ -4122,7 +4530,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 +4540,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 item */
uint32_t pos_ms;
/* Length in ms of playing item */
uint32_t len_ms;
/* Playlist position of playing item*/
int pos_pl;
/* Item id of next item in playlist */
uint32_t next_id;
/* Playlist position of next item */
int next_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 len_ms;
enum source_type type;
int setup_done;
@ -82,6 +95,21 @@ struct player_source
struct player_source *play_next;
};
struct player_queue
{
// The item id of the current playing item
uint32_t playingid;
// The number of items in the queue
unsigned int length;
// The position in the queue for the first item in the queue array
unsigned int start_pos;
// The number of items in the queue array
unsigned int count;
// The queue array (array of item ids)
uint32_t *queue;
};
struct player_history
{
/* Buffer index of the oldest remembered song */
@ -113,6 +141,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 +185,13 @@ 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);
int
player_queue_add(struct player_source *ps);
@ -165,6 +205,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);