]> mj.ucw.cz Git - ursary.git/blob - mpd.c
MPD: Separate stop/prev/next buttons
[ursary.git] / mpd.c
1 /*
2  *      Interface to Music Player Daemon
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/clists.h>
11 #include <ucw/mainloop.h>
12 #include <ucw/mempool.h>
13 #include <ucw/simple-lists.h>
14 #include <ucw/stkstring.h>
15 #include <ucw/string.h>
16
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <netinet/in.h>
27 #include <netinet/tcp.h>
28
29 #include "ursaryd.h"
30
31 static struct main_timer mpd_connect_timer;
32 static struct main_file mpd_connect_file;
33 static struct main_rec_io mpd_rio;
34 static int mpd_sk = -1;
35
36 enum mpd_state mpd_state;
37 #define MPD_STATE(s) do { mpd_state = s; DBG("MPD: " #s); } while (0)
38
39 struct mpd_cmd {                        // malloc'ed
40   cnode n;
41   char *cmd;                            // malloc'ed
42   void (*done)(struct mpd_cmd *c);
43   char *status;
44   int err;
45   int idle;                             // 1=this is an idle command, 2=aborted idle command
46   clist output;                         // list of simp2_node's allocated from mpd_reply_pool
47 };
48
49 static clist mpd_cmd_queue;
50 static struct mpd_cmd *mpd_current_command;             // ... at the head of the queue
51 static struct mempool *mpd_reply_pool;
52
53 static void mpd_send_cmd(void);
54 static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...);
55
56 static struct mpd_cmd *mpd_new_cmd(void)
57 {
58   struct mpd_cmd *c = xmalloc_zero(sizeof(*c));
59   clist_add_tail(&mpd_cmd_queue, &c->n);
60   clist_init(&c->output);
61   return c;
62 }
63
64 static void mpd_free_cmd(struct mpd_cmd *c)
65 {
66   xfree(c->cmd);
67   xfree(c);
68 }
69
70 static void mpd_error(const char *text, ...)
71 {
72   va_list args;
73   va_start(args, text);
74   const char *x = stk_vprintf(text, args);
75   msg(L_ERROR, "MPD: %s", x);
76   va_end(args);
77
78   DBG("MPD: Flushing commands");
79   mpd_current_command = NULL;
80   struct mpd_cmd *c;
81   while (c = (struct mpd_cmd *) clist_remove_head(&mpd_cmd_queue))
82     mpd_free_cmd(c);
83
84   DBG("MPD: Tearing down server connection");
85   rec_io_del(&mpd_rio);
86   file_del(&mpd_connect_file);
87   if (mpd_sk >= 0)
88     {
89       close(mpd_sk);
90       mpd_sk = -1;
91     }
92
93   DBG("MPD: Scheduling reconnect");
94   timer_add_rel(&mpd_connect_timer, 5000);
95   MPD_STATE(MPD_OFFLINE);
96   schedule_update();
97 }
98
99 static void mpd_cmd_done(struct mpd_cmd *c)
100 {
101   DBG("MPD: Command finished (err=%d)", c->err);
102   rec_io_set_timeout(&mpd_rio, 0);
103   if (c->done)
104     c->done(c);
105   clist_remove(&c->n);
106   mpd_free_cmd(c);
107
108   mpd_current_command = NULL;
109   mpd_send_cmd();
110 }
111
112 static const char *mpd_find_output(struct mpd_cmd *c, const char *key, const char *deflt)
113 {
114   CLIST_FOR_EACH(struct simp2_node *, n, c->output)
115     if (!strcmp(n->s1, key))
116       return n->s2;
117   return deflt;
118 }
119
120 static char *mpd_player_state;
121
122 static void mpd_got_status(struct mpd_cmd *c)
123 {
124   const char *state = mpd_find_output(c, "state", "");
125   DBG("MPD: Player state <%s>", state);
126   SET_STRING(mpd_player_state, state);
127   schedule_update();
128 }
129
130 const char *mpd_get_player_state(void)
131 {
132   if (mpd_state != MPD_ONLINE)
133     return "down";
134   else if (mpd_player_state)
135     return mpd_player_state;
136   else
137     return "unknown";
138 }
139
140 static void mpd_got_idle(struct mpd_cmd *c)
141 {
142   const char *chg = mpd_find_output(c, "changed", "");
143   if (!strcmp(chg, "player"))
144     mpd_cmd(mpd_got_status, "status");
145 }
146
147 static bool mpd_got_line(char *line)
148 {
149   if (mpd_state == MPD_WAIT_GREETING)
150     {
151       if (!str_has_prefix(line, "OK "))
152         mpd_error("Invalid welcome line");
153       MPD_STATE(MPD_ONLINE);
154       mpd_cmd(mpd_got_status, "status");
155       return 1;
156     }
157
158   ASSERT(mpd_state == MPD_ONLINE);
159
160   struct mpd_cmd *c = mpd_current_command;
161   if (!c)
162     {
163       mpd_error("Reply for no command");
164       return 0;
165     }
166
167   if (!strcmp(line, "OK"))
168     {
169       c->status = line;
170       c->err = 0;
171       mpd_cmd_done(c);
172     }
173   else if (str_has_prefix(line, "ACK "))
174     {
175       c->status = line;
176       if (line[4] != '[')
177         {
178           mpd_error("Reply syntax error: <%s>", line);
179           return 0;
180         }
181       c->err = atoi(line+5);
182       mpd_cmd_done(c);
183     }
184   else
185     {
186       char *sep = strchr(line, ':');
187       if (!sep || sep[1] != ' ')
188         {
189           mpd_error("Reply syntax error: <%s>", line);
190           return 0;
191         }
192       *sep++ = 0;
193       *sep++ = 0;
194       simp2_node *sn = simp2_append(mpd_reply_pool, &c->output);
195       sn->s1 = mp_strdup(mpd_reply_pool, line);
196       sn->s2 = mp_strdup(mpd_reply_pool, sep);
197     }
198
199   return 1;
200 }
201
202 static uint mpd_read_handler(struct main_rec_io *rio)
203 {
204   uint len = rec_io_parse_line(rio);
205   if (!len)
206     return 0;
207
208   char line[len];
209   memcpy(line, rio->read_rec_start, len-1);
210   line[len-1] = 0;
211
212   DBG("Received <%s>", line);
213   if (mpd_got_line(line))
214     return len;
215   else
216     return ~0U;
217 }
218
219 static int mpd_notify_handler(struct main_rec_io *rio UNUSED, int status)
220 {
221   switch (status)
222     {
223     case RIO_EVENT_EOF:
224       mpd_error("Connection closed by server");
225       return HOOK_IDLE;
226     default:
227       if (status < 0)
228         {
229           mpd_error("Error on server connection (status=%d errno=%d)", status, errno);
230           return HOOK_IDLE;
231         }
232       return HOOK_RETRY;
233     }
234 }
235
236 static void mpd_send_cmd(void)
237 {
238   struct mpd_cmd *c;
239
240   if (c = mpd_current_command)
241     {
242       if (c->idle == 1)
243         {
244           DBG("MPD: Sending noidle");
245           rec_io_write(&mpd_rio, "noidle\n", 7);
246           c->idle = 2;
247         }
248       return;
249     }
250
251   c = (struct mpd_cmd *) clist_head(&mpd_cmd_queue);
252   if (!c)
253     {
254       c = mpd_new_cmd();
255       c->cmd = malloc(6);
256       strcpy(c->cmd, "idle\n");
257       c->idle = 1;
258       c->done = mpd_got_idle;
259     }
260   mpd_current_command = c;
261
262   DBG("MPD: Sending command <%s>", c->cmd);
263   rec_io_write(&mpd_rio, c->cmd, strlen(c->cmd));
264   if (!c->idle)
265     rec_io_set_timeout(&mpd_rio, 5000);
266 }
267
268 static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...)
269 {
270   struct mpd_cmd *c = mpd_new_cmd();
271   c->done = done;
272
273   va_list args, args2;
274   va_start(args, cmd);
275   va_copy(args2, args);
276   int len = vsnprintf(NULL, 0, cmd, args);
277   c->cmd = xmalloc(len + 2);
278   vsnprintf(c->cmd, len + 1, cmd, args2);
279   c->cmd[len] = '\n';
280   c->cmd[len+1] = 0;
281   va_end(args);
282   va_end(args2);
283
284   mpd_send_cmd();
285 }
286
287 static int mpd_connected(struct main_file *f)
288 {
289   if (f)
290     {
291       // Called from the hook
292       DBG("MPD: Connection hook");
293       file_del(f);
294       timer_del(&mpd_connect_timer);
295       int err;
296       socklen_t len = sizeof(err);
297       if (getsockopt(mpd_sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
298         ASSERT(0);
299       if (err)
300         {
301           mpd_error("Connection refused: %s", strerror(err));
302           return HOOK_IDLE;
303         }
304     }
305
306   MPD_STATE(MPD_WAIT_GREETING);
307   mpd_rio.read_handler = mpd_read_handler;
308   mpd_rio.notify_handler = mpd_notify_handler;
309   mpd_rio.read_rec_max = 16384;
310   rec_io_add(&mpd_rio, mpd_sk);
311   rec_io_start_read(&mpd_rio);
312
313   return HOOK_IDLE;
314 }
315
316 void mpd_play(void)
317 {
318   return mpd_cmd(NULL, "play");
319 }
320
321 void mpd_stop(void)
322 {
323   return mpd_cmd(NULL, "stop");
324 }
325
326 void mpd_pause(int arg)
327 {
328   return mpd_cmd(NULL, "pause %d", arg);
329 }
330
331 void mpd_next(void)
332 {
333   return mpd_cmd(NULL, "next");
334 }
335
336 void mpd_prev(void)
337 {
338   return mpd_cmd(NULL, "previous");
339 }
340
341 static void mpd_connect(struct main_timer *t)
342 {
343   timer_del(t);
344
345   if (mpd_state == MPD_CONNECTING)
346     {
347       mpd_error("Attempt to connect timed out");
348       return;
349     }
350
351   DBG("MPD: Trying to connect");
352
353   mpd_sk = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
354   if (mpd_sk < 0)
355     die("Cannot create socket: %m");
356
357   fcntl(mpd_sk, F_SETFL, fcntl(mpd_sk, F_GETFL) | O_NONBLOCK);
358
359   bzero(&mpd_rio, sizeof(mpd_rio));
360   bzero(&mpd_connect_file, sizeof(mpd_connect_file));
361   mpd_connect_file.fd = mpd_sk;
362   mpd_connect_file.write_handler = mpd_connected;
363   MPD_STATE(MPD_CONNECTING);
364
365   struct sockaddr_in sin;
366   sin.sin_family = AF_INET;
367   sin.sin_port = htons(6600);
368   sin.sin_addr.s_addr = htonl(0x7f000001);
369   if (connect(mpd_sk, (struct sockaddr *) &sin, sizeof(sin)) < 0)
370     {
371       if (errno == EINPROGRESS)
372         {
373           file_add(&mpd_connect_file);
374           timer_add_rel(&mpd_connect_timer, 5000);
375         }
376       else
377         mpd_error("Unable to connect: %m");
378     }
379   else
380     mpd_connected(NULL);
381 }
382
383 void mpd_init(void)
384 {
385   clist_init(&mpd_cmd_queue);
386   mpd_reply_pool = mp_new(4096);
387
388   mpd_connect_timer.handler = mpd_connect;
389   timer_add_rel(&mpd_connect_timer, 100);
390   mpd_state = MPD_OFFLINE;
391 }