From 600e48842fc00dd1fc6d1659b6845c42f08b0ae2 Mon Sep 17 00:00:00 2001 From: chme Date: Sat, 21 Feb 2015 06:04:17 +0100 Subject: [PATCH 01/12] [mpd] idle command --- src/Makefile.am | 3 +- src/listener.c | 63 ++++++++++ src/listener.h | 20 ++++ src/mpd.c | 303 +++++++++++++++++++++++++++++++++++++++++++++++- src/player.c | 3 + 5 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 src/listener.c create mode 100644 src/listener.h diff --git a/src/Makefile.am b/src/Makefile.am index b91edc49..5ef6390f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -119,7 +119,8 @@ forked_daapd_SOURCES = main.c \ scan-wma.c \ $(SPOTIFY_SRC) $(LASTFM_SRC) \ $(MPD_SRC) \ - $(FLAC_SRC) $(MUSEPACK_SRC) + $(FLAC_SRC) $(MUSEPACK_SRC) \ + listener.c listener.h nodist_forked_daapd_SOURCES = \ $(ANTLR_SOURCES) diff --git a/src/listener.c b/src/listener.c new file mode 100644 index 00000000..c653ddc5 --- /dev/null +++ b/src/listener.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 Christian Meffert + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include "logger.h" +#include "listener.h" + +struct listener +{ + notify notify_cb; + struct listener *next; +}; + +struct listener *listener_list = NULL; + +int +listener_add(notify notify_cb) +{ + struct listener *listener; + + listener = (struct listener*)malloc(sizeof(struct listener)); + listener->notify_cb = notify_cb; + listener->next = listener_list; + listener_list = listener; + + return 0; +} + +int +listener_notify(enum listener_event_type type) +{ + struct listener *listener; + + DPRINTF(E_DBG, L_MPD, "Notify event type %d\n", type); + + listener = listener_list; + while (listener) + { + listener->notify_cb(type); + listener = listener->next; + } + + return 0; +} diff --git a/src/listener.h b/src/listener.h new file mode 100644 index 00000000..0b7c91ba --- /dev/null +++ b/src/listener.h @@ -0,0 +1,20 @@ + +#ifndef __LISTENER_H__ +#define __LISTENER_H__ + +enum listener_event_type +{ + LISTENER_NONE = 0, + LISTENER_DATABASE = 1, + LISTENER_PLAYER = 2, +}; + +typedef void (*notify)(enum listener_event_type type); + +int +listener_add(notify notify_cb); + +int +listener_notify(enum listener_event_type type); + +#endif /* !__LISTENER_H__ */ diff --git a/src/mpd.c b/src/mpd.c index 24ffa5e2..9648e48b 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -47,6 +47,7 @@ #include "db.h" #include "conffile.h" #include "misc.h" +#include "listener.h" #include "mpd.h" #include "player.h" @@ -59,6 +60,26 @@ struct event_base *evbase_mpd; static int g_exit_pipe[2]; static struct event *g_exitev; +static int g_cmd_pipe[2]; +static struct event *g_cmdev; + +struct mpd_command; + +typedef int (*cmd_func)(struct mpd_command *cmd); + +struct mpd_command +{ + pthread_mutex_t lck; + pthread_cond_t cond; + + cmd_func func; + + enum listener_event_type arg_evtype; + int nonblock; + + int ret; +}; + #define COMMAND_ARGV_MAX 37 /* MPD error codes (taken from ack.h) */ @@ -121,6 +142,52 @@ free_outputs(struct output *outputs) } } +struct idle_client +{ + struct evbuffer *evbuffer; + enum listener_event_type *types; + unsigned int types_len; + + struct idle_client *next; +}; + +struct idle_client *idle_clients; + +/* ---------------------------- COMMAND EXECUTION -------------------------- */ + +static int +send_command(struct mpd_command *cmd) +{ + int ret; + + if (!cmd->func) + { + DPRINTF(E_LOG, L_MPD, "BUG: cmd->func is NULL!\n"); + return -1; + } + + ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd)); + if (ret != sizeof(cmd)) + { + DPRINTF(E_LOG, L_MPD, "Could not send command: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +static int +nonblock_command(struct mpd_command *cmd) +{ + int ret; + + ret = send_command(cmd); + if (ret < 0) + return -1; + + return 0; +} + static void thread_exit(void) { @@ -554,13 +621,83 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er static int mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - DPRINTF(E_WARN, L_MPD, "Idle command is not supported by forked-daapd, there will be no notifications about changes\n"); + struct idle_client *client; + int i; + + client = (struct idle_client*)malloc(sizeof(struct idle_client)); + if (!client) + { + DPRINTF(E_LOG, L_MPD, "Out of memory for idle_client\n"); + return ACK_ERROR_UNKNOWN; + } + + client->evbuffer = evbuf; + client->types = NULL; + client->types_len = 0; + client->next = idle_clients; + + if (argc > 1) + { + client->types_len = argc - 1; + client->types = (enum listener_event_type*)malloc(client->types_len * sizeof(enum listener_event_type)); + + if (!client->types) + { + DPRINTF(E_LOG, L_MPD, "Out of memory for types\n"); + return ACK_ERROR_UNKNOWN; + } + + for (i = 1; i < argc; i++) + { + if (0 == strcmp(argv[i], "player")) + { + client->types[i - 1] = LISTENER_PLAYER; + } + else if (0 == strcmp(argv[i], "database")) + { + client->types[i - 1] = LISTENER_DATABASE; + } + else + { + DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", argv[i]); + client->types[i - 1] = LISTENER_NONE; + } + } + } + + idle_clients = client; + return 0; } static int mpd_command_noidle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { + struct idle_client *client; + struct idle_client *prev; + + client = idle_clients; + prev = NULL; + + while (client) + { + if (client->evbuffer == evbuf) + { + DPRINTF(E_DBG, L_MPD, "Found idle client for noidle command\n"); + + if (prev) + prev->next = client->next; + else + idle_clients = client->next; + + free(client); + break; + } + + prev = client; + client = client->next; + } + return 0; } @@ -3648,6 +3785,134 @@ mpd_accept_error_cb(struct evconnlistener *listener, void *ctx) DPRINTF(E_LOG, L_MPD, "Error occured %d (%s) on the listener.\n", err, evutil_socket_error_to_string(err)); } +static int +mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type) +{ + int i; + int has_type; + + if (client->types_len == 0) + has_type = 1; + else + has_type = 0; + + for (i = 0; !has_type && (i < client->types_len); i++) + { + has_type = (client->types[i] == type); + } + + if (!has_type) + { + DPRINTF(E_DBG, L_MPD, "Client not listening for event: %d\n", type); + return 1; + } + + evbuffer_add(client->evbuffer, "changed: player\n", 16); + evbuffer_add(client->evbuffer, "OK\n", 3); + + return 0; +} + +static int +mpd_notify_idle(struct mpd_command *cmd) +{ + struct idle_client *client; + struct idle_client *prev; + struct idle_client *next; + int i; + int ret; + + DPRINTF(E_DBG, L_MPD, "Notify clients waiting for idle results: %d\n", cmd->arg_evtype); + + prev = NULL; + next = NULL; + i = 0; + client = idle_clients; + while (client) + { + DPRINTF(E_DBG, L_MPD, "Notify client #%d\n", i); + + next = client->next; + + ret = mpd_notify_idle_client(client, cmd->arg_evtype); + + if (ret == 0) + { + if (prev) + prev->next = next; + else + idle_clients = next; + + free(client); + } + else + { + prev = client; + } + + client = next; + i++; + } + + return 0; +} + +static void +mpd_listener_cb(enum listener_event_type type) +{ + DPRINTF(E_DBG, L_MPD, "Listener callback called with event type %d.\n", type); + struct mpd_command *cmd; + + cmd = (struct mpd_command *)malloc(sizeof(struct mpd_command)); + if (!cmd) + { + DPRINTF(E_LOG, L_MPD, "Could not allocate cache_command\n"); + return; + } + + memset(cmd, 0, sizeof(struct mpd_command)); + + cmd->nonblock = 1; + + cmd->func = mpd_notify_idle; + cmd->arg_evtype = type; + + nonblock_command(cmd); +} + +static void +command_cb(int fd, short what, void *arg) +{ + struct mpd_command *cmd; + int ret; + + ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd)); + if (ret != sizeof(cmd)) + { + DPRINTF(E_LOG, L_MPD, "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(g_cmdev, NULL); +} + /* Thread: main */ int mpd_init(void) @@ -3682,6 +3947,17 @@ int mpd_init(void) goto exit_fail; } +# if defined(__linux__) + ret = pipe2(g_cmd_pipe, O_CLOEXEC); +# else + ret = pipe(g_cmd_pipe); +# endif + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Could not create command pipe: %s\n", strerror(errno)); + goto cmd_fail; + } + evbase_mpd = event_base_new(); if (!evbase_mpd) { @@ -3698,6 +3974,16 @@ int mpd_init(void) event_add(g_exitev, NULL); + + g_cmdev = event_new(evbase_mpd, g_cmd_pipe[0], EV_READ, command_cb, NULL); + if (!g_cmdev) + { + DPRINTF(E_LOG, L_MPD, "Could not create cmd event\n"); + goto evnew_fail; + } + + event_add(g_cmdev, NULL); + if (v6enabled) { saddr_length = sizeof(sin6); @@ -3743,6 +4029,9 @@ int mpd_init(void) goto thread_fail; } + idle_clients = NULL; + listener_add(mpd_listener_cb); + return 0; @@ -3753,6 +4042,10 @@ int mpd_init(void) evbase_mpd = NULL; evbase_fail: + close(g_cmd_pipe[0]); + close(g_cmd_pipe[1]); + + cmd_fail: close(g_exit_pipe[0]); close(g_exit_pipe[1]); @@ -3763,6 +4056,7 @@ int mpd_init(void) /* Thread: main */ void mpd_deinit(void) { + struct idle_client *temp; unsigned short port; int ret; @@ -3782,6 +4076,13 @@ void mpd_deinit(void) return; } + while (idle_clients) + { + temp = idle_clients; + idle_clients = idle_clients->next; + free(temp); + } + // Free event base (should free events too) event_base_free(evbase_mpd); diff --git a/src/player.c b/src/player.c index 4bbd659a..13f967da 100644 --- a/src/player.c +++ b/src/player.c @@ -58,6 +58,7 @@ #include "raop.h" #include "laudio.h" #include "worker.h" +#include "listener.h" #ifdef LASTFM # include "lastfm.h" @@ -302,6 +303,8 @@ status_update(enum play_status status) if (update_handler) update_handler(); + listener_notify(LISTENER_PLAYER); + if (status == PLAY_PLAYING) dev_autoselect = 0; } From e156181121862e382bb1d820ec4f35702645c0a4 Mon Sep 17 00:00:00 2001 From: chme Date: Sat, 11 Apr 2015 14:22:15 +0200 Subject: [PATCH 02/12] [mpd] remove idle client if an eof or error occurred on the event buffer (fixes segfault after closing the connection of an mpd client) --- src/mpd.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/mpd.c b/src/mpd.c index 9648e48b..ded4bbd8 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -670,8 +670,8 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return 0; } -static int -mpd_command_noidle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +static void +mpd_remove_idle_client(struct evbuffer *evbuf) { struct idle_client *client; struct idle_client *prev; @@ -683,7 +683,7 @@ mpd_command_noidle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { if (client->evbuffer == evbuf) { - DPRINTF(E_DBG, L_MPD, "Found idle client for noidle command\n"); + DPRINTF(E_DBG, L_MPD, "Removing idle client for evbuffer\n"); if (prev) prev->next = client->next; @@ -697,7 +697,12 @@ mpd_command_noidle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) prev = client; client = client->next; } +} +static int +mpd_command_noidle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + mpd_remove_idle_client(evbuf); return 0; } @@ -3675,11 +3680,20 @@ mpd_read_cb(struct bufferevent *bev, void *ctx) static void mpd_event_cb(struct bufferevent *bev, short events, void *ctx) { + struct evbuffer *evbuf; + if (events & BEV_EVENT_ERROR) - DPRINTF(E_LOG, L_MPD, "Error from buffer event\n"); + { + DPRINTF(E_LOG, L_MPD, "Error from bufferevent: %s\n", + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + } if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) + { + evbuf = bufferevent_get_output(bev); + mpd_remove_idle_client(evbuf); bufferevent_free(bev); + } } /* From 36499f49972bdfd2f15b1020bb6a85189d4984ce Mon Sep 17 00:00:00 2001 From: chme Date: Sat, 25 Apr 2015 07:43:55 +0200 Subject: [PATCH 03/12] [player] added some source code comments --- src/player.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/player.c b/src/player.c index 13f967da..450bb0ba 100644 --- a/src/player.c +++ b/src/player.c @@ -1644,6 +1644,10 @@ source_count() return ret; } +/* + * Updates cur_playing and notifies remotes and raop devices about + * changes. + */ static uint64_t source_check(void) { @@ -1667,6 +1671,7 @@ source_check(void) return 0; } + /* If cur_playing is NULL, we are still in the first two seconds after starting the stream */ if (!cur_playing) { if (pos >= cur_streaming->output_start) @@ -1680,9 +1685,13 @@ source_check(void) return pos; } + /* Check if we are still in the middle of the current playing song */ if ((cur_playing->end == 0) || (pos < cur_playing->end)) return pos; + /* We have reached the end of the current playing song, update cur_playing to the next song in the queue + and initialize stream_start and output_start values. */ + r_mode = repeat; /* Playlist has only one file, treat REPEAT_ALL as REPEAT_SONG */ if ((r_mode == REPEAT_ALL) && (source_head == source_head->pl_next)) From 1714f3dee4f54335590b07c3bdae629efcac5085 Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 3 May 2015 07:59:34 +0200 Subject: [PATCH 04/12] [mpd] move include of libevent from mpd.h to mpd.c --- src/mpd.c | 10 +++++++++- src/mpd.h | 8 -------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/mpd.c b/src/mpd.c index ded4bbd8..152cc2a3 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -34,6 +34,15 @@ #include #include +#ifdef HAVE_LIBEVENT2 +# include +# include +# include +# include +#else +# include +#endif + #if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD) # define USE_EVENTFD # include @@ -48,7 +57,6 @@ #include "conffile.h" #include "misc.h" #include "listener.h" -#include "mpd.h" #include "player.h" #include "filescanner.h" diff --git a/src/mpd.h b/src/mpd.h index c5430a40..5df5917a 100644 --- a/src/mpd.h +++ b/src/mpd.h @@ -2,14 +2,6 @@ #ifndef __MPD_H__ #define __MPD_H__ -#ifdef HAVE_LIBEVENT2 -# include -# include -# include -# include -#else -# include -#endif int mpd_init(void); From 7097dd15eb3a53e5838272a73ddb960f5c38220c Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 3 May 2015 08:18:26 +0200 Subject: [PATCH 05/12] allow removing of listeners --- src/listener.c | 29 +++++++++++++++++++++++++++++ src/listener.h | 3 +++ src/mpd.c | 2 ++ 3 files changed, 34 insertions(+) diff --git a/src/listener.c b/src/listener.c index c653ddc5..0080a804 100644 --- a/src/listener.c +++ b/src/listener.c @@ -45,6 +45,35 @@ listener_add(notify notify_cb) return 0; } +int +listener_remove(notify notify_cb) +{ + struct listener *listener; + struct listener *prev; + + listener = listener_list; + prev = NULL; + + while (listener) + { + if (listener->notify_cb == notify_cb) + { + if (prev) + prev->next = listener->next; + else + listener_list = NULL; + + free(listener); + return 0; + } + + prev = listener; + listener = listener->next; + } + + return -1; +} + int listener_notify(enum listener_event_type type) { diff --git a/src/listener.h b/src/listener.h index 0b7c91ba..94a0c804 100644 --- a/src/listener.h +++ b/src/listener.h @@ -14,6 +14,9 @@ typedef void (*notify)(enum listener_event_type type); int listener_add(notify notify_cb); +int +listener_remove(notify notify_cb); + int listener_notify(enum listener_event_type type); diff --git a/src/mpd.c b/src/mpd.c index 152cc2a3..34950cee 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -4098,6 +4098,8 @@ void mpd_deinit(void) return; } + listener_remove(mpd_listener_cb); + while (idle_clients) { temp = idle_clients; From d2c7c87191f3373f8b5e1a868606fcf09af6c373 Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 3 May 2015 08:19:15 +0200 Subject: [PATCH 06/12] use listener logic to send dacp update requests --- src/httpd_dacp.c | 11 ++++++++--- src/player.c | 33 --------------------------------- src/player.h | 4 ---- 3 files changed, 8 insertions(+), 40 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index d52396c3..8fa79076 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -46,6 +46,7 @@ #include "dmap_common.h" #include "db.h" #include "player.h" +#include "listener.h" /* httpd event base, from httpd.c */ extern struct event_base *evbase_httpd; @@ -342,10 +343,14 @@ playstatusupdate_cb(int fd, short what, void *arg) /* Thread: player */ static void -dacp_playstatus_update_handler(void) +dacp_playstatus_update_handler(enum listener_event_type type) { int ret; + // Only send status update on player change events + if (type != LISTENER_PLAYER) + return; + #ifdef USE_EVENTFD ret = eventfd_write(update_efd, 1); if (ret < 0) @@ -2423,7 +2428,7 @@ dacp_init(void) event_base_set(evbase_httpd, &updateev); event_add(&updateev, NULL); - player_set_update_handler(dacp_playstatus_update_handler); + listener_add(dacp_playstatus_update_handler); return 0; @@ -2444,7 +2449,7 @@ dacp_deinit(void) struct evhttp_connection *evcon; int i; - player_set_update_handler(NULL); + listener_remove(dacp_playstatus_update_handler); for (i = 0; dacp_handlers[i].handler; i++) regfree(&dacp_handlers[i].preg); diff --git a/src/player.c b/src/player.c index 450bb0ba..18c15bd8 100644 --- a/src/player.c +++ b/src/player.c @@ -165,7 +165,6 @@ struct player_command struct player_status *status; struct player_source *ps; struct player_metadata *pmd; - player_status_handler status_handler; uint32_t *id_ptr; uint64_t *raop_ids; enum repeat_mode mode; @@ -208,9 +207,6 @@ static enum play_status player_state; static enum repeat_mode repeat; static char shuffle; -/* Status updates (for DACP) */ -static player_status_handler update_handler; - /* Playback timer */ #if defined(__linux__) static int pb_timer_fd; @@ -300,9 +296,6 @@ status_update(enum play_status status) { player_state = status; - if (update_handler) - update_handler(); - listener_notify(LISTENER_PLAYER); if (status == PLAY_PLAYING) @@ -4125,14 +4118,6 @@ queue_plid(struct player_command *cmd) return 0; } -static int -set_update_handler(struct player_command *cmd) -{ - update_handler = cmd->arg.status_handler; - - return 0; -} - /* Command processing */ /* Thread: player */ static void @@ -4821,22 +4806,6 @@ player_queue_plid(uint32_t plid) command_deinit(&cmd); } -void -player_set_update_handler(player_status_handler handler) -{ - struct player_command cmd; - - command_init(&cmd); - - cmd.func = set_update_handler; - cmd.func_bh = NULL; - cmd.arg.status_handler = handler; - - sync_command(&cmd); - - command_deinit(&cmd); -} - /* Non-blocking commands used by mDNS */ static void player_device_add(struct raop_device *rd) @@ -5198,8 +5167,6 @@ player_init(void) repeat = REPEAT_OFF; shuffle = 0; - update_handler = NULL; - history = (struct player_history *)calloc(1, sizeof(struct player_history)); /* diff --git a/src/player.h b/src/player.h index a0a591e1..7f98d719 100644 --- a/src/player.h +++ b/src/player.h @@ -71,7 +71,6 @@ struct player_status { }; typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg); -typedef void (*player_status_handler)(void); struct player_source { @@ -224,9 +223,6 @@ player_queue_plid(uint32_t plid); struct player_history * player_history_get(void); -void -player_set_update_handler(player_status_handler handler); - int player_init(void); From aa4a12cabec12839812c162eddc4ae020ad001fa Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 3 May 2015 08:45:38 +0200 Subject: [PATCH 07/12] add events for playlist (queue), volume, speaker, options (random/shuffle) changes --- src/listener.h | 8 ++++++-- src/mpd.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- src/player.c | 27 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/listener.h b/src/listener.h index 94a0c804..fb16c81a 100644 --- a/src/listener.h +++ b/src/listener.h @@ -5,8 +5,12 @@ enum listener_event_type { LISTENER_NONE = 0, - LISTENER_DATABASE = 1, - LISTENER_PLAYER = 2, + LISTENER_DATABASE, + LISTENER_PLAYER, + LISTENER_PLAYLIST, + LISTENER_VOLUME, + LISTENER_SPEAKER, + LISTENER_OPTIONS, }; typedef void (*notify)(enum listener_event_type type); diff --git a/src/mpd.c b/src/mpd.c index 34950cee..965db31f 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -657,13 +657,29 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) for (i = 1; i < argc; i++) { - if (0 == strcmp(argv[i], "player")) + if (0 == strcmp(argv[i], "database")) + { + client->types[i - 1] = LISTENER_DATABASE; + } + else if (0 == strcmp(argv[i], "player")) { client->types[i - 1] = LISTENER_PLAYER; } - else if (0 == strcmp(argv[i], "database")) + else if (0 == strcmp(argv[i], "playlist")) { - client->types[i - 1] = LISTENER_DATABASE; + client->types[i - 1] = LISTENER_PLAYLIST; + } + else if (0 == strcmp(argv[i], "mixer")) + { + client->types[i - 1] = LISTENER_VOLUME; + } + else if (0 == strcmp(argv[i], "output")) + { + client->types[i - 1] = LISTENER_SPEAKER; + } + else if (0 == strcmp(argv[i], "options")) + { + client->types[i - 1] = LISTENER_OPTIONS; } else { @@ -3829,7 +3845,33 @@ mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type return 1; } - evbuffer_add(client->evbuffer, "changed: player\n", 16); + switch (type) + { + case LISTENER_PLAYER: + evbuffer_add(client->evbuffer, "changed: player\n", 16); + break; + + case LISTENER_PLAYLIST: + evbuffer_add(client->evbuffer, "changed: playlist\n", 18); + break; + + case LISTENER_VOLUME: + evbuffer_add(client->evbuffer, "changed: mixer\n", 15); + break; + + case LISTENER_SPEAKER: + evbuffer_add(client->evbuffer, "changed: output\n", 16); + break; + + case LISTENER_OPTIONS: + evbuffer_add(client->evbuffer, "changed: options\n", 17); + break; + + default: + DPRINTF(E_WARN, L_MPD, "Unsupported event type (%d) in notify idle clients.\n", type); + return -1; + } + evbuffer_add(client->evbuffer, "OK\n", 3); return 0; diff --git a/src/player.c b/src/player.c index 18c15bd8..bd1f9fa6 100644 --- a/src/player.c +++ b/src/player.c @@ -3514,6 +3514,8 @@ speaker_set(struct player_command *cmd) } } + listener_notify(LISTENER_SPEAKER); + if (cmd->raop_pending > 0) return 1; /* async */ @@ -3560,6 +3562,8 @@ volume_set(struct player_command *cmd) cmd->raop_pending += raop_set_volume_one(rd->session, rd->volume, device_command_cb); } + listener_notify(LISTENER_VOLUME); + if (cmd->raop_pending > 0) return 1; /* async */ @@ -3610,6 +3614,8 @@ volume_setrel_speaker(struct player_command *cmd) } } + listener_notify(LISTENER_VOLUME); + if (cmd->raop_pending > 0) return 1; /* async */ @@ -3669,6 +3675,8 @@ volume_setabs_speaker(struct player_command *cmd) } } + listener_notify(LISTENER_VOLUME); + if (cmd->raop_pending > 0) return 1; /* async */ @@ -3678,6 +3686,9 @@ volume_setabs_speaker(struct player_command *cmd) static int repeat_set(struct player_command *cmd) { + if (cmd->arg.mode == repeat) + return 0; + switch (cmd->arg.mode) { case REPEAT_OFF: @@ -3691,6 +3702,8 @@ repeat_set(struct player_command *cmd) return -1; } + listener_notify(LISTENER_OPTIONS); + return 0; } @@ -3712,6 +3725,8 @@ shuffle_set(struct player_command *cmd) return -1; } + listener_notify(LISTENER_OPTIONS); + return 0; } @@ -3856,6 +3871,8 @@ queue_add(struct player_command *cmd) if (cur_plid != 0) cur_plid = 0; + listener_notify(LISTENER_PLAYLIST); + return 0; } @@ -3897,6 +3914,8 @@ queue_add_next(struct player_command *cmd) if (cur_plid != 0) cur_plid = 0; + listener_notify(LISTENER_PLAYLIST); + return 0; } @@ -3964,6 +3983,8 @@ queue_move(struct player_command *cmd) ps_dst->pl_next = ps_src; } + listener_notify(LISTENER_PLAYLIST); + return 0; } @@ -4029,6 +4050,8 @@ queue_remove(struct player_command *cmd) source_free(ps); + listener_notify(LISTENER_PLAYLIST); + return 0; } @@ -4055,6 +4078,8 @@ queue_clear(struct player_command *cmd) cur_plid = 0; + listener_notify(LISTENER_PLAYLIST); + return 0; } @@ -4104,6 +4129,8 @@ queue_empty(struct player_command *cmd) source_head->shuffle_prev = source_head; } + listener_notify(LISTENER_PLAYLIST); + return 0; } From 3886ec6638c89c2d6c2ffa0ae4f545aa52047549 Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 3 May 2015 10:34:49 +0200 Subject: [PATCH 08/12] Add version number for playlist (queue) This is necessary to return a valid playlist id for mpd status command. --- src/mpd.c | 3 ++- src/player.c | 12 ++++++++++++ src/player.h | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/mpd.c b/src/mpd.c index 965db31f..ac62bc94 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -790,7 +790,7 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) status.shuffle, (status.repeat == REPEAT_SONG ? 1 : 0), 0 /* consume: not supported by forked-daapd, always return 'off' */, - status.plid, + status.plversion, status.playlistlength, state); @@ -1815,6 +1815,7 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e static int mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { + DPRINTF(E_WARN, L_MPD, "Ignore command %s\n", argv[0]); return 0; } diff --git a/src/player.c b/src/player.c index bd1f9fa6..44d7babb 100644 --- a/src/player.c +++ b/src/player.c @@ -256,6 +256,7 @@ static struct player_source *shuffle_head; static struct player_source *cur_playing; static struct player_source *cur_streaming; static uint32_t cur_plid; +static uint32_t cur_plversion; static struct evbuffer *audio_buf; /* Play history */ @@ -2513,6 +2514,7 @@ get_status(struct player_command *cmd) status->volume = master_volume; status->plid = cur_plid; + status->plversion = cur_plversion; switch (player_state) { @@ -3870,6 +3872,7 @@ queue_add(struct player_command *cmd) if (cur_plid != 0) cur_plid = 0; + cur_plversion++; listener_notify(LISTENER_PLAYLIST); @@ -3913,6 +3916,7 @@ queue_add_next(struct player_command *cmd) if (cur_plid != 0) cur_plid = 0; + cur_plversion++; listener_notify(LISTENER_PLAYLIST); @@ -3983,6 +3987,8 @@ queue_move(struct player_command *cmd) ps_dst->pl_next = ps_src; } + cur_plversion++; + listener_notify(LISTENER_PLAYLIST); return 0; @@ -4050,6 +4056,8 @@ queue_remove(struct player_command *cmd) source_free(ps); + cur_plversion++; + listener_notify(LISTENER_PLAYLIST); return 0; @@ -4077,6 +4085,7 @@ queue_clear(struct player_command *cmd) } cur_plid = 0; + cur_plversion++; listener_notify(LISTENER_PLAYLIST); @@ -4129,6 +4138,8 @@ queue_empty(struct player_command *cmd) source_head->shuffle_prev = source_head; } + cur_plversion++; + listener_notify(LISTENER_PLAYLIST); return 0; @@ -5189,6 +5200,7 @@ player_init(void) cur_playing = NULL; cur_streaming = NULL; cur_plid = 0; + cur_plversion = 0; player_state = PLAY_STOPPED; repeat = REPEAT_OFF; diff --git a/src/player.h b/src/player.h index 7f98d719..f21ab1d5 100644 --- a/src/player.h +++ b/src/player.h @@ -54,6 +54,8 @@ struct player_status { /* Playlist id */ uint32_t plid; + /* Playlist version */ + uint32_t plversion; /* Playlist length */ uint32_t playlistlength; /* Playing song id*/ From baffe498870d1f45d0a5a279d972cc9ba2afa1d6 Mon Sep 17 00:00:00 2001 From: chme Date: Wed, 13 May 2015 11:37:09 +0200 Subject: [PATCH 09/12] refactor use for loop to remove item from linked list --- src/listener.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/listener.c b/src/listener.c index 0080a804..64ffed2f 100644 --- a/src/listener.c +++ b/src/listener.c @@ -51,27 +51,25 @@ listener_remove(notify notify_cb) struct listener *listener; struct listener *prev; - listener = listener_list; prev = NULL; - - while (listener) + for (listener = listener_list; listener; listener = listener->next) { if (listener->notify_cb == notify_cb) - { - if (prev) - prev->next = listener->next; - else - listener_list = NULL; - - free(listener); - return 0; - } + break; prev = listener; - listener = listener->next; } - return -1; + if (!listener) + return 0; + + if (prev) + prev->next = listener->next; + else + listener_list = NULL; + + free(listener); + return 0; } int From 41a08d1931373b89e66172dab8597784990818eb Mon Sep 17 00:00:00 2001 From: chme Date: Mon, 18 May 2015 20:12:18 +0200 Subject: [PATCH 10/12] [mpd] rework listener events to use a bit map --- src/httpd_dacp.c | 2 +- src/listener.c | 7 +++++-- src/listener.h | 15 +++++++-------- src/mpd.c | 47 ++++++++++++----------------------------------- 4 files changed, 25 insertions(+), 46 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 8fa79076..446436de 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -2428,7 +2428,7 @@ dacp_init(void) event_base_set(evbase_httpd, &updateev); event_add(&updateev, NULL); - listener_add(dacp_playstatus_update_handler); + listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER); return 0; diff --git a/src/listener.c b/src/listener.c index 64ffed2f..0c4a7cec 100644 --- a/src/listener.c +++ b/src/listener.c @@ -27,18 +27,20 @@ struct listener { notify notify_cb; + short events; struct listener *next; }; struct listener *listener_list = NULL; int -listener_add(notify notify_cb) +listener_add(notify notify_cb, short events) { struct listener *listener; listener = (struct listener*)malloc(sizeof(struct listener)); listener->notify_cb = notify_cb; + listener->events = events; listener->next = listener_list; listener_list = listener; @@ -82,7 +84,8 @@ listener_notify(enum listener_event_type type) listener = listener_list; while (listener) { - listener->notify_cb(type); + if (type & listener->events) + listener->notify_cb(type); listener = listener->next; } diff --git a/src/listener.h b/src/listener.h index fb16c81a..7fffe37a 100644 --- a/src/listener.h +++ b/src/listener.h @@ -4,19 +4,18 @@ enum listener_event_type { - LISTENER_NONE = 0, - LISTENER_DATABASE, - LISTENER_PLAYER, - LISTENER_PLAYLIST, - LISTENER_VOLUME, - LISTENER_SPEAKER, - LISTENER_OPTIONS, + LISTENER_PLAYER = (1 << 0), + LISTENER_PLAYLIST = (1 << 1), + LISTENER_VOLUME = (1 << 2), + LISTENER_SPEAKER = (1 << 3), + LISTENER_OPTIONS = (1 << 4), + LISTENER_DATABASE = (1 << 5), }; typedef void (*notify)(enum listener_event_type type); int -listener_add(notify notify_cb); +listener_add(notify notify_cb, short events); int listener_remove(notify notify_cb); diff --git a/src/mpd.c b/src/mpd.c index ac62bc94..30e7d2d4 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -153,8 +153,7 @@ free_outputs(struct output *outputs) struct idle_client { struct evbuffer *evbuffer; - enum listener_event_type *types; - unsigned int types_len; + short events; struct idle_client *next; }; @@ -640,54 +639,45 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } client->evbuffer = evbuf; - client->types = NULL; - client->types_len = 0; + client->events = 0; client->next = idle_clients; if (argc > 1) { - client->types_len = argc - 1; - client->types = (enum listener_event_type*)malloc(client->types_len * sizeof(enum listener_event_type)); - - if (!client->types) - { - DPRINTF(E_LOG, L_MPD, "Out of memory for types\n"); - return ACK_ERROR_UNKNOWN; - } - for (i = 1; i < argc; i++) { if (0 == strcmp(argv[i], "database")) { - client->types[i - 1] = LISTENER_DATABASE; + client->events |= LISTENER_DATABASE; } else if (0 == strcmp(argv[i], "player")) { - client->types[i - 1] = LISTENER_PLAYER; + client->events |= LISTENER_PLAYER; } else if (0 == strcmp(argv[i], "playlist")) { - client->types[i - 1] = LISTENER_PLAYLIST; + client->events |= LISTENER_PLAYLIST; } else if (0 == strcmp(argv[i], "mixer")) { - client->types[i - 1] = LISTENER_VOLUME; + client->events |= LISTENER_VOLUME; } else if (0 == strcmp(argv[i], "output")) { - client->types[i - 1] = LISTENER_SPEAKER; + client->events |= LISTENER_SPEAKER; } else if (0 == strcmp(argv[i], "options")) { - client->types[i - 1] = LISTENER_OPTIONS; + client->events |= LISTENER_OPTIONS; } else { DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", argv[i]); - client->types[i - 1] = LISTENER_NONE; } } } + else + client->events = LISTENER_PLAYER | LISTENER_PLAYLIST | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS; idle_clients = client; @@ -3827,20 +3817,7 @@ mpd_accept_error_cb(struct evconnlistener *listener, void *ctx) static int mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type) { - int i; - int has_type; - - if (client->types_len == 0) - has_type = 1; - else - has_type = 0; - - for (i = 0; !has_type && (i < client->types_len); i++) - { - has_type = (client->types[i] == type); - } - - if (!has_type) + if (!(client->events & type)) { DPRINTF(E_DBG, L_MPD, "Client not listening for event: %d\n", type); return 1; @@ -4095,7 +4072,7 @@ int mpd_init(void) } idle_clients = NULL; - listener_add(mpd_listener_cb); + listener_add(mpd_listener_cb, LISTENER_PLAYER | LISTENER_PLAYLIST | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS); return 0; From 0b7323319756f0fae6625926d055066d114e6873 Mon Sep 17 00:00:00 2001 From: chme Date: Thu, 21 May 2015 07:15:05 +0200 Subject: [PATCH 11/12] [mpd] remove libevent1 support --- src/mpd.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mpd.c b/src/mpd.c index 30e7d2d4..ab76a453 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -34,14 +34,10 @@ #include #include -#ifdef HAVE_LIBEVENT2 # include # include # include # include -#else -# include -#endif #if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD) # define USE_EVENTFD From 8c12929b910b59fa86e208f1ca814550da10bbfc Mon Sep 17 00:00:00 2001 From: chme Date: Thu, 21 May 2015 07:57:18 +0200 Subject: [PATCH 12/12] Added source code comments, return error value if adding/removing a listener failed --- src/listener.c | 15 ++++++++------- src/listener.h | 30 +++++++++++++++++++++++++++++- src/player.h | 5 ++++- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/listener.c b/src/listener.c index 0c4a7cec..92b575ba 100644 --- a/src/listener.c +++ b/src/listener.c @@ -21,7 +21,6 @@ #include #include -#include "logger.h" #include "listener.h" struct listener @@ -39,6 +38,10 @@ listener_add(notify notify_cb, short events) struct listener *listener; listener = (struct listener*)malloc(sizeof(struct listener)); + if (!listener) + { + return -1; + } listener->notify_cb = notify_cb; listener->events = events; listener->next = listener_list; @@ -63,7 +66,9 @@ listener_remove(notify notify_cb) } if (!listener) - return 0; + { + return -1; + } if (prev) prev->next = listener->next; @@ -74,13 +79,11 @@ listener_remove(notify notify_cb) return 0; } -int +void listener_notify(enum listener_event_type type) { struct listener *listener; - DPRINTF(E_DBG, L_MPD, "Notify event type %d\n", type); - listener = listener_list; while (listener) { @@ -88,6 +91,4 @@ listener_notify(enum listener_event_type type) listener->notify_cb(type); listener = listener->next; } - - return 0; } diff --git a/src/listener.h b/src/listener.h index 7fffe37a..8b493552 100644 --- a/src/listener.h +++ b/src/listener.h @@ -4,23 +4,51 @@ enum listener_event_type { + /* The player has been started, stopped or seeked */ LISTENER_PLAYER = (1 << 0), + /* The current playlist has been modified */ LISTENER_PLAYLIST = (1 << 1), + /* The volume has been changed */ LISTENER_VOLUME = (1 << 2), + /* A speaker has been enabled or disabled */ LISTENER_SPEAKER = (1 << 3), + /* Options like repeat, random has been changed */ LISTENER_OPTIONS = (1 << 4), + /* The library has been modified */ LISTENER_DATABASE = (1 << 5), }; typedef void (*notify)(enum listener_event_type type); +/* + * Registers the given callback function to the given event types. + * This function is not thread safe. Listeners must be added once at startup. + * + * @param notify_cb Callback function + * @param events Event mask, one or more of LISTENER_* + * @return 0 on success, -1 on failure + */ int listener_add(notify notify_cb, short events); +/* + * Removes the given callback function + * This function is not thread safe. Listeners must be removed once at shutdown. + * + * @param notify_cb Callback function + * @return 0 on success, -1 if the callback was not registered + */ int listener_remove(notify notify_cb); -int +/* + * Calls the callback function of the registered listeners listening for the + * given type of event. + * + * @param type The event type, on of the LISTENER_* values + * + */ +void listener_notify(enum listener_event_type type); #endif /* !__LISTENER_H__ */ diff --git a/src/player.h b/src/player.h index f21ab1d5..8be2168c 100644 --- a/src/player.h +++ b/src/player.h @@ -54,7 +54,10 @@ struct player_status { /* Playlist id */ uint32_t plid; - /* Playlist version */ + /* Playlist version + After startup plversion is 0 and gets incremented after each change of the playlist + (e. g. after adding/moving/removing items). It is used by mpd clients to recognize if + they need to update the current playlist. */ uint32_t plversion; /* Playlist length */ uint32_t playlistlength;