--- /dev/null
+/*
+ * Interface to Music Player Daemon
+ *
+ * (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#define LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/clists.h>
+#include <ucw/mainloop.h>
+#include <ucw/mempool.h>
+#include <ucw/simple-lists.h>
+#include <ucw/stkstring.h>
+#include <ucw/string.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include "ursaryd.h"
+
+static struct main_timer mpd_connect_timer;
+static struct main_file mpd_connect_file;
+static struct main_rec_io mpd_rio;
+static int mpd_sk = -1;
+
+enum mpd_state mpd_state;
+#define MPD_STATE(s) do { mpd_state = s; DBG("MPD: " #s); } while (0)
+
+struct mpd_cmd { // malloc'ed
+ cnode n;
+ char *cmd; // malloc'ed
+ void (*done)(struct mpd_cmd *c);
+ char *status;
+ int err;
+ int idle; // 1=this is an idle command, 2=aborted idle command
+ clist output; // list of simp2_node's allocated from mpd_reply_pool
+};
+
+static clist mpd_cmd_queue;
+static struct mpd_cmd *mpd_current_command; // ... at the head of the queue
+static struct mempool *mpd_reply_pool;
+
+static void mpd_send_cmd(void);
+static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...);
+
+static struct mpd_cmd *mpd_new_cmd(void)
+{
+ struct mpd_cmd *c = xmalloc_zero(sizeof(*c));
+ clist_add_tail(&mpd_cmd_queue, &c->n);
+ clist_init(&c->output);
+ return c;
+}
+
+static void mpd_free_cmd(struct mpd_cmd *c)
+{
+ xfree(c->cmd);
+ xfree(c);
+}
+
+static void mpd_error(const char *text, ...)
+{
+ va_list args;
+ va_start(args, text);
+ const char *x = stk_vprintf(text, args);
+ msg(L_ERROR, "MPD: %s", x);
+ va_end(args);
+
+ DBG("MPD: Flushing commands");
+ mpd_current_command = NULL;
+ struct mpd_cmd *c;
+ while (c = (struct mpd_cmd *) clist_remove_head(&mpd_cmd_queue))
+ mpd_free_cmd(c);
+
+ DBG("MPD: Tearing down server connection");
+ rec_io_del(&mpd_rio);
+ file_del(&mpd_connect_file);
+ if (mpd_sk >= 0)
+ {
+ close(mpd_sk);
+ mpd_sk = -1;
+ }
+
+ DBG("MPD: Scheduling reconnect");
+ timer_add_rel(&mpd_connect_timer, 5000);
+ MPD_STATE(MPD_OFFLINE);
+ schedule_update();
+}
+
+static void mpd_cmd_done(struct mpd_cmd *c)
+{
+ DBG("MPD: Command finished (err=%d)", c->err);
+ rec_io_set_timeout(&mpd_rio, 0);
+ if (c->done)
+ c->done(c);
+ clist_remove(&c->n);
+ mpd_free_cmd(c);
+
+ mpd_current_command = NULL;
+ mpd_send_cmd();
+}
+
+static const char *mpd_find_output(struct mpd_cmd *c, const char *key, const char *deflt)
+{
+ CLIST_FOR_EACH(struct simp2_node *, n, c->output)
+ if (!strcmp(n->s1, key))
+ return n->s2;
+ return deflt;
+}
+
+static char *mpd_player_state;
+
+static void mpd_got_status(struct mpd_cmd *c)
+{
+ const char *state = mpd_find_output(c, "state", "");
+ DBG("MPD: Player state <%s>", state);
+ SET_STRING(mpd_player_state, state);
+ schedule_update();
+}
+
+const char *mpd_get_player_state(void)
+{
+ if (mpd_state != MPD_ONLINE)
+ return "down";
+ else if (mpd_player_state)
+ return mpd_player_state;
+ else
+ return "unknown";
+}
+
+static void mpd_got_idle(struct mpd_cmd *c)
+{
+ const char *chg = mpd_find_output(c, "changed", "");
+ if (!strcmp(chg, "player"))
+ mpd_cmd(mpd_got_status, "status");
+}
+
+static bool mpd_got_line(char *line)
+{
+ if (mpd_state == MPD_WAIT_GREETING)
+ {
+ if (!str_has_prefix(line, "OK "))
+ mpd_error("Invalid welcome line");
+ MPD_STATE(MPD_ONLINE);
+ mpd_cmd(mpd_got_status, "status");
+ return 1;
+ }
+
+ ASSERT(mpd_state == MPD_ONLINE);
+
+ struct mpd_cmd *c = mpd_current_command;
+ if (!c)
+ {
+ mpd_error("Reply for no command");
+ return 0;
+ }
+
+ if (!strcmp(line, "OK"))
+ {
+ c->status = line;
+ c->err = 0;
+ mpd_cmd_done(c);
+ }
+ else if (str_has_prefix(line, "ACK "))
+ {
+ c->status = line;
+ if (line[4] != '[')
+ {
+ mpd_error("Reply syntax error: <%s>", line);
+ return 0;
+ }
+ c->err = atoi(line+5);
+ mpd_cmd_done(c);
+ }
+ else
+ {
+ char *sep = strchr(line, ':');
+ if (!sep || sep[1] != ' ')
+ {
+ mpd_error("Reply syntax error: <%s>", line);
+ return 0;
+ }
+ *sep++ = 0;
+ *sep++ = 0;
+ simp2_node *sn = simp2_append(mpd_reply_pool, &c->output);
+ sn->s1 = mp_strdup(mpd_reply_pool, line);
+ sn->s2 = mp_strdup(mpd_reply_pool, sep);
+ }
+
+ return 1;
+}
+
+static uint mpd_read_handler(struct main_rec_io *rio)
+{
+ uint len = rec_io_parse_line(rio);
+ if (!len)
+ return 0;
+
+ char line[len];
+ memcpy(line, rio->read_rec_start, len-1);
+ line[len-1] = 0;
+
+ DBG("Received <%s>", line);
+ if (mpd_got_line(line))
+ return len;
+ else
+ return ~0U;
+}
+
+static int mpd_notify_handler(struct main_rec_io *rio UNUSED, int status)
+{
+ switch (status)
+ {
+ case RIO_EVENT_EOF:
+ mpd_error("Connection closed by server");
+ return HOOK_IDLE;
+ default:
+ if (status < 0)
+ {
+ mpd_error("Error on server connection (status=%d errno=%d)", status, errno);
+ return HOOK_IDLE;
+ }
+ return HOOK_RETRY;
+ }
+}
+
+static void mpd_send_cmd(void)
+{
+ struct mpd_cmd *c;
+
+ if (c = mpd_current_command)
+ {
+ if (c->idle == 1)
+ {
+ DBG("MPD: Sending noidle");
+ rec_io_write(&mpd_rio, "noidle\n", 7);
+ c->idle = 2;
+ }
+ return;
+ }
+
+ c = (struct mpd_cmd *) clist_head(&mpd_cmd_queue);
+ if (!c)
+ {
+ c = mpd_new_cmd();
+ c->cmd = malloc(6);
+ strcpy(c->cmd, "idle\n");
+ c->idle = 1;
+ c->done = mpd_got_idle;
+ }
+ mpd_current_command = c;
+
+ DBG("MPD: Sending command <%s>", c->cmd);
+ rec_io_write(&mpd_rio, c->cmd, strlen(c->cmd));
+ if (!c->idle)
+ rec_io_set_timeout(&mpd_rio, 5000);
+}
+
+static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...)
+{
+ struct mpd_cmd *c = mpd_new_cmd();
+ c->done = done;
+
+ va_list args, args2;
+ va_start(args, cmd);
+ va_copy(args2, args);
+ int len = vsnprintf(NULL, 0, cmd, args);
+ c->cmd = xmalloc(len + 2);
+ vsnprintf(c->cmd, len + 1, cmd, args2);
+ c->cmd[len] = '\n';
+ c->cmd[len+1] = 0;
+ va_end(args);
+ va_end(args2);
+
+ mpd_send_cmd();
+}
+
+static int mpd_connected(struct main_file *f)
+{
+ if (f)
+ {
+ // Called from the hook
+ DBG("MPD: Connection hook");
+ file_del(f);
+ timer_del(&mpd_connect_timer);
+ int err;
+ socklen_t len = sizeof(err);
+ if (getsockopt(mpd_sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
+ ASSERT(0);
+ if (err)
+ {
+ mpd_error("Connection refused: %s", strerror(err));
+ return HOOK_IDLE;
+ }
+ }
+
+ MPD_STATE(MPD_WAIT_GREETING);
+ mpd_rio.read_handler = mpd_read_handler;
+ mpd_rio.notify_handler = mpd_notify_handler;
+ mpd_rio.read_rec_max = 16384;
+ rec_io_add(&mpd_rio, mpd_sk);
+ rec_io_start_read(&mpd_rio);
+
+ return HOOK_IDLE;
+}
+
+void mpd_play(void)
+{
+ return mpd_cmd(NULL, "play");
+}
+
+void mpd_stop(void)
+{
+ return mpd_cmd(NULL, "stop");
+}
+
+void mpd_pause(int arg)
+{
+ return mpd_cmd(NULL, "pause %d", arg);
+}
+
+static void mpd_connect(struct main_timer *t)
+{
+ timer_del(t);
+
+ if (mpd_state == MPD_CONNECTING)
+ {
+ mpd_error("Attempt to connect timed out");
+ return;
+ }
+
+ DBG("MPD: Trying to connect");
+
+ mpd_sk = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (mpd_sk < 0)
+ die("Cannot create socket: %m");
+
+ fcntl(mpd_sk, F_SETFL, fcntl(mpd_sk, F_GETFL) | O_NONBLOCK);
+
+ bzero(&mpd_rio, sizeof(mpd_rio));
+ bzero(&mpd_connect_file, sizeof(mpd_connect_file));
+ mpd_connect_file.fd = mpd_sk;
+ mpd_connect_file.write_handler = mpd_connected;
+ MPD_STATE(MPD_CONNECTING);
+
+ struct sockaddr_in sin;
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(6600);
+ sin.sin_addr.s_addr = htonl(0x7f000001);
+ if (connect(mpd_sk, (struct sockaddr *) &sin, sizeof(sin)) < 0)
+ {
+ if (errno == EINPROGRESS)
+ {
+ file_add(&mpd_connect_file);
+ timer_add_rel(&mpd_connect_timer, 5000);
+ }
+ else
+ mpd_error("Unable to connect: %m");
+ }
+ else
+ mpd_connected(NULL);
+}
+
+void mpd_init(void)
+{
+ clist_init(&mpd_cmd_queue);
+ mpd_reply_pool = mp_new(4096);
+
+ mpd_connect_timer.handler = mpd_connect;
+ timer_add_rel(&mpd_connect_timer, 100);
+ mpd_state = MPD_OFFLINE;
+}