From 600e48842fc00dd1fc6d1659b6845c42f08b0ae2 Mon Sep 17 00:00:00 2001 From: chme Date: Sat, 21 Feb 2015 06:04:17 +0100 Subject: [PATCH] [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; }