chiark / gitweb /
disobedience survives losing TCP connections better
[disorder] / lib / eclient.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2006, 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20 /** @file lib/eclient.c
21  * @brief Client code for event-driven programs
22  */
23
24 #include <config.h>
25 #include "types.h"
26
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <sys/un.h>
31 #include <string.h>
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <netdb.h>
36 #include <stdlib.h>
37 #include <assert.h>
38 #include <inttypes.h>
39 #include <stddef.h>
40
41 #include "log.h"
42 #include "mem.h"
43 #include "configuration.h"
44 #include "queue.h"
45 #include "eclient.h"
46 #include "charset.h"
47 #include "hex.h"
48 #include "split.h"
49 #include "vector.h"
50 #include "inputline.h"
51 #include "kvp.h"
52 #include "syscalls.h"
53 #include "printf.h"
54 #include "addr.h"
55 #include "authhash.h"
56 #include "table.h"
57 #include "client-common.h"
58
59 /* TODO: more commands */
60
61 /** @brief How often to send data to the server when receiving logs */
62 #define LOG_PROD_INTERVAL 10
63
64 /* Types *********************************************************************/
65
66 /** @brief Client state */
67 enum client_state {
68   state_disconnected,          /**< @brief not connected */
69   state_connecting,            /**< @brief waiting for connect() */
70   state_connected,             /**< @brief connected but not authenticated */
71   state_idle,                  /**< @brief not doing anything */
72   state_cmdresponse,           /**< @brief waiting for command resonse */
73   state_body,                  /**< @brief accumulating body */
74   state_log,                   /**< @brief monitoring log */
75 };
76
77 /** @brief Names for @ref client_state */
78 static const char *const states[] = {
79   "disconnected",
80   "connecting",
81   "connected",
82   "idle",
83   "cmdresponse",
84   "body",
85   "log"
86 };
87
88 struct operation;                       /* forward decl */
89
90 /** @brief Type of an operation callback */
91 typedef void operation_callback(disorder_eclient *c, struct operation *op);
92
93 /** @brief A pending operation.
94  *
95  * This can be either a command or part of the authentication protocol.  In the
96  * former case new commands are appended to the list, in the latter case they
97  * are inserted at the front. */
98 struct operation {
99   struct operation *next;          /**< @brief next operation */
100   char *cmd;                       /**< @brief command to send or 0 */
101   operation_callback *opcallback;  /**< @brief internal completion callback */
102   void (*completed)();             /**< @brief user completion callback or 0 */
103   void *v;                         /**< @brief data for COMPLETED */
104   disorder_eclient *client;        /**< @brief owning client */
105
106   /** @brief true if sent to server
107    *
108    * This is cleared by disorder_eclient_close(), forcing all queued
109    * commands to be transparently resent.
110    */
111   int sent;
112 };
113
114 /** @brief Client structure */
115 struct disorder_eclient {
116   const char *ident;
117   int fd;                               /**< @brief connection to server */
118   enum client_state state;              /**< @brief current state */
119   int authenticated;                    /**< @brief true when authenicated */
120   struct dynstr output;                 /**< @brief output buffer */
121   struct dynstr input;                  /**< @brief input buffer */
122   int eof;                              /**< @brief input buffer is at EOF */
123   const disorder_eclient_callbacks *callbacks; /**< @brief error callbacks */
124   void *u;                              /**< @brief user data */
125   struct operation *ops;                /**< @brief queue of operations */
126   struct operation **opstail;           /**< @brief queue tail */
127   /* accumulated response */
128   int rc;                               /**< @brief response code */
129   char *line;                           /**< @brief complete line */
130   struct vector vec;                    /**< @brief body */
131
132   const disorder_eclient_log_callbacks *log_callbacks;
133   /**< @brief log callbacks
134    *
135    * Once disorder_eclient_log() has been issued this is always set.  When we
136    * re-connect it is checked to re-issue the log command.
137    */
138   void *log_v;                          /**< @brief user data */
139   unsigned long statebits;              /**< @brief latest state */
140
141   time_t last_prod;
142   /**< @brief last time we sent a prod
143    *
144    * When we are receiving log data we send a "prod" byte to the server from
145    * time to time so that we detect broken connections reasonably quickly.  The
146    * server just ignores these bytes.
147    */
148 };
149
150 /* Forward declarations ******************************************************/
151
152 static int start_connect(void *cc,
153                          const struct sockaddr *sa,
154                          socklen_t len,
155                          const char *ident);
156 static void process_line(disorder_eclient *c, char *line);
157 static int start_connect(void *cc,
158                          const struct sockaddr *sa,
159                          socklen_t len,
160                          const char *ident);
161 static void maybe_connected(disorder_eclient *c);
162 static void authbanner_opcallback(disorder_eclient *c,
163                                   struct operation *op);
164 static void authuser_opcallback(disorder_eclient *c,
165                                 struct operation *op);
166 static void complete(disorder_eclient *c);
167 static void send_output(disorder_eclient *c);
168 static void put(disorder_eclient *c, const char *s, size_t n);
169 static void read_input(disorder_eclient *c);
170 static void stash_command(disorder_eclient *c,
171                           int queuejump,
172                           operation_callback *opcallback,
173                           void (*completed)(),
174                           void *v,
175                           const char *cmd,
176                           ...);
177 static void log_opcallback(disorder_eclient *c, struct operation *op);
178 static void logline(disorder_eclient *c, const char *line);
179 static void logentry_completed(disorder_eclient *c, int nvec, char **vec);
180 static void logentry_failed(disorder_eclient *c, int nvec, char **vec);
181 static void logentry_moved(disorder_eclient *c, int nvec, char **vec);
182 static void logentry_playing(disorder_eclient *c, int nvec, char **vec);
183 static void logentry_queue(disorder_eclient *c, int nvec, char **vec);
184 static void logentry_recent_added(disorder_eclient *c, int nvec, char **vec);
185 static void logentry_recent_removed(disorder_eclient *c, int nvec, char **vec);
186 static void logentry_removed(disorder_eclient *c, int nvec, char **vec);
187 static void logentry_scratched(disorder_eclient *c, int nvec, char **vec);
188 static void logentry_state(disorder_eclient *c, int nvec, char **vec);
189 static void logentry_volume(disorder_eclient *c, int nvec, char **vec);
190 static void logentry_rescanned(disorder_eclient *c, int nvec, char **vec);
191
192 /* Tables ********************************************************************/
193
194 /** @brief One possible log entry */
195 struct logentry_handler {
196   const char *name;                     /**< @brief Entry name */
197   int min;                              /**< @brief Minimum arguments */
198   int max;                              /**< @brief Maximum arguments */
199   void (*handler)(disorder_eclient *c,
200                   int nvec,
201                   char **vec);          /**< @brief Handler function */
202 };
203
204 /** @brief Table for parsing log entries */
205 static const struct logentry_handler logentry_handlers[] = {
206 #define LE(X, MIN, MAX) { #X, MIN, MAX, logentry_##X }
207   LE(completed, 1, 1),
208   LE(failed, 2, 2),
209   LE(moved, 1, 1),
210   LE(playing, 1, 2),
211   LE(queue, 2, INT_MAX),
212   LE(recent_added, 2, INT_MAX),
213   LE(recent_removed, 1, 1),
214   LE(removed, 1, 2),
215   LE(rescanned, 0, 0),
216   LE(scratched, 2, 2),
217   LE(state, 1, 1),
218   LE(volume, 2, 2)
219 };
220
221 /* Setup and teardown ********************************************************/
222
223 /** @brief Create a new client
224  *
225  * Does NOT connect the client - connections are made (and re-made) on demand.
226  */
227 disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb,
228                                        void *u) {
229   disorder_eclient *c = xmalloc(sizeof *c);
230   D(("disorder_eclient_new"));
231   c->fd = -1;
232   c->callbacks = cb;
233   c->u = u;
234   c->opstail = &c->ops;
235   vector_init(&c->vec);
236   dynstr_init(&c->input);
237   dynstr_init(&c->output);
238   if(!config->password) {
239     error(0, "no password set");
240     return 0;
241   }
242   return c;
243 }
244
245 /** @brief Disconnect a client
246  * @param c Client to disconnect
247  *
248  * NB that this routine just disconnnects the TCP connection.  It does not
249  * destroy the client!  If you continue to use it then it will attempt to
250  * reconnect.
251  */
252 void disorder_eclient_close(disorder_eclient *c) {
253   struct operation *op;
254
255   D(("disorder_eclient_close"));
256   if(c->fd != -1) {
257     D(("disorder_eclient_close closing fd %d", c->fd));
258     c->callbacks->poll(c->u, c, c->fd, 0);
259     xclose(c->fd);
260     c->fd = -1;
261     c->state = state_disconnected;
262     c->statebits = 0;
263   }
264   c->output.nvec = 0;
265   c->input.nvec = 0;
266   c->eof = 0;
267   c->authenticated = 0;
268   /* We'll need to resend all operations */
269   for(op = c->ops; op; op = op->next)
270     op->sent = 0;
271   /* Drop our use a hint that we're disconnected */
272   if(c->log_callbacks && c->log_callbacks->state)
273     c->log_callbacks->state(c->log_v, c->statebits);
274 }
275
276 /** @brief Return current state */
277 unsigned long disorder_eclient_state(const disorder_eclient *c) {
278   return c->statebits | (c->state > state_connected ? DISORDER_CONNECTED : 0);
279 }
280
281 /* Error reporting ***********************************************************/
282
283 /** @brief called when a connection error occurs
284  *
285  * After this called we will be disconnected (by disorder_eclient_close()),
286  * so there will be a reconnection before any commands can be sent.
287  */
288 static int comms_error(disorder_eclient *c, const char *fmt, ...) {
289   va_list ap;
290   char *s;
291
292   D(("comms_error"));
293   va_start(ap, fmt);
294   byte_xvasprintf(&s, fmt, ap);
295   va_end(ap);
296   disorder_eclient_close(c);
297   c->callbacks->comms_error(c->u, s);
298   return -1;
299 }
300
301 /** @brief called when the server reports an error */
302 static int protocol_error(disorder_eclient *c, struct operation *op,
303                           int code, const char *fmt, ...) {
304   va_list ap;
305   char *s;
306
307   D(("protocol_error"));
308   va_start(ap, fmt);
309   byte_xvasprintf(&s, fmt, ap);
310   va_end(ap);
311   c->callbacks->protocol_error(c->u, op->v, code, s);
312   return -1;
313 }
314
315 /* State machine *************************************************************/
316
317 /** @brief Called when there's something to do
318  * @param c Client
319  * @param mode bitmap of @ref DISORDER_POLL_READ and/or @ref DISORDER_POLL_WRITE.
320  *
321  * This should be called from by your code when the file descriptor is readable
322  * or writable (as requested by the @c poll callback, see @ref
323  * disorder_eclient_callbacks) and in any case from time to time (with @p mode
324  * = 0) to allow for retries to work.
325  */
326 void disorder_eclient_polled(disorder_eclient *c, unsigned mode) {
327   struct operation *op;
328   time_t now;
329   
330   D(("disorder_eclient_polled fd=%d state=%s mode=[%s %s]",
331      c->fd, states[c->state],
332      mode & DISORDER_POLL_READ ? "READ" : "",
333      mode & DISORDER_POLL_WRITE ? "WRITE" : ""));
334   /* The pattern here is to check each possible state in turn and try to
335    * advance (though on error we might go back).  If we advance we leave open
336    * the possibility of falling through to the next state, but we set the mode
337    * bits to 0, to avoid false positives (which matter more in some cases than
338    * others). */
339
340   if(c->state == state_disconnected) {
341     D(("state_disconnected"));
342     with_sockaddr(c, start_connect);
343     /* might now be state_disconnected (on error), state_connecting (slow
344      * connect) or state_connected (fast connect).  If state_disconnected then
345      * we just rely on a periodic callback from the event loop sometime. */
346     mode = 0;
347   }
348
349   if(c->state == state_connecting && mode) {
350     D(("state_connecting"));
351     maybe_connected(c);
352     /* Might be state_disconnected (on error) or state_connected (on success).
353      * In the former case we rely on the event loop for a periodic callback to
354      * retry. */
355     mode = 0;
356   }
357
358   if(c->state == state_connected) {
359     D(("state_connected"));
360     /* We just connected.  Initiate the authentication protocol. */
361     stash_command(c, 1/*queuejump*/, authbanner_opcallback,
362                   0/*completed*/, 0/*v*/, 0/*cmd*/);
363     /* We never stay is state_connected very long.  We could in principle jump
364      * straight to state_cmdresponse since there's actually no command to
365      * send, but that would arguably be cheating. */
366     c->state = state_idle;
367   }
368
369   if(c->state == state_idle) {
370     D(("state_idle"));
371     /* We are connected, and have finished any command we set off, look for
372      * some work to do */
373     if(c->ops) {
374       D(("have ops"));
375       if(c->authenticated) {
376         /* Transmit all unsent operations */
377         for(op = c->ops; op; op = op->next) {
378           if(!op->sent) {
379             put(c, op->cmd, strlen(op->cmd));
380             op->sent = 1;
381           }
382         }
383       } else {
384         /* Just send the head operation */
385         if(c->ops->cmd && !c->ops->sent) {
386           put(c, c->ops->cmd, strlen(c->ops->cmd));
387           c->ops->sent = 1;
388         }
389       }
390       /* Awaiting response for the operation at the head of the list */
391       c->state = state_cmdresponse;
392     } else
393       /* genuinely idle */
394       c->callbacks->report(c->u, 0);
395   }
396
397   /* Queue up a byte to send */
398   if(c->state == state_log
399      && c->output.nvec == 0
400      && time(&now) - c->last_prod > LOG_PROD_INTERVAL) {
401     put(c, "x", 1);
402     c->last_prod = now;
403   }
404   
405   if(c->state == state_cmdresponse
406      || c->state == state_body
407      || c->state == state_log) {
408     D(("state_%s", states[c->state]));
409     /* We are awaiting a response */
410     if(mode & DISORDER_POLL_WRITE) send_output(c);
411     if(mode & DISORDER_POLL_READ) read_input(c);
412     /* There are a couple of reasons we might want to re-enter the state
413      * machine from the top.  state_idle is obvious: there may be further
414      * commands to process.  Re-entering on state_disconnected means that we
415      * immediately retry connection if a comms error occurs during a command.
416      * This is different to the case where a connection fails, where we await a
417      * spontaneous call to initiate the retry. */
418     switch(c->state) {
419     case state_disconnected:            /* lost connection */
420     case state_idle:                    /* completed a command */
421       D(("retrying"));
422       disorder_eclient_polled(c, 0);
423       return;
424     default:
425       break;
426     }
427   }
428   
429   /* Figure out what to set the mode to */
430   switch(c->state) {
431   case state_disconnected:
432     D(("state_disconnected (2)"));
433     /* Probably an error occurred.  Await a retry. */
434     mode = 0;
435     break;
436   case state_connecting:
437     D(("state_connecting (2)"));
438     /* Waiting for connect to complete */
439     mode = DISORDER_POLL_READ|DISORDER_POLL_WRITE;
440     break;
441   case state_connected:
442     D(("state_connected (2)"));
443     assert(!"should never be in state_connected here");
444     break;
445   case state_idle:
446     D(("state_idle (2)"));
447     /* Connected but nothing to do. */
448     mode = 0;
449     break;
450   case state_cmdresponse:
451   case state_body:
452   case state_log:
453     D(("state_%s (2)", states[c->state]));
454     /* Gathering a response.  Wait for input. */
455     mode = DISORDER_POLL_READ;
456     /* Flush any pending output. */
457     if(c->output.nvec) mode |= DISORDER_POLL_WRITE;
458     break;
459   }
460   D(("fd=%d new mode [%s %s]",
461      c->fd,
462      mode & DISORDER_POLL_READ ? "READ" : "",
463      mode & DISORDER_POLL_WRITE ? "WRITE" : ""));
464   if(c->fd != -1) c->callbacks->poll(c->u, c, c->fd, mode);
465 }
466
467 /** @brief Called to start connecting */
468 static int start_connect(void *cc,
469                          const struct sockaddr *sa,
470                          socklen_t len,
471                          const char *ident) {
472   disorder_eclient *c = cc;
473
474   D(("start_connect"));
475   c->ident = xstrdup(ident);
476   if(c->fd != -1) {
477     xclose(c->fd);
478     c->fd = -1;
479   }
480   if((c->fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
481     return comms_error(c, "socket: %s", strerror(errno));
482   c->eof = 0;
483   nonblock(c->fd);
484   cloexec(c->fd);
485   if(connect(c->fd, sa, len) < 0) {
486     switch(errno) {
487     case EINTR:
488     case EINPROGRESS:
489       c->state = state_connecting;
490       /* We are called from _polled so the state machine will get to do its
491        * thing */
492       return 0;
493     default:
494       /* Signal the error to the caller. */
495       return comms_error(c, "connecting to %s: %s", ident, strerror(errno));
496     }
497   } else
498     c->state = state_connected;
499   return 0;
500 }
501
502 /** @brief Called when poll triggers while waiting for a connection */
503 static void maybe_connected(disorder_eclient *c) {
504   /* We either connected, or got an error. */
505   int err;
506   socklen_t len = sizeof err;
507   
508   D(("maybe_connected"));
509   /* Work around over-enthusiastic error slippage */
510   if(getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
511     err = errno;
512   if(err) {
513     /* The connection failed */
514     comms_error(c, "connecting to %s: %s", c->ident, strerror(err));
515     /* sets state_disconnected */
516   } else {
517     char *r;
518     
519     /* The connection succeeded */
520     c->state = state_connected;
521     byte_xasprintf(&r, "connected to %s", c->ident);
522     c->callbacks->report(c->u, r);
523     /* If this is a log client we expect to get a bunch of updates from the
524      * server straight away */
525   }
526 }
527
528 /* Authentication ************************************************************/
529
530 static void authbanner_opcallback(disorder_eclient *c,
531                                   struct operation *op) {
532   size_t nonce_len;
533   const unsigned char *nonce;
534   const char *res;
535   char **rvec;
536   int nrvec;
537   const char *algo = "SHA1";
538   
539   D(("authbanner_opcallback"));
540   if(c->rc / 100 != 2
541      || !(rvec = split(c->line + 4, &nrvec, SPLIT_QUOTES, 0, 0))
542      || nrvec < 1) {
543     /* Banner told us to go away, or was malformed.  We cannot proceed. */
544     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
545     disorder_eclient_close(c);
546     return;
547   }
548   if(nrvec > 1) {
549     algo = *rvec++;
550     --nrvec;
551   }
552   nonce = unhex(rvec[0], &nonce_len);
553   res = authhash(nonce, nonce_len, config->password, algo);
554   if(!res) {
555     protocol_error(c, op, c->rc, "%s: unknown authentication algorithm '%s'",
556                    c->ident, algo);
557     disorder_eclient_close(c);
558     return;
559   }
560   stash_command(c, 1/*queuejump*/, authuser_opcallback, 0/*completed*/, 0/*v*/,
561                 "user", quoteutf8(config->username), quoteutf8(res),
562                 (char *)0);
563 }
564
565 static void authuser_opcallback(disorder_eclient *c,
566                                 struct operation *op) {
567   char *r;
568
569   D(("authuser_opcallback"));
570   if(c->rc / 100 != 2) {
571     /* Wrong password or something.  We cannot proceed. */
572     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
573     disorder_eclient_close(c);
574     return;
575   }
576   /* OK, we're authenticated now. */
577   c->authenticated = 1;
578   byte_xasprintf(&r, "authenticated with %s", c->ident);
579   c->callbacks->report(c->u, r);
580   if(c->log_callbacks && !(c->ops && c->ops->opcallback == log_opcallback))
581     /* We are a log client, switch to logging mode */
582     stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, c->log_v,
583                   "log", (char *)0);
584 }
585
586 /* Output ********************************************************************/
587
588 /* Chop N bytes off the front of a dynstr */
589 static void consume(struct dynstr *d, int n) {
590   D(("consume %d", n));
591   assert(d->nvec >= n);
592   memmove(d->vec, d->vec + n, d->nvec - n);
593   d->nvec -= n;
594 }
595
596 /* Write some bytes */
597 static void put(disorder_eclient *c, const char *s, size_t n) {
598   D(("put %d %.*s", c->fd, (int)n, s));
599   dynstr_append_bytes(&c->output, s, n);
600 }
601
602 /* Called when we can write to our FD, or at any other time */
603 static void send_output(disorder_eclient *c) {
604   int n;
605
606   D(("send_output %d bytes pending", c->output.nvec));
607   if(c->state > state_connecting && c->output.nvec) {
608     n = write(c->fd, c->output.vec, c->output.nvec);
609     if(n < 0) {
610       switch(errno) {
611       case EINTR:
612       case EAGAIN:
613         break;
614       default:
615         comms_error(c, "writing to %s: %s", c->ident, strerror(errno));
616         break;
617       }
618     } else
619       consume(&c->output, n);
620   }
621 }
622
623 /* Input *********************************************************************/
624
625 /* Called when c->fd might be readable, or at any other time */
626 static void read_input(disorder_eclient *c) {
627   char *nl;
628   int n;
629   char buffer[512];
630
631   D(("read_input in state %s", states[c->state]));
632   if(c->state <= state_connected) return; /* ignore bogus calls */
633   /* read some more input */
634   n = read(c->fd, buffer, sizeof buffer);
635   if(n < 0) {
636     switch(errno) {
637     case EINTR:
638     case EAGAIN:
639       break;
640     default:
641       comms_error(c, "reading from %s: %s", c->ident, strerror(errno));
642       break;
643     }
644     return;                             /* no new input to process */
645   } else if(n) {
646     D(("read %d bytes: [%.*s]", n, n, buffer));
647     dynstr_append_bytes(&c->input, buffer, n);
648   } else
649     c->eof = 1;
650   /* might have more than one line to process */
651   while(c->state > state_connecting
652         && (nl = memchr(c->input.vec, '\n', c->input.nvec))) {
653     process_line(c, xstrndup(c->input.vec, nl - c->input.vec));
654     /* we might have disconnected along the way, which zogs the input buffer */
655     if(c->state > state_connecting)
656       consume(&c->input, (nl - c->input.vec) + 1);
657   }
658   if(c->eof) {
659     comms_error(c, "reading from %s: server disconnected", c->ident);
660     c->authenticated = 0;
661   }
662 }
663
664 /* called with a line that has just been read */
665 static void process_line(disorder_eclient *c, char *line) {
666   D(("process_line %d [%s]", c->fd, line));
667   switch(c->state) {
668   case state_cmdresponse:
669     /* This is the first line of a response */
670     if(!(line[0] >= '0' && line[0] <= '9'
671          && line[1] >= '0' && line[1] <= '9'
672          && line[2] >= '0' && line[2] <= '9'
673          && line[3] == ' '))
674       fatal(0, "invalid response from server: %s", line);
675     c->rc = (line[0] * 10 + line[1]) * 10 + line[2] - 111 * '0';
676     c->line = line;
677     switch(c->rc % 10) {
678     case 3:
679       /* We need to collect the body. */
680       c->state = state_body;
681       vector_init(&c->vec);
682       break;
683     case 4:
684       assert(c->log_callbacks != 0);
685       if(c->log_callbacks->connected)
686         c->log_callbacks->connected(c->log_v);
687       c->state = state_log;
688       break;
689     default:
690       /* We've got the whole response.  Go into the idle state so the state
691        * machine knows we're done and then call the operation callback. */
692       complete(c);
693       break;
694     }
695     break;
696   case state_body:
697     if(strcmp(line, ".")) {
698       /* A line from the body */
699       vector_append(&c->vec, line + (line[0] == '.'));
700     } else {
701       /* End of the body. */
702       vector_terminate(&c->vec);
703       complete(c);
704     }
705     break;
706   case state_log:
707     if(strcmp(line, ".")) {
708       logline(c, line + (line[0] == '.'));
709     } else 
710       complete(c);
711     break;
712   default:
713     assert(!"wrong state for location");
714     break;
715   }
716 }
717
718 /* Called when an operation completes */
719 static void complete(disorder_eclient *c) {
720   struct operation *op;
721
722   D(("complete"));
723   /* Pop the operation off the queue */
724   op = c->ops;
725   c->ops = op->next;
726   if(c->opstail == &op->next)
727     c->opstail = &c->ops;
728   /* If we've pipelined a command ahead then we go straight to cmdresponser.
729    * Otherwise we go to idle, which will arrange further sends. */
730   c->state = c->ops && c->ops->sent ? state_cmdresponse : state_idle;
731   op->opcallback(c, op);
732   /* Note that we always call the opcallback even on error, though command
733    * opcallbacks generally always do the same error handling, i.e. just call
734    * protocol_error().  It's the auth* opcallbacks that have different
735    * behaviour. */
736 }
737
738 /* Operation setup ***********************************************************/
739
740 static void stash_command_vector(disorder_eclient *c,
741                                  int queuejump,
742                                  operation_callback *opcallback,
743                                  void (*completed)(),
744                                  void *v,
745                                  int ncmd,
746                                  char **cmd) {
747   struct operation *op = xmalloc(sizeof *op);
748   struct dynstr d;
749   int n;
750
751   if(cmd) {
752     dynstr_init(&d);
753     for(n = 0; n < ncmd; ++n) {
754       if(n)
755         dynstr_append(&d, ' ');
756       dynstr_append_string(&d, quoteutf8(cmd[n]));
757     }
758     dynstr_append(&d, '\n');
759     dynstr_terminate(&d);
760     op->cmd = d.vec;
761   } else
762     op->cmd = 0;                        /* usually, awaiting challenge */
763   op->opcallback = opcallback;
764   op->completed = completed;
765   op->v = v;
766   op->next = 0;
767   op->client = c;
768   assert(op->sent == 0);
769   if(queuejump) {
770     /* Authentication operations jump the queue of useful commands */
771     op->next = c->ops;
772     c->ops = op;
773     if(c->opstail == &c->ops)
774       c->opstail = &op->next;
775     for(op = c->ops; op; op = op->next)
776       assert(!op->sent);
777   } else {
778     *c->opstail = op;
779     c->opstail = &op->next;
780   }
781 }
782
783 static void vstash_command(disorder_eclient *c,
784                            int queuejump,
785                            operation_callback *opcallback,
786                            void (*completed)(),
787                            void *v,
788                            const char *cmd, va_list ap) {
789   char *arg;
790   struct vector vec;
791
792   D(("vstash_command %s", cmd ? cmd : "NULL"));
793   if(cmd) {
794     vector_init(&vec);
795     vector_append(&vec, (char *)cmd);
796     while((arg = va_arg(ap, char *)))
797       vector_append(&vec, arg);
798     stash_command_vector(c, queuejump, opcallback, completed, v, 
799                          vec.nvec, vec.vec);
800   } else
801     stash_command_vector(c, queuejump, opcallback, completed, v, 0, 0);
802 }
803
804 static void stash_command(disorder_eclient *c,
805                           int queuejump,
806                           operation_callback *opcallback,
807                           void (*completed)(),
808                           void *v,
809                           const char *cmd,
810                           ...) {
811   va_list ap;
812
813   va_start(ap, cmd);
814   vstash_command(c, queuejump, opcallback, completed, v, cmd, ap);
815   va_end(ap);
816 }
817
818 /* Command support ***********************************************************/
819
820 /* for commands with a simple string response */ 
821 static void string_response_opcallback(disorder_eclient *c,
822                                        struct operation *op) {
823   D(("string_response_callback"));
824   if(c->rc / 100 == 2) {
825     if(op->completed)
826       ((disorder_eclient_string_response *)op->completed)(op->v, c->line + 4);
827   } else
828     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
829 }
830
831 /* for commands with a simple integer response */ 
832 static void integer_response_opcallback(disorder_eclient *c,
833                                         struct operation *op) {
834   D(("string_response_callback"));
835   if(c->rc / 100 == 2) {
836     if(op->completed)
837       ((disorder_eclient_integer_response *)op->completed)
838         (op->v, strtol(c->line + 4, 0, 10));
839   } else
840     protocol_error(c, op,  c->rc, "%s: %s", c->ident, c->line);
841 }
842
843 /* for commands with no response */
844 static void no_response_opcallback(disorder_eclient *c,
845                                    struct operation *op) {
846   D(("no_response_callback"));
847   if(c->rc / 100 == 2) {
848     if(op->completed)
849       ((disorder_eclient_no_response *)op->completed)(op->v);
850   } else
851     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
852 }
853
854 /* error callback for queue_unmarshall */
855 static void eclient_queue_error(const char *msg,
856                                 void *u) {
857   struct operation *op = u;
858
859   protocol_error(op->client, op, -1, "error parsing queue entry: %s", msg);
860 }
861
862 /* for commands that expect a queue dump */
863 static void queue_response_opcallback(disorder_eclient *c,
864                                       struct operation *op) {
865   int n;
866   struct queue_entry *q, *qh = 0, **qtail = &qh, *qlast = 0;
867   
868   D(("queue_response_callback"));
869   if(c->rc / 100 == 2) {
870     /* parse the queue */
871     for(n = 0; n < c->vec.nvec; ++n) {
872       q = xmalloc(sizeof *q);
873       D(("queue_unmarshall %s", c->vec.vec[n]));
874       if(!queue_unmarshall(q, c->vec.vec[n], eclient_queue_error, op)) {
875         q->prev = qlast;
876         *qtail = q;
877         qtail = &q->next;
878         qlast = q;
879       }
880     }
881     if(op->completed)
882       ((disorder_eclient_queue_response *)op->completed)(op->v, qh);
883   } else
884     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
885
886
887 /* for 'playing' */
888 static void playing_response_opcallback(disorder_eclient *c,
889                                         struct operation *op) {
890   struct queue_entry *q;
891
892   D(("playing_response_callback"));
893   if(c->rc / 100 == 2) {
894     switch(c->rc % 10) {
895     case 2:
896       if(queue_unmarshall(q = xmalloc(sizeof *q), c->line + 4,
897                           eclient_queue_error, c))
898         return;
899       break;
900     case 9:
901       q = 0;
902       break;
903     default:
904       protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
905       return;
906     }
907     if(op->completed)
908       ((disorder_eclient_queue_response *)op->completed)(op->v, q);
909   } else
910     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
911 }
912
913 /* for commands that expect a list of some sort */
914 static void list_response_opcallback(disorder_eclient *c,
915                                      struct operation *op) {
916   D(("list_response_callback"));
917   if(c->rc / 100 == 2) {
918     if(op->completed)
919       ((disorder_eclient_list_response *)op->completed)(op->v,
920                                                         c->vec.nvec,
921                                                         c->vec.vec);
922   } else
923     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
924 }
925
926 /* for volume */
927 static void volume_response_opcallback(disorder_eclient *c,
928                                        struct operation *op) {
929   int l, r;
930
931   D(("volume_response_callback"));
932   if(c->rc / 100 == 2) {
933     if(op->completed) {
934       if(sscanf(c->line + 4, "%d %d", &l, &r) != 2 || l < 0 || r < 0)
935         protocol_error(c, op, -1, "%s: invalid volume response: %s",
936                        c->ident, c->line);
937       else
938         ((disorder_eclient_volume_response *)op->completed)(op->v, l, r);
939     }
940   } else
941     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
942 }
943
944 static int simple(disorder_eclient *c,
945                   operation_callback *opcallback,
946                   void (*completed)(),
947                   void *v,
948                   const char *cmd, ...) {
949   va_list ap;
950
951   va_start(ap, cmd);
952   vstash_command(c, 0/*queuejump*/, opcallback, completed, v, cmd, ap);
953   va_end(ap);
954   /* Give the state machine a kick, since we might be in state_idle */
955   disorder_eclient_polled(c, 0);
956   return 0;
957 }
958
959 /* Commands ******************************************************************/
960  
961 int disorder_eclient_version(disorder_eclient *c,
962                              disorder_eclient_string_response *completed,
963                              void *v) {
964   return simple(c, string_response_opcallback, (void (*)())completed, v,
965                 "version", (char *)0);
966 }
967
968 int disorder_eclient_namepart(disorder_eclient *c,
969                               disorder_eclient_string_response *completed,
970                               const char *track,
971                               const char *context,
972                               const char *part,
973                               void *v) {
974   return simple(c, string_response_opcallback, (void (*)())completed, v,
975                 "part", track, context, part, (char *)0);
976 }
977
978 int disorder_eclient_play(disorder_eclient *c,
979                           const char *track,
980                           disorder_eclient_no_response *completed,
981                           void *v) {
982   return simple(c, no_response_opcallback, (void (*)())completed, v,
983                 "play", track, (char *)0);
984 }
985
986 int disorder_eclient_pause(disorder_eclient *c,
987                            disorder_eclient_no_response *completed,
988                            void *v) {
989   return simple(c, no_response_opcallback, (void (*)())completed, v,
990                 "pause", (char *)0);
991 }
992
993 int disorder_eclient_resume(disorder_eclient *c,
994                             disorder_eclient_no_response *completed,
995                             void *v) {
996   return simple(c, no_response_opcallback, (void (*)())completed, v,
997                 "resume", (char *)0);
998 }
999
1000 int disorder_eclient_scratch(disorder_eclient *c,
1001                              const char *id,
1002                              disorder_eclient_no_response *completed,
1003                              void *v) {
1004   return simple(c, no_response_opcallback, (void (*)())completed, v,
1005                 "scratch", id, (char *)0);
1006 }
1007
1008 int disorder_eclient_scratch_playing(disorder_eclient *c,
1009                                      disorder_eclient_no_response *completed,
1010                                      void *v) {
1011   return disorder_eclient_scratch(c, 0, completed, v);
1012 }
1013
1014 int disorder_eclient_remove(disorder_eclient *c,
1015                             const char *id,
1016                             disorder_eclient_no_response *completed,
1017                             void *v) {
1018   return simple(c, no_response_opcallback, (void (*)())completed, v,
1019                 "remove", id, (char *)0);
1020 }
1021
1022 int disorder_eclient_moveafter(disorder_eclient *c,
1023                                const char *target,
1024                                int nids,
1025                                const char **ids,
1026                                disorder_eclient_no_response *completed,
1027                                void *v) {
1028   struct vector vec;
1029   int n;
1030
1031   vector_init(&vec);
1032   vector_append(&vec, (char *)"moveafter");
1033   vector_append(&vec, (char *)target);
1034   for(n = 0; n < nids; ++n)
1035     vector_append(&vec, (char *)ids[n]);
1036   stash_command_vector(c, 0/*queuejump*/, no_response_opcallback, completed, v,
1037                        vec.nvec, vec.vec);
1038   disorder_eclient_polled(c, 0);
1039   return 0;
1040 }
1041
1042 int disorder_eclient_recent(disorder_eclient *c,
1043                             disorder_eclient_queue_response *completed,
1044                             void *v) {
1045   return simple(c, queue_response_opcallback, (void (*)())completed, v,
1046                 "recent", (char *)0);
1047 }
1048
1049 int disorder_eclient_queue(disorder_eclient *c,
1050                             disorder_eclient_queue_response *completed,
1051                             void *v) {
1052   return simple(c, queue_response_opcallback, (void (*)())completed, v,
1053                 "queue", (char *)0);
1054 }
1055
1056 int disorder_eclient_files(disorder_eclient *c,
1057                            disorder_eclient_list_response *completed,
1058                            const char *dir,
1059                            const char *re,
1060                            void *v) {
1061   return simple(c, list_response_opcallback, (void (*)())completed, v,
1062                 "files", dir, re, (char *)0);
1063 }
1064
1065 int disorder_eclient_dirs(disorder_eclient *c,
1066                           disorder_eclient_list_response *completed,
1067                           const char *dir,
1068                           const char *re,
1069                           void *v) {
1070   return simple(c, list_response_opcallback, (void (*)())completed, v,
1071                 "dirs", dir, re, (char *)0);
1072 }
1073
1074 int disorder_eclient_playing(disorder_eclient *c,
1075                              disorder_eclient_queue_response *completed,
1076                              void *v) {
1077   return simple(c, playing_response_opcallback, (void (*)())completed, v,
1078                 "playing", (char *)0);
1079 }
1080
1081 int disorder_eclient_length(disorder_eclient *c,
1082                             disorder_eclient_integer_response *completed,
1083                             const char *track,
1084                             void *v) {
1085   return simple(c, integer_response_opcallback, (void (*)())completed, v,
1086                 "length", track, (char *)0);
1087 }
1088
1089 int disorder_eclient_volume(disorder_eclient *c,
1090                             disorder_eclient_volume_response *completed,
1091                             int l, int r,
1092                             void *v) {
1093   char sl[64], sr[64];
1094
1095   if(l < 0 && r < 0) {
1096     return simple(c, volume_response_opcallback, (void (*)())completed, v,
1097                   "volume", (char *)0);
1098   } else if(l >= 0 && r >= 0) {
1099     assert(l <= 100);
1100     assert(r <= 100);
1101     byte_snprintf(sl, sizeof sl, "%d", l);
1102     byte_snprintf(sr, sizeof sr, "%d", r);
1103     return simple(c, volume_response_opcallback, (void (*)())completed, v,
1104                   "volume", sl, sr, (char *)0);
1105   } else {
1106     assert(!"invalid arguments to disorder_eclient_volume");
1107     return -1;                          /* gcc is being dim */
1108   }
1109 }
1110
1111 int disorder_eclient_enable(disorder_eclient *c,
1112                             disorder_eclient_no_response *completed,
1113                             void *v) {
1114   return simple(c, no_response_opcallback, (void (*)())completed, v,
1115                 "enable", (char *)0);
1116 }
1117
1118 int disorder_eclient_disable(disorder_eclient *c,
1119                              disorder_eclient_no_response *completed,
1120                              void *v){
1121   return simple(c, no_response_opcallback, (void (*)())completed, v,
1122                 "disable", (char *)0);
1123 }
1124
1125 int disorder_eclient_random_enable(disorder_eclient *c,
1126                                    disorder_eclient_no_response *completed,
1127                                    void *v){
1128   return simple(c, no_response_opcallback, (void (*)())completed, v,
1129                 "random-enable", (char *)0);
1130 }
1131
1132 int disorder_eclient_random_disable(disorder_eclient *c,
1133                                     disorder_eclient_no_response *completed,
1134                                     void *v){
1135   return simple(c, no_response_opcallback, (void (*)())completed, v,
1136                 "random-disable", (char *)0);
1137 }
1138
1139 int disorder_eclient_get(disorder_eclient *c,
1140                          disorder_eclient_string_response *completed,
1141                          const char *track, const char *pref,
1142                          void *v) {
1143   return simple(c, string_response_opcallback, (void (*)())completed, v, 
1144                 "get", track, pref, (char *)0);
1145 }
1146
1147 int disorder_eclient_set(disorder_eclient *c,
1148                          disorder_eclient_no_response *completed,
1149                          const char *track, const char *pref, 
1150                          const char *value,
1151                          void *v) {
1152   return simple(c, no_response_opcallback, (void (*)())completed, v, 
1153                 "set", track, pref, value, (char *)0);
1154 }
1155
1156 int disorder_eclient_unset(disorder_eclient *c,
1157                            disorder_eclient_no_response *completed,
1158                            const char *track, const char *pref, 
1159                            void *v) {
1160   return simple(c, no_response_opcallback, (void (*)())completed, v, 
1161                 "unset", track, pref, (char *)0);
1162 }
1163
1164 int disorder_eclient_resolve(disorder_eclient *c,
1165                              disorder_eclient_string_response *completed,
1166                              const char *track,
1167                              void *v) {
1168   return simple(c, string_response_opcallback,  (void (*)())completed, v, 
1169                 "resolve", track, (char *)0);
1170 }
1171
1172 int disorder_eclient_search(disorder_eclient *c,
1173                             disorder_eclient_list_response *completed,
1174                             const char *terms,
1175                             void *v) {
1176   if(!split(terms, 0, SPLIT_QUOTES, 0, 0)) return -1;
1177   return simple(c, list_response_opcallback, (void (*)())completed, v,
1178                 "search", terms, (char *)0);
1179 }
1180
1181 int disorder_eclient_nop(disorder_eclient *c,
1182                          disorder_eclient_no_response *completed,
1183                          void *v) {
1184   return simple(c, no_response_opcallback, (void (*)())completed, v, 
1185                 "nop", (char *)0);
1186 }
1187
1188 /** @brief Get the last @p max added tracks
1189  * @param c Client
1190  * @param completed Called with list
1191  * @param max Number of tracks to get, 0 for all
1192  * @param v Passed to @p completed
1193  *
1194  * The first track in the list is the most recently added.
1195  */
1196 int disorder_eclient_new_tracks(disorder_eclient *c,
1197                                 disorder_eclient_list_response *completed,
1198                                 int max,
1199                                 void *v) {
1200   char limit[32];
1201
1202   sprintf(limit, "%d", max);
1203   return simple(c, list_response_opcallback, (void (*)())completed, v,
1204                 "new", limit, (char *)0);
1205 }
1206
1207 static void rtp_response_opcallback(disorder_eclient *c,
1208                                     struct operation *op) {
1209   D(("rtp_response_opcallback"));
1210   if(c->rc / 100 == 2) {
1211     if(op->completed) {
1212       int nvec;
1213       char **vec = split(c->line + 4, &nvec, SPLIT_QUOTES, 0, 0);
1214
1215       ((disorder_eclient_list_response *)op->completed)(op->v, nvec, vec);
1216     }
1217   } else
1218     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
1219 }
1220
1221 /** @brief Determine the RTP target address
1222  * @param c Client
1223  * @param completed Called with address details
1224  * @param v Passed to @p completed
1225  *
1226  * The address details will be two elements, the first being the hostname and
1227  * the second the service (port).
1228  */
1229 int disorder_eclient_rtp_address(disorder_eclient *c,
1230                                  disorder_eclient_list_response *completed,
1231                                  void *v) {
1232   return simple(c, rtp_response_opcallback, (void (*)())completed, v,
1233                 "rtp-address", (char *)0);
1234 }
1235
1236 /* Log clients ***************************************************************/
1237
1238 /** @brief Monitor the server log
1239  * @param c Client
1240  * @param callbacks Functions to call when anything happens
1241  * @param v Passed to @p callbacks functions
1242  *
1243  * Once a client is being used for logging it cannot be used for anything else.
1244  * There is magic in authuser_opcallback() to re-submit the @c log command
1245  * after reconnection.
1246  *
1247  * NB that the @c state callback may be called from within this function,
1248  * i.e. not solely later on from the event loop callback.
1249  */
1250 int disorder_eclient_log(disorder_eclient *c,
1251                          const disorder_eclient_log_callbacks *callbacks,
1252                          void *v) {
1253   if(c->log_callbacks) return -1;
1254   c->log_callbacks = callbacks;
1255   c->log_v = v;
1256   /* Repoort initial state */
1257   if(c->log_callbacks->state)
1258     c->log_callbacks->state(c->log_v, c->statebits);
1259   stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, v,
1260                 "log", (char *)0);
1261   return 0;
1262 }
1263
1264 /* If we get here we've stopped being a log client */
1265 static void log_opcallback(disorder_eclient *c,
1266                            struct operation attribute((unused)) *op) {
1267   D(("log_opcallback"));
1268   c->log_callbacks = 0;
1269   c->log_v = 0;
1270 }
1271
1272 /* error callback for log line parsing */
1273 static void logline_error(const char *msg, void *u) {
1274   disorder_eclient *c = u;
1275   protocol_error(c, c->ops, -1, "error parsing log line: %s", msg);
1276 }
1277
1278 /* process a single log line */
1279 static void logline(disorder_eclient *c, const char *line) {
1280   int nvec, n;
1281   char **vec;
1282   uintmax_t when;
1283
1284   D(("logline [%s]", line));
1285   vec = split(line, &nvec, SPLIT_QUOTES, logline_error, c);
1286   if(nvec < 2) return;                  /* probably an error, already
1287                                          * reported */
1288   if(sscanf(vec[0], "%"SCNxMAX, &when) != 1) {
1289     /* probably the wrong side of a format change */
1290     protocol_error(c, c->ops, -1, "invalid log timestamp '%s'", vec[0]);
1291     return;
1292   }
1293   /* TODO: do something with the time */
1294   n = TABLE_FIND(logentry_handlers, struct logentry_handler, name, vec[1]);
1295   if(n < 0) return;                     /* probably a future command */
1296   vec += 2;
1297   nvec -= 2;
1298   if(nvec < logentry_handlers[n].min || nvec > logentry_handlers[n].max)
1299     return;
1300   logentry_handlers[n].handler(c, nvec, vec);
1301 }
1302
1303 static void logentry_completed(disorder_eclient *c,
1304                                int attribute((unused)) nvec, char **vec) {
1305   if(!c->log_callbacks->completed) return;
1306   c->statebits &= ~DISORDER_PLAYING;
1307   c->log_callbacks->completed(c->log_v, vec[0]);
1308   if(c->log_callbacks->state)
1309     c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
1310 }
1311
1312 static void logentry_failed(disorder_eclient *c,
1313                             int attribute((unused)) nvec, char **vec) {
1314   if(!c->log_callbacks->failed)return;
1315   c->statebits &= ~DISORDER_PLAYING;
1316   c->log_callbacks->failed(c->log_v, vec[0], vec[1]);
1317   if(c->log_callbacks->state)
1318     c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
1319 }
1320
1321 static void logentry_moved(disorder_eclient *c,
1322                            int attribute((unused)) nvec, char **vec) {
1323   if(!c->log_callbacks->moved) return;
1324   c->log_callbacks->moved(c->log_v, vec[0]);
1325 }
1326
1327 static void logentry_playing(disorder_eclient *c,
1328                              int attribute((unused)) nvec, char **vec) {
1329   if(!c->log_callbacks->playing) return;
1330   c->statebits |= DISORDER_PLAYING;
1331   c->log_callbacks->playing(c->log_v, vec[0], vec[1]);
1332   if(c->log_callbacks->state)
1333     c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
1334 }
1335
1336 static void logentry_queue(disorder_eclient *c,
1337                            int attribute((unused)) nvec, char **vec) {
1338   struct queue_entry *q;
1339
1340   if(!c->log_callbacks->completed) return;
1341   q = xmalloc(sizeof *q);
1342   if(queue_unmarshall_vec(q, nvec, vec, eclient_queue_error, c))
1343     return;                             /* bogus */
1344   c->log_callbacks->queue(c->log_v, q);
1345 }
1346
1347 static void logentry_recent_added(disorder_eclient *c,
1348                                   int attribute((unused)) nvec, char **vec) {
1349   struct queue_entry *q;
1350
1351   if(!c->log_callbacks->recent_added) return;
1352   q = xmalloc(sizeof *q);
1353   if(queue_unmarshall_vec(q, nvec, vec, eclient_queue_error, c))
1354     return;                           /* bogus */
1355   c->log_callbacks->recent_added(c->log_v, q);
1356 }
1357
1358 static void logentry_recent_removed(disorder_eclient *c,
1359                                     int attribute((unused)) nvec, char **vec) {
1360   if(!c->log_callbacks->recent_removed) return;
1361   c->log_callbacks->recent_removed(c->log_v, vec[0]);
1362 }
1363
1364 static void logentry_removed(disorder_eclient *c,
1365                              int attribute((unused)) nvec, char **vec) {
1366   if(!c->log_callbacks->removed) return;
1367   c->log_callbacks->removed(c->log_v, vec[0], vec[1]);
1368 }
1369
1370 static void logentry_rescanned(disorder_eclient *c,
1371                                int attribute((unused)) nvec,
1372                                char attribute((unused)) **vec) {
1373   if(!c->log_callbacks->rescanned) return;
1374   c->log_callbacks->rescanned(c->log_v);
1375 }
1376
1377 static void logentry_scratched(disorder_eclient *c,
1378                                int attribute((unused)) nvec, char **vec) {
1379   if(!c->log_callbacks->scratched) return;
1380   c->statebits &= ~DISORDER_PLAYING;
1381   c->log_callbacks->scratched(c->log_v, vec[0], vec[1]);
1382   if(c->log_callbacks->state)
1383     c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
1384 }
1385
1386 static const struct {
1387   unsigned long bit;
1388   const char *enable;
1389   const char *disable;
1390 } statestrings[] = {
1391   { DISORDER_PLAYING_ENABLED, "enable_play", "disable_play" },
1392   { DISORDER_RANDOM_ENABLED, "enable_random", "disable_random" },
1393   { DISORDER_TRACK_PAUSED, "pause", "resume" },
1394   { DISORDER_PLAYING, "playing", "completed" },
1395   { DISORDER_PLAYING, 0, "scratched" },
1396   { DISORDER_PLAYING, 0, "failed" },
1397 };
1398 #define NSTATES (int)(sizeof statestrings / sizeof *statestrings)
1399
1400 static void logentry_state(disorder_eclient *c,
1401                            int attribute((unused)) nvec, char **vec) {
1402   int n;
1403
1404   for(n = 0; n < NSTATES; ++n)
1405     if(statestrings[n].enable && !strcmp(vec[0], statestrings[n].enable)) {
1406       c->statebits |= statestrings[n].bit;
1407       break;
1408     } else if(statestrings[n].disable && !strcmp(vec[0], statestrings[n].disable)) {
1409       c->statebits &= ~statestrings[n].bit;
1410       break;
1411     }
1412   if(!c->log_callbacks->state) return;
1413   c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED);
1414 }
1415
1416 static void logentry_volume(disorder_eclient *c,
1417                             int attribute((unused)) nvec, char **vec) {
1418   long l, r;
1419
1420   if(!c->log_callbacks->volume) return;
1421   if(xstrtol(&l, vec[0], 0, 10)
1422      || xstrtol(&r, vec[1], 0, 10)
1423      || l < 0 || l > INT_MAX
1424      || r < 0 || r > INT_MAX)
1425     return;                             /* bogus */
1426   c->log_callbacks->volume(c->log_v, (int)l, (int)r);
1427 }
1428
1429 /** @brief Convert @p statebits to a string */
1430 char *disorder_eclient_interpret_state(unsigned long statebits) {
1431   struct dynstr d[1];
1432   size_t n;
1433
1434   static const struct {
1435     unsigned long bit;
1436     const char *name;
1437   } bits[] = {
1438     { DISORDER_PLAYING_ENABLED, "playing_enabled" },
1439     { DISORDER_RANDOM_ENABLED, "random_enabled" },
1440     { DISORDER_TRACK_PAUSED, "track_paused" },
1441     { DISORDER_PLAYING, "playing" },
1442     { DISORDER_CONNECTED, "connected" },
1443   };
1444 #define NBITS (sizeof bits / sizeof *bits)
1445
1446   dynstr_init(d);
1447   if(!statebits)
1448     dynstr_append(d, '0');
1449   for(n = 0; n < NBITS; ++n)
1450     if(statebits & bits[n].bit) {
1451       if(d->nvec)
1452         dynstr_append(d, '|');
1453       dynstr_append_string(d, bits[n].name);
1454       statebits ^= bits[n].bit;
1455     }
1456   if(statebits) {
1457     char s[20];
1458
1459     if(d->nvec)
1460       dynstr_append(d, '|');
1461     sprintf(s, "%#lx", statebits);
1462     dynstr_append_string(d, s);
1463   }
1464   dynstr_terminate(d);
1465   return d->vec;
1466 }
1467
1468 /*
1469 Local Variables:
1470 c-basic-offset:2
1471 comment-column:40
1472 fill-column:79
1473 indent-tabs-mode:nil
1474 End:
1475 */