chiark / gitweb /
Grey out edit playlists menu item if server does not appear to support
[disorder] / lib / client.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004-2008 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/client.c
21  * @brief Simple C client
22  *
23  * See @ref lib/eclient.c for an asynchronous-capable client
24  * implementation.
25  */
26
27 #include "common.h"
28
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <sys/un.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <netdb.h>
36 #include <pcre.h>
37
38 #include "log.h"
39 #include "mem.h"
40 #include "queue.h"
41 #include "client.h"
42 #include "charset.h"
43 #include "hex.h"
44 #include "split.h"
45 #include "vector.h"
46 #include "inputline.h"
47 #include "kvp.h"
48 #include "syscalls.h"
49 #include "printf.h"
50 #include "sink.h"
51 #include "addr.h"
52 #include "authhash.h"
53 #include "client-common.h"
54 #include "rights.h"
55 #include "trackdb.h"
56 #include "kvp.h"
57
58 /** @brief Client handle contents */
59 struct disorder_client {
60   /** @brief Stream to read from */
61   FILE *fpin;
62   /** @brief Stream to write to */
63   FILE *fpout;
64   /** @brief Peer description */
65   char *ident;
66   /** @brief Username */
67   char *user;
68   /** @brief Report errors to @c stderr */
69   int verbose;
70   /** @brief Last error string */
71   const char *last;
72 };
73
74 /** @brief Create a new client
75  * @param verbose If nonzero, write extra junk to stderr
76  * @return Pointer to new client
77  *
78  * You must call disorder_connect(), disorder_connect_user() or
79  * disorder_connect_cookie() to connect it.  Use disorder_close() to
80  * dispose of the client when finished with it.
81  */
82 disorder_client *disorder_new(int verbose) {
83   disorder_client *c = xmalloc(sizeof (struct disorder_client));
84
85   c->verbose = verbose;
86   return c;
87 }
88
89 /** @brief Read a response line
90  * @param c Client
91  * @param rp Where to store response, or NULL (UTF-8)
92  * @return Response code 0-999 or -1 on error
93  */
94 static int response(disorder_client *c, char **rp) {
95   char *r;
96
97   if(inputline(c->ident, c->fpin, &r, '\n')) {
98     byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
99     return -1;
100   }
101   D(("response: %s", r));
102   if(rp)
103     *rp = r;
104   if(r[0] >= '0' && r[0] <= '9'
105      && r[1] >= '0' && r[1] <= '9'
106      && r[2] >= '0' && r[2] <= '9'
107      && r[3] == ' ') {
108     c->last = r + 4;
109     return (r[0] * 10 + r[1]) * 10 + r[2] - 111 * '0';
110   } else {
111     c->last = "invalid reply format";
112     error(0, "invalid reply format from %s", c->ident);
113     return -1;
114   }
115 }
116
117 /** @brief Return last response string
118  * @param c Client
119  * @return Last response string (UTF-8, English) or NULL
120  */
121 const char *disorder_last(disorder_client *c) {
122   return c->last;
123 }
124
125 /** @brief Read and partially parse a response
126  * @param c Client
127  * @param rp Where to store response text (or NULL) (UTF-8)
128  * @return 0 on success, non-0 on error
129  *
130  * 5xx responses count as errors.
131  *
132  * @p rp will NOT be filled in for xx9 responses (where it is just
133  * commentary for a command where it would normally be meaningful).
134  *
135  * NB that the response will NOT be converted to the local encoding.
136  */
137 static int check_response(disorder_client *c, char **rp) {
138   int rc;
139   char *r;
140
141   if((rc = response(c, &r)) == -1)
142     return -1;
143   else if(rc / 100 == 2) {
144     if(rp)
145       *rp = (rc % 10 == 9) ? 0 : xstrdup(r + 4);
146     return 0;
147   } else {
148     if(c->verbose)
149       error(0, "from %s: %s", c->ident, utf82mb(r));
150     return rc;
151   }
152 }
153
154 /** @brief Issue a command and parse a simple response
155  * @param c Client
156  * @param rp Where to store result, or NULL
157  * @param cmd Command
158  * @param body Body or NULL
159  * @param nbody Length of body or -1
160  * @param ap Arguments (UTF-8), terminated by (char *)0
161  * @return 0 on success, non-0 on error
162  *
163  * 5xx responses count as errors.
164  *
165  * @p rp will NOT be filled in for xx9 responses (where it is just
166  * commentary for a command where it would normally be meaningful).
167  *
168  * NB that the response will NOT be converted to the local encoding
169  * nor will quotes be stripped.  See dequote().
170  *
171  * If @p body is not NULL then the body is sent immediately after the
172  * command.  @p nbody should be the number of lines or @c -1 to count
173  * them if @p body is NULL-terminated.
174  *
175  * Usually you would call this via one of the following interfaces:
176  * - disorder_simple()
177  * - disorder_simple_body()
178  * - disorder_simple_list()
179  */
180 static int disorder_simple_v(disorder_client *c,
181                              char **rp,
182                              const char *cmd,
183                              char **body, int nbody,
184                              va_list ap) {
185   const char *arg;
186   struct dynstr d;
187
188   if(!c->fpout) {
189     c->last = "not connected";
190     error(0, "not connected to server");
191     return -1;
192   }
193   if(cmd) {
194     dynstr_init(&d);
195     dynstr_append_string(&d, cmd);
196     while((arg = va_arg(ap, const char *))) {
197       dynstr_append(&d, ' ');
198       dynstr_append_string(&d, quoteutf8(arg));
199     }
200     dynstr_append(&d, '\n');
201     dynstr_terminate(&d);
202     D(("command: %s", d.vec));
203     if(fputs(d.vec, c->fpout) < 0)
204       goto write_error;
205     if(body) {
206       if(nbody < 0)
207         for(nbody = 0; body[nbody]; ++nbody)
208           ;
209       for(int n = 0; n < nbody; ++n) {
210         if(body[n][0] == '.')
211           if(fputc('.', c->fpout) < 0)
212             goto write_error;
213         if(fputs(body[n], c->fpout) < 0)
214           goto write_error;
215         if(fputc('\n', c->fpout) < 0)
216           goto write_error;
217       }
218       if(fputs(".\n", c->fpout) < 0)
219         goto write_error;
220     }
221     if(fflush(c->fpout))
222       goto write_error;
223   }
224   return check_response(c, rp);
225 write_error:
226   byte_xasprintf((char **)&c->last, "write error: %s", strerror(errno));
227   error(errno, "error writing to %s", c->ident);
228   return -1;
229 }
230
231 /** @brief Issue a command and parse a simple response
232  * @param c Client
233  * @param rp Where to store result, or NULL (UTF-8)
234  * @param cmd Command
235  * @return 0 on success, non-0 on error
236  *
237  * The remaining arguments are command arguments, terminated by (char
238  * *)0.  They should be in UTF-8.
239  *
240  * 5xx responses count as errors.
241  *
242  * @p rp will NOT be filled in for xx9 responses (where it is just
243  * commentary for a command where it would normally be meaningful).
244  *
245  * NB that the response will NOT be converted to the local encoding
246  * nor will quotes be stripped.  See dequote().
247  */
248 static int disorder_simple(disorder_client *c,
249                            char **rp,
250                            const char *cmd, ...) {
251   va_list ap;
252   int ret;
253
254   va_start(ap, cmd);
255   ret = disorder_simple_v(c, rp, cmd, 0, 0, ap);
256   va_end(ap);
257   return ret;
258 }
259
260 /** @brief Issue a command with a body and parse a simple response
261  * @param c Client
262  * @param rp Where to store result, or NULL (UTF-8)
263  * @param body Pointer to body
264  * @param nbody Size of body
265  * @param cmd Command
266  * @return 0 on success, non-0 on error
267  *
268  * See disorder_simple().
269  */
270 static int disorder_simple_body(disorder_client *c,
271                                 char **rp,
272                                 char **body, int nbody,
273                                 const char *cmd, ...) {
274   va_list ap;
275   int ret;
276
277   va_start(ap, cmd);
278   ret = disorder_simple_v(c, rp, cmd, body, nbody, ap);
279   va_end(ap);
280   return ret;
281 }
282
283 /** @brief Dequote a result string
284  * @param rc 0 on success, non-0 on error
285  * @param rp Where result string is stored (UTF-8)
286  * @return @p rc
287  *
288  * This is used as a wrapper around disorder_simple() to dequote
289  * results in place.
290  */
291 static int dequote(int rc, char **rp) {
292   char **rr;
293
294   if(!rc) {
295     if((rr = split(*rp, 0, SPLIT_QUOTES, 0, 0)) && *rr) {
296       *rp = *rr;
297       return 0;
298     }
299     error(0, "invalid reply: %s", *rp);
300   }
301   return rc;
302 }
303
304 /** @brief Generic connection routine
305  * @param conf Configuration to follow
306  * @param c Client
307  * @param username Username to log in with or NULL
308  * @param password Password to log in with or NULL
309  * @param cookie Cookie to log in with or NULL
310  * @return 0 on success, non-0 on error
311  *
312  * @p cookie is tried first if not NULL.  If it is NULL then @p
313  * username must not be.  If @p username is not NULL then nor may @p
314  * password be.
315  */
316 int disorder_connect_generic(struct config *conf,
317                              disorder_client *c,
318                              const char *username,
319                              const char *password,
320                              const char *cookie) {
321   int fd = -1, fd2 = -1, nrvec, rc;
322   unsigned char *nonce;
323   size_t nl;
324   const char *res;
325   char *r, **rvec;
326   const char *protocol, *algorithm, *challenge;
327   struct sockaddr *sa;
328   socklen_t salen;
329
330   if((salen = find_server(conf, &sa, &c->ident)) == (socklen_t)-1)
331     return -1;
332   c->fpin = c->fpout = 0;
333   if((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
334     byte_xasprintf((char **)&c->last, "socket: %s", strerror(errno));
335     error(errno, "error calling socket");
336     return -1;
337   }
338   if(connect(fd, sa, salen) < 0) {
339     byte_xasprintf((char **)&c->last, "connect: %s", strerror(errno));
340     error(errno, "error calling connect");
341     goto error;
342   }
343   if((fd2 = dup(fd)) < 0) {
344     byte_xasprintf((char **)&c->last, "dup: %s", strerror(errno));
345     error(errno, "error calling dup");
346     goto error;
347   }
348   if(!(c->fpin = fdopen(fd, "rb"))) {
349     byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
350     error(errno, "error calling fdopen");
351     goto error;
352   }
353   fd = -1;
354   if(!(c->fpout = fdopen(fd2, "wb"))) {
355     byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
356     error(errno, "error calling fdopen");
357     goto error;
358   }
359   fd2 = -1;
360   if((rc = disorder_simple(c, &r, 0, (const char *)0)))
361     goto error_rc;
362   if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
363     goto error;
364   if(nrvec != 3) {
365     c->last = "cannot parse server greeting";
366     error(0, "cannot parse server greeting %s", r);
367     goto error;
368   }
369   protocol = *rvec++;
370   if(strcmp(protocol, "2")) {
371     c->last = "unknown protocol version";
372     error(0, "unknown protocol version: %s", protocol);
373     goto error;
374   }
375   algorithm = *rvec++;
376   challenge = *rvec++;
377   if(!(nonce = unhex(challenge, &nl)))
378     goto error;
379   if(cookie) {
380     if(!dequote(disorder_simple(c, &c->user, "cookie", cookie, (char *)0),
381                 &c->user))
382       return 0;                         /* success */
383     if(!username) {
384       c->last = "cookie failed and no username";
385       error(0, "cookie did not work and no username available");
386       goto error;
387     }
388   }
389   if(!(res = authhash(nonce, nl, password, algorithm))) {
390     c->last = "error computing authorization hash";
391     goto error;
392   }
393   if((rc = disorder_simple(c, 0, "user", username, res, (char *)0)))
394     goto error_rc;
395   c->user = xstrdup(username);
396   return 0;
397 error:
398   rc = -1;
399 error_rc:
400   if(c->fpin) {
401     fclose(c->fpin);
402     c->fpin = 0;
403   }
404   if(c->fpout) {
405     fclose(c->fpout);
406     c->fpout = 0;
407   }
408   if(fd2 != -1) close(fd2);
409   if(fd != -1) close(fd);
410   return rc;
411 }
412
413 /** @brief Connect a client with a specified username and password
414  * @param c Client
415  * @param username Username to log in with
416  * @param password Password to log in with
417  * @return 0 on success, non-0 on error
418  */
419 int disorder_connect_user(disorder_client *c,
420                           const char *username,
421                           const char *password) {
422   return disorder_connect_generic(config,
423                                   c,
424                                   username,
425                                   password,
426                                   0);
427 }
428
429 /** @brief Connect a client
430  * @param c Client
431  * @return 0 on success, non-0 on error
432  *
433  * The connection will use the username and password found in @ref
434  * config, or directly from the database if no password is found and
435  * the database is readable (usually only for root).
436  */
437 int disorder_connect(disorder_client *c) {
438   const char *username, *password;
439
440   if(!(username = config->username)) {
441     c->last = "no username";
442     error(0, "no username configured");
443     return -1;
444   }
445   password = config->password;
446   /* Maybe we can read the database */
447   if(!password && trackdb_readable()) {
448     trackdb_init(TRACKDB_NO_RECOVER|TRACKDB_NO_UPGRADE);
449     trackdb_open(TRACKDB_READ_ONLY);
450     password = trackdb_get_password(username);
451     trackdb_close();
452   }
453   if(!password) {
454     /* Oh well */
455     c->last = "no password";
456     error(0, "no password configured");
457     return -1;
458   }
459   return disorder_connect_generic(config,
460                                   c,
461                                   username,
462                                   password,
463                                   0);
464 }
465
466 /** @brief Connect a client
467  * @param c Client
468  * @param cookie Cookie to log in with, or NULL
469  * @return 0 on success, non-0 on error
470  *
471  * If @p cookie is NULL or does not work then we attempt to log in as
472  * guest instead (so when the cookie expires only an extra round trip
473  * is needed rathre than a complete new login).
474  */
475 int disorder_connect_cookie(disorder_client *c,
476                             const char *cookie) {
477   return disorder_connect_generic(config,
478                                   c,
479                                   "guest",
480                                   "",
481                                   cookie);
482 }
483
484 /** @brief Close a client
485  * @param c Client
486  * @return 0 on succcess, non-0 on errior
487  *
488  * The client is still closed even on error.  It might well be
489  * appropriate to ignore the return value.
490  */
491 int disorder_close(disorder_client *c) {
492   int ret = 0;
493
494   if(c->fpin) {
495     if(fclose(c->fpin) < 0) {
496       byte_xasprintf((char **)&c->last, "fclose: %s", strerror(errno));
497       error(errno, "error calling fclose");
498       ret = -1;
499     }
500     c->fpin = 0;
501   }
502   if(c->fpout) {
503     if(fclose(c->fpout) < 0) {
504       byte_xasprintf((char **)&c->last, "fclose: %s", strerror(errno));
505       error(errno, "error calling fclose");
506       ret = -1;
507     }
508     c->fpout = 0;
509   }
510   c->ident = 0;
511   c->user = 0;
512   return 0;
513 }
514
515 /** @brief Play a track
516  * @param c Client
517  * @param track Track to play (UTF-8)
518  * @return 0 on success, non-0 on error
519  */
520 int disorder_play(disorder_client *c, const char *track) {
521   return disorder_simple(c, 0, "play", track, (char *)0);
522 }
523
524 /** @brief Remove a track
525  * @param c Client
526  * @param track Track to remove (UTF-8)
527  * @return 0 on success, non-0 on error
528  */
529 int disorder_remove(disorder_client *c, const char *track) {
530   return disorder_simple(c, 0, "remove", track, (char *)0);
531 }
532
533 /** @brief Move a track
534  * @param c Client
535  * @param track Track to move (UTF-8)
536  * @param delta Distance to move by
537  * @return 0 on success, non-0 on error
538  */
539 int disorder_move(disorder_client *c, const char *track, int delta) {
540   char d[16];
541
542   byte_snprintf(d, sizeof d, "%d", delta);
543   return disorder_simple(c, 0, "move", track, d, (char *)0);
544 }
545
546 /** @brief Enable play
547  * @param c Client
548  * @return 0 on success, non-0 on error
549  */
550 int disorder_enable(disorder_client *c) {
551   return disorder_simple(c, 0, "enable", (char *)0);
552 }
553
554 /** @brief Disable play
555  * @param c Client
556  * @return 0 on success, non-0 on error
557  */
558 int disorder_disable(disorder_client *c) {
559   return disorder_simple(c, 0, "disable", (char *)0);
560 }
561
562 /** @brief Scratch the currently playing track
563  * @param id Playing track ID or NULL (UTF-8)
564  * @param c Client
565  * @return 0 on success, non-0 on error
566  */
567 int disorder_scratch(disorder_client *c, const char *id) {
568   return disorder_simple(c, 0, "scratch", id, (char *)0);
569 }
570
571 /** @brief Shut down the server
572  * @param c Client
573  * @return 0 on success, non-0 on error
574  */
575 int disorder_shutdown(disorder_client *c) {
576   return disorder_simple(c, 0, "shutdown", (char *)0);
577 }
578
579 /** @brief Make the server re-read its configuration
580  * @param c Client
581  * @return 0 on success, non-0 on error
582  */
583 int disorder_reconfigure(disorder_client *c) {
584   return disorder_simple(c, 0, "reconfigure", (char *)0);
585 }
586
587 /** @brief Rescan tracks
588  * @param c Client
589  * @return 0 on success, non-0 on error
590  */
591 int disorder_rescan(disorder_client *c) {
592   return disorder_simple(c, 0, "rescan", (char *)0);
593 }
594
595 /** @brief Get server version number
596  * @param c Client
597  * @param rp Where to store version string (UTF-8)
598  * @return 0 on success, non-0 on error
599  */
600 int disorder_version(disorder_client *c, char **rp) {
601   return dequote(disorder_simple(c, rp, "version", (char *)0), rp);
602 }
603
604 static void client_error(const char *msg,
605                          void attribute((unused)) *u) {
606   error(0, "error parsing reply: %s", msg);
607 }
608
609 /** @brief Get currently playing track
610  * @param c Client
611  * @param qp Where to store track information
612  * @return 0 on success, non-0 on error
613  *
614  * @p qp gets NULL if no track is playing.
615  */
616 int disorder_playing(disorder_client *c, struct queue_entry **qp) {
617   char *r;
618   struct queue_entry *q;
619   int rc;
620
621   if((rc = disorder_simple(c, &r, "playing", (char *)0)))
622     return rc;
623   if(r) {
624     q = xmalloc(sizeof *q);
625     if(queue_unmarshall(q, r, client_error, 0))
626       return -1;
627     *qp = q;
628   } else
629     *qp = 0;
630   return 0;
631 }
632
633 /** @brief Fetch the queue, recent list, etc */
634 static int disorder_somequeue(disorder_client *c,
635                               const char *cmd, struct queue_entry **qp) {
636   struct queue_entry *qh, **qt = &qh, *q;
637   char *l;
638   int rc;
639
640   if((rc = disorder_simple(c, 0, cmd, (char *)0)))
641     return rc;
642   while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
643     if(!strcmp(l, ".")) {
644       *qt = 0;
645       *qp = qh;
646       return 0;
647     }
648     q = xmalloc(sizeof *q);
649     if(!queue_unmarshall(q, l, client_error, 0)) {
650       *qt = q;
651       qt = &q->next;
652     }
653   }
654   if(ferror(c->fpin)) {
655     byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
656     error(errno, "error reading %s", c->ident);
657   } else {
658     c->last = "input error: unexpxected EOF";
659     error(0, "error reading %s: unexpected EOF", c->ident);
660   }
661   return -1;
662 }
663
664 /** @brief Get recently played tracks
665  * @param c Client
666  * @param qp Where to store track information
667  * @return 0 on success, non-0 on error
668  *
669  * The last entry in the list is the most recently played track.
670  */
671 int disorder_recent(disorder_client *c, struct queue_entry **qp) {
672   return disorder_somequeue(c, "recent", qp);
673 }
674
675 /** @brief Get queue
676  * @param c Client
677  * @param qp Where to store track information
678  * @return 0 on success, non-0 on error
679  *
680  * The first entry in the list will be played next.
681  */
682 int disorder_queue(disorder_client *c, struct queue_entry **qp) {
683   return disorder_somequeue(c, "queue", qp);
684 }
685
686 /** @brief Read a dot-stuffed list
687  * @param c Client
688  * @param vecp Where to store list (UTF-8)
689  * @param nvecp Where to store number of items, or NULL
690  * @return 0 on success, non-0 on error
691  *
692  * The list will have a final NULL not counted in @p nvecp.
693  */
694 static int readlist(disorder_client *c, char ***vecp, int *nvecp) {
695   char *l;
696   struct vector v;
697
698   vector_init(&v);
699   while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
700     if(!strcmp(l, ".")) {
701       vector_terminate(&v);
702       if(nvecp)
703         *nvecp = v.nvec;
704       *vecp = v.vec;
705       return 0;
706     }
707     vector_append(&v, l + (*l == '.'));
708   }
709   if(ferror(c->fpin)) {
710     byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
711     error(errno, "error reading %s", c->ident);
712   } else {
713     c->last = "input error: unexpxected EOF";
714     error(0, "error reading %s: unexpected EOF", c->ident);
715   }
716   return -1;
717 }
718
719 /** @brief Issue a comamnd and get a list response
720  * @param c Client
721  * @param vecp Where to store list (UTF-8)
722  * @param nvecp Where to store number of items, or NULL
723  * @param cmd Command
724  * @return 0 on success, non-0 on error
725  *
726  * The remaining arguments are command arguments, terminated by (char
727  * *)0.  They should be in UTF-8.
728  *
729  * 5xx responses count as errors.
730  *
731  * See disorder_simple().
732  */
733 static int disorder_simple_list(disorder_client *c,
734                                 char ***vecp, int *nvecp,
735                                 const char *cmd, ...) {
736   va_list ap;
737   int ret;
738
739   va_start(ap, cmd);
740   ret = disorder_simple_v(c, 0, cmd, 0, 0, ap);
741   va_end(ap);
742   if(ret) return ret;
743   return readlist(c, vecp, nvecp);
744 }
745
746 /** @brief List directories below @p dir
747  * @param c Client
748  * @param dir Directory to list, or NULL for root (UTF-8)
749  * @param re Regexp that results must match, or NULL (UTF-8)
750  * @param vecp Where to store list (UTF-8)
751  * @param nvecp Where to store number of items, or NULL
752  * @return 0 on success, non-0 on error
753  */
754 int disorder_directories(disorder_client *c, const char *dir, const char *re,
755                          char ***vecp, int *nvecp) {
756   return disorder_simple_list(c, vecp, nvecp, "dirs", dir, re, (char *)0);
757 }
758
759 /** @brief List files below @p dir
760  * @param c Client
761  * @param dir Directory to list, or NULL for root (UTF-8)
762  * @param re Regexp that results must match, or NULL (UTF-8)
763  * @param vecp Where to store list (UTF-8)
764  * @param nvecp Where to store number of items, or NULL
765  * @return 0 on success, non-0 on error
766  */
767 int disorder_files(disorder_client *c, const char *dir, const char *re,
768                    char ***vecp, int *nvecp) {
769   return disorder_simple_list(c, vecp, nvecp, "files", dir, re, (char *)0);
770 }
771
772 /** @brief List files and directories below @p dir
773  * @param c Client
774  * @param dir Directory to list, or NULL for root (UTF-8)
775  * @param re Regexp that results must match, or NULL (UTF-8)
776  * @param vecp Where to store list (UTF-8)
777  * @param nvecp Where to store number of items, or NULL
778  * @return 0 on success, non-0 on error
779  */
780 int disorder_allfiles(disorder_client *c, const char *dir, const char *re,
781                       char ***vecp, int *nvecp) {
782   return disorder_simple_list(c, vecp, nvecp, "allfiles", dir, re, (char *)0);
783 }
784
785 /** @brief Return the user we logged in with
786  * @param c Client
787  * @return User name (owned by @p c, don't modify)
788  */
789 char *disorder_user(disorder_client *c) {
790   return c->user;
791 }
792
793 /** @brief Set a track preference
794  * @param c Client
795  * @param track Track name (UTF-8)
796  * @param key Preference name (UTF-8)
797  * @param value Preference value (UTF-8)
798  * @return 0 on success, non-0 on error
799  */
800 int disorder_set(disorder_client *c, const char *track,
801                  const char *key, const char *value) {
802   return disorder_simple(c, 0, "set", track, key, value, (char *)0);
803 }
804
805 /** @brief Unset a track preference
806  * @param c Client
807  * @param track Track name (UTF-8)
808  * @param key Preference name (UTF-8)
809  * @return 0 on success, non-0 on error
810  */
811 int disorder_unset(disorder_client *c, const char *track,
812                    const char *key) {
813   return disorder_simple(c, 0, "unset", track, key, (char *)0);
814 }
815
816 /** @brief Get a track preference
817  * @param c Client
818  * @param track Track name (UTF-8)
819  * @param key Preference name (UTF-8)
820  * @param valuep Where to store preference value (UTF-8)
821  * @return 0 on success, non-0 on error
822  */
823 int disorder_get(disorder_client *c,
824                  const char *track, const char *key, char **valuep) {
825   return dequote(disorder_simple(c, valuep, "get", track, key, (char *)0),
826                  valuep);
827 }
828
829 static void pref_error_handler(const char *msg,
830                                void attribute((unused)) *u) {
831   error(0, "error handling 'prefs' reply: %s", msg);
832 }
833
834 /** @brief Get all preferences for a trcak
835  * @param c Client
836  * @param track Track name
837  * @param kp Where to store linked list of preferences
838  * @return 0 on success, non-0 on error
839  */
840 int disorder_prefs(disorder_client *c, const char *track, struct kvp **kp) {
841   char **vec, **pvec;
842   int nvec, npvec, n, rc;
843   struct kvp *k;
844
845   if((rc = disorder_simple_list(c, &vec, &nvec, "prefs", track, (char *)0)))
846     return rc;
847   for(n = 0; n < nvec; ++n) {
848     if(!(pvec = split(vec[n], &npvec, SPLIT_QUOTES, pref_error_handler, 0)))
849       return -1;
850     if(npvec != 2) {
851       pref_error_handler("malformed response", 0);
852       return -1;
853     }
854     *kp = k = xmalloc(sizeof *k);
855     k->name = pvec[0];
856     k->value = pvec[1];
857     kp = &k->next;
858   }
859   *kp = 0;
860   return 0;
861 }
862
863 /** @brief Parse a boolean response
864  * @param cmd Command for use in error messsage
865  * @param value Result from server
866  * @param flagp Where to store result
867  * @return 0 on success, non-0 on error
868  */
869 static int boolean(const char *cmd, const char *value,
870                    int *flagp) {
871   if(!strcmp(value, "yes")) *flagp = 1;
872   else if(!strcmp(value, "no")) *flagp = 0;
873   else {
874     error(0, "malformed response to '%s'", cmd);
875     return -1;
876   }
877   return 0;
878 }
879
880 /** @brief Test whether a track exists
881  * @param c Client
882  * @param track Track name (UTF-8)
883  * @param existsp Where to store result (non-0 iff does exist)
884  * @return 0 on success, non-0 on error
885  */
886 int disorder_exists(disorder_client *c, const char *track, int *existsp) {
887   char *v;
888   int rc;
889
890   if((rc = disorder_simple(c, &v, "exists", track, (char *)0)))
891     return rc;
892   return boolean("exists", v, existsp);
893 }
894
895 /** @brief Test whether playing is enabled
896  * @param c Client
897  * @param enabledp Where to store result (non-0 iff enabled)
898  * @return 0 on success, non-0 on error
899  */
900 int disorder_enabled(disorder_client *c, int *enabledp) {
901   char *v;
902   int rc;
903
904   if((rc = disorder_simple(c, &v, "enabled", (char *)0)))
905     return rc;
906   return boolean("enabled", v, enabledp);
907 }
908
909 /** @brief Get the length of a track
910  * @param c Client
911  * @param track Track name (UTF-8)
912  * @param valuep Where to store length in seconds
913  * @return 0 on success, non-0 on error
914  *
915  * If the length is unknown 0 is returned.
916  */
917 int disorder_length(disorder_client *c, const char *track,
918                     long *valuep) {
919   char *value;
920   int rc;
921
922   if((rc = disorder_simple(c, &value, "length", track, (char *)0)))
923     return rc;
924   *valuep = atol(value);
925   return 0;
926 }
927
928 /** @brief Search for tracks
929  * @param c Client
930  * @param terms Search terms (UTF-8)
931  * @param vecp Where to store list (UTF-8)
932  * @param nvecp Where to store number of items, or NULL
933  * @return 0 on success, non-0 on error
934  */
935 int disorder_search(disorder_client *c, const char *terms,
936                     char ***vecp, int *nvecp) {
937   return disorder_simple_list(c, vecp, nvecp, "search", terms, (char *)0);
938 }
939
940 /** @brief Enable random play
941  * @param c Client
942  * @return 0 on success, non-0 on error
943  */
944 int disorder_random_enable(disorder_client *c) {
945   return disorder_simple(c, 0, "random-enable", (char *)0);
946 }
947
948 /** @brief Disable random play
949  * @param c Client
950  * @return 0 on success, non-0 on error
951  */
952 int disorder_random_disable(disorder_client *c) {
953   return disorder_simple(c, 0, "random-disable", (char *)0);
954 }
955
956 /** @brief Test whether random play is enabled
957  * @param c Client
958  * @param enabledp Where to store result (non-0 iff enabled)
959  * @return 0 on success, non-0 on error
960  */
961 int disorder_random_enabled(disorder_client *c, int *enabledp) {
962   char *v;
963   int rc;
964
965   if((rc = disorder_simple(c, &v, "random-enabled", (char *)0)))
966     return rc;
967   return boolean("random-enabled", v, enabledp);
968 }
969
970 /** @brief Get server stats
971  * @param c Client
972  * @param vecp Where to store list (UTF-8)
973  * @param nvecp Where to store number of items, or NULL
974  * @return 0 on success, non-0 on error
975  */
976 int disorder_stats(disorder_client *c,
977                    char ***vecp, int *nvecp) {
978   return disorder_simple_list(c, vecp, nvecp, "stats", (char *)0);
979 }
980
981 /** @brief Set volume
982  * @param c Client
983  * @param left New left channel value
984  * @param right New right channel value
985  * @return 0 on success, non-0 on error
986  */
987 int disorder_set_volume(disorder_client *c, int left, int right) {
988   char *ls, *rs;
989
990   if(byte_asprintf(&ls, "%d", left) < 0
991      || byte_asprintf(&rs, "%d", right) < 0)
992     return -1;
993   return disorder_simple(c, 0, "volume", ls, rs, (char *)0);
994 }
995
996 /** @brief Get volume
997  * @param c Client
998  * @param left Where to store left channel value
999  * @param right Where to store right channel value
1000  * @return 0 on success, non-0 on error
1001  */
1002 int disorder_get_volume(disorder_client *c, int *left, int *right) {
1003   char *r;
1004   int rc;
1005
1006   if((rc = disorder_simple(c, &r, "volume", (char *)0)))
1007     return rc;
1008   if(sscanf(r, "%d %d", left, right) != 2) {
1009     c->last = "malformed volume response";
1010     error(0, "error parsing response to 'volume': '%s'", r);
1011     return -1;
1012   }
1013   return 0;
1014 }
1015
1016 /** @brief Log to a sink
1017  * @param c Client
1018  * @param s Sink to write log lines to
1019  * @return 0 on success, non-0 on error
1020  */
1021 int disorder_log(disorder_client *c, struct sink *s) {
1022   char *l;
1023   int rc;
1024     
1025   if((rc = disorder_simple(c, 0, "log", (char *)0)))
1026     return rc;
1027   while(inputline(c->ident, c->fpin, &l, '\n') >= 0 && strcmp(l, "."))
1028     if(sink_printf(s, "%s\n", l) < 0) return -1;
1029   if(ferror(c->fpin) || feof(c->fpin)) {
1030     byte_xasprintf((char **)&c->last, "input error: %s",
1031                    ferror(c->fpin) ? strerror(errno) : "unexpxected EOF");
1032     return -1;
1033   }
1034   return 0;
1035 }
1036
1037 /** @brief Look up a track name part
1038  * @param c Client
1039  * @param partp Where to store result (UTF-8)
1040  * @param track Track name (UTF-8)
1041  * @param context Context (usually "sort" or "display") (UTF-8)
1042  * @param part Track part (UTF-8)
1043  * @return 0 on success, non-0 on error
1044  */
1045 int disorder_part(disorder_client *c, char **partp,
1046                   const char *track, const char *context, const char *part) {
1047   return dequote(disorder_simple(c, partp, "part",
1048                                  track, context, part, (char *)0), partp);
1049 }
1050
1051 /** @brief Resolve aliases
1052  * @param c Client
1053  * @param trackp Where to store canonical name (UTF-8)
1054  * @param track Track name (UTF-8)
1055  * @return 0 on success, non-0 on error
1056  */
1057 int disorder_resolve(disorder_client *c, char **trackp, const char *track) {
1058   return dequote(disorder_simple(c, trackp, "resolve", track, (char *)0),
1059                  trackp);
1060 }
1061
1062 /** @brief Pause the current track
1063  * @param c Client
1064  * @return 0 on success, non-0 on error
1065  */
1066 int disorder_pause(disorder_client *c) {
1067   return disorder_simple(c, 0, "pause", (char *)0);
1068 }
1069
1070 /** @brief Resume the current track
1071  * @param c Client
1072  * @return 0 on success, non-0 on error
1073  */
1074 int disorder_resume(disorder_client *c) {
1075   return disorder_simple(c, 0, "resume", (char *)0);
1076 }
1077
1078 /** @brief List all known tags
1079  * @param c Client
1080  * @param vecp Where to store list (UTF-8)
1081  * @param nvecp Where to store number of items, or NULL
1082  * @return 0 on success, non-0 on error
1083  */
1084 int disorder_tags(disorder_client *c,
1085                    char ***vecp, int *nvecp) {
1086   return disorder_simple_list(c, vecp, nvecp, "tags", (char *)0);
1087 }
1088
1089 /** @brief List all known users
1090  * @param c Client
1091  * @param vecp Where to store list (UTF-8)
1092  * @param nvecp Where to store number of items, or NULL
1093  * @return 0 on success, non-0 on error
1094  */
1095 int disorder_users(disorder_client *c,
1096                    char ***vecp, int *nvecp) {
1097   return disorder_simple_list(c, vecp, nvecp, "users", (char *)0);
1098 }
1099
1100 /** @brief Get recently added tracks
1101  * @param c Client
1102  * @param vecp Where to store pointer to list (UTF-8)
1103  * @param nvecp Where to store count
1104  * @param max Maximum tracks to fetch, or 0 for all available
1105  * @return 0 on success, non-0 on error
1106  */
1107 int disorder_new_tracks(disorder_client *c,
1108                         char ***vecp, int *nvecp,
1109                         int max) {
1110   char limit[32];
1111
1112   sprintf(limit, "%d", max);
1113   return disorder_simple_list(c, vecp, nvecp, "new", limit, (char *)0);
1114 }
1115
1116 /** @brief Set a global preference
1117  * @param c Client
1118  * @param key Preference name (UTF-8)
1119  * @param value Preference value (UTF-8)
1120  * @return 0 on success, non-0 on error
1121  */
1122 int disorder_set_global(disorder_client *c,
1123                         const char *key, const char *value) {
1124   return disorder_simple(c, 0, "set-global", key, value, (char *)0);
1125 }
1126
1127 /** @brief Unset a global preference
1128  * @param c Client
1129  * @param key Preference name (UTF-8)
1130  * @return 0 on success, non-0 on error
1131  */
1132 int disorder_unset_global(disorder_client *c, const char *key) {
1133   return disorder_simple(c, 0, "unset-global", key, (char *)0);
1134 }
1135
1136 /** @brief Get a global preference
1137  * @param c Client
1138  * @param key Preference name (UTF-8)
1139  * @param valuep Where to store preference value (UTF-8)
1140  * @return 0 on success, non-0 on error
1141  */
1142 int disorder_get_global(disorder_client *c, const char *key, char **valuep) {
1143   return dequote(disorder_simple(c, valuep, "get-global", key, (char *)0),
1144                  valuep);
1145 }
1146
1147 /** @brief Get server's RTP address information
1148  * @param c Client
1149  * @param addressp Where to store address (UTF-8)
1150  * @param portp Where to store port (UTF-8)
1151  * @return 0 on success, non-0 on error
1152  */
1153 int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
1154   char *r;
1155   int rc, n;
1156   char **vec;
1157
1158   if((rc = disorder_simple(c, &r, "rtp-address", (char *)0)))
1159     return rc;
1160   vec = split(r, &n, SPLIT_QUOTES, 0, 0);
1161   if(n != 2) {
1162     c->last = "malformed RTP address";
1163     error(0, "malformed rtp-address reply");
1164     return -1;
1165   }
1166   *addressp = vec[0];
1167   *portp = vec[1];
1168   return 0;
1169 }
1170
1171 /** @brief Create a user
1172  * @param c Client
1173  * @param user Username
1174  * @param password Password
1175  * @param rights Initial rights or NULL to use default
1176  * @return 0 on success, non-0 on error
1177  */
1178 int disorder_adduser(disorder_client *c,
1179                      const char *user, const char *password,
1180                      const char *rights) {
1181   return disorder_simple(c, 0, "adduser", user, password, rights, (char *)0);
1182 }
1183
1184 /** @brief Delete a user
1185  * @param c Client
1186  * @param user Username
1187  * @return 0 on success, non-0 on error
1188  */
1189 int disorder_deluser(disorder_client *c, const char *user) {
1190   return disorder_simple(c, 0, "deluser", user, (char *)0);
1191 }
1192
1193 /** @brief Get user information
1194  * @param c Client
1195  * @param user Username
1196  * @param key Property name (UTF-8)
1197  * @param valuep Where to store value (UTF-8)
1198  * @return 0 on success, non-0 on error
1199  */
1200 int disorder_userinfo(disorder_client *c, const char *user, const char *key,
1201                       char **valuep) {
1202   return dequote(disorder_simple(c, valuep, "userinfo", user, key, (char *)0),
1203                  valuep);
1204 }
1205
1206 /** @brief Set user information
1207  * @param c Client
1208  * @param user Username
1209  * @param key Property name (UTF-8)
1210  * @param value New property value (UTF-8)
1211  * @return 0 on success, non-0 on error
1212  */
1213 int disorder_edituser(disorder_client *c, const char *user,
1214                       const char *key, const char *value) {
1215   return disorder_simple(c, 0, "edituser", user, key, value, (char *)0);
1216 }
1217
1218 /** @brief Register a user
1219  * @param c Client
1220  * @param user Username
1221  * @param password Password
1222  * @param email Email address (UTF-8)
1223  * @param confirmp Where to store confirmation string
1224  * @return 0 on success, non-0 on error
1225  */
1226 int disorder_register(disorder_client *c, const char *user,
1227                       const char *password, const char *email,
1228                       char **confirmp) {
1229   return dequote(disorder_simple(c, confirmp, "register",
1230                                  user, password, email, (char *)0),
1231                  confirmp);
1232 }
1233
1234 /** @brief Confirm a user
1235  * @param c Client
1236  * @param confirm Confirmation string
1237  * @return 0 on success, non-0 on error
1238  */
1239 int disorder_confirm(disorder_client *c, const char *confirm) {
1240   char *u;
1241   int rc;
1242   
1243   if(!(rc = dequote(disorder_simple(c, &u, "confirm", confirm, (char *)0),
1244                     &u)))
1245     c->user = u;
1246   return rc;
1247 }
1248
1249 /** @brief Make a cookie for this login
1250  * @param c Client
1251  * @param cookiep Where to store cookie string
1252  * @return 0 on success, non-0 on error
1253  */
1254 int disorder_make_cookie(disorder_client *c, char **cookiep) {
1255   return dequote(disorder_simple(c, cookiep, "make-cookie", (char *)0),
1256                  cookiep);
1257 }
1258
1259 /** @brief Revoke the cookie used by this session
1260  * @param c Client
1261  * @return 0 on success, non-0 on error
1262  */
1263 int disorder_revoke(disorder_client *c) {
1264   return disorder_simple(c, 0, "revoke", (char *)0);
1265 }
1266
1267 /** @brief Request a password reminder email
1268  * @param c Client
1269  * @param user Username
1270  * @return 0 on success, non-0 on error
1271  */
1272 int disorder_reminder(disorder_client *c, const char *user) {
1273   return disorder_simple(c, 0, "reminder", user, (char *)0);
1274 }
1275
1276 /** @brief List scheduled events
1277  * @param c Client
1278  * @param idsp Where to put list of event IDs
1279  * @param nidsp Where to put count of event IDs, or NULL
1280  * @return 0 on success, non-0 on error
1281  */
1282 int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp) {
1283   return disorder_simple_list(c, idsp, nidsp, "schedule-list", (char *)0);
1284 }
1285
1286 /** @brief Delete a scheduled event
1287  * @param c Client
1288  * @param id Event ID to delete
1289  * @return 0 on success, non-0 on error
1290  */
1291 int disorder_schedule_del(disorder_client *c, const char *id) {
1292   return disorder_simple(c, 0, "schedule-del", id, (char *)0);
1293 }
1294
1295 /** @brief Get details of a scheduled event
1296  * @param c Client
1297  * @param id Event ID
1298  * @param actiondatap Where to put details
1299  * @return 0 on success, non-0 on error
1300  */
1301 int disorder_schedule_get(disorder_client *c, const char *id,
1302                           struct kvp **actiondatap) {
1303   char **lines, **bits;
1304   int rc, nbits;
1305
1306   *actiondatap = 0;
1307   if((rc = disorder_simple_list(c, &lines, NULL,
1308                                 "schedule-get", id, (char *)0)))
1309     return rc;
1310   while(*lines) {
1311     if(!(bits = split(*lines++, &nbits, SPLIT_QUOTES, 0, 0))) {
1312       error(0, "invalid schedule-get reply: cannot split line");
1313       return -1;
1314     }
1315     if(nbits != 2) {
1316       error(0, "invalid schedule-get reply: wrong number of fields");
1317       return -1;
1318     }
1319     kvp_set(actiondatap, bits[0], bits[1]);
1320   }
1321   return 0;
1322 }
1323
1324 /** @brief Add a scheduled event
1325  * @param c Client
1326  * @param when When to trigger the event
1327  * @param priority Event priority ("normal" or "junk")
1328  * @param action What action to perform
1329  * @param ... Action-specific arguments
1330  * @return 0 on success, non-0 on error
1331  *
1332  * For action @c "play" the next argument is the track.
1333  *
1334  * For action @c "set-global" next argument is the global preference name
1335  * and the final argument the value to set it to, or (char *)0 to unset it.
1336  */
1337 int disorder_schedule_add(disorder_client *c,
1338                           time_t when,
1339                           const char *priority,
1340                           const char *action,
1341                           ...) {
1342   va_list ap;
1343   char when_str[64];
1344   int rc;
1345
1346   snprintf(when_str, sizeof when_str, "%lld", (long long)when);
1347   va_start(ap, action);
1348   if(!strcmp(action, "play"))
1349     rc = disorder_simple(c, 0, "schedule-add", when_str, priority,
1350                          action, va_arg(ap, char *),
1351                          (char *)0);
1352   else if(!strcmp(action, "set-global")) {
1353     const char *key = va_arg(ap, char *);
1354     const char *value = va_arg(ap, char *);
1355     rc = disorder_simple(c, 0,"schedule-add",  when_str, priority,
1356                          action, key, value,
1357                          (char *)0);
1358   } else
1359     fatal(0, "unknown action '%s'", action);
1360   va_end(ap);
1361   return rc;
1362 }
1363
1364 /** @brief Delete a playlist
1365  * @param c Client
1366  * @param playlist Playlist to delete
1367  * @return 0 on success, non-0 on error
1368  */
1369 int disorder_playlist_delete(disorder_client *c,
1370                              const char *playlist) {
1371   return disorder_simple(c, 0, "playlist-delete", playlist, (char *)0);
1372 }
1373
1374 /** @brief Get the contents of a playlist
1375  * @param c Client
1376  * @param playlist Playlist to get
1377  * @param tracksp Where to put list of tracks
1378  * @param ntracksp Where to put count of tracks
1379  * @return 0 on success, non-0 on error
1380  */
1381 int disorder_playlist_get(disorder_client *c, const char *playlist,
1382                           char ***tracksp, int *ntracksp) {
1383   return disorder_simple_list(c, tracksp, ntracksp,
1384                               "playlist-get", playlist, (char *)0);
1385 }
1386
1387 /** @brief List all readable playlists
1388  * @param c Client
1389  * @param playlistsp Where to put list of playlists
1390  * @param nplaylistsp Where to put count of playlists
1391  * @return 0 on success, non-0 on error
1392  */
1393 int disorder_playlists(disorder_client *c,
1394                        char ***playlistsp, int *nplaylistsp) {
1395   return disorder_simple_list(c, playlistsp, nplaylistsp,
1396                               "playlists", (char *)0);
1397 }
1398
1399 /** @brief Get the sharing status of a playlist
1400  * @param c Client
1401  * @param playlist Playlist to inspect
1402  * @param sharep Where to put sharing status
1403  * @return 0 on success, non-0 on error
1404  *
1405  * Possible @p sharep values are @c public, @c private and @c shared.
1406  */
1407 int disorder_playlist_get_share(disorder_client *c, const char *playlist,
1408                                 char **sharep) {
1409   return disorder_simple(c, sharep,
1410                          "playlist-get-share", playlist, (char *)0);
1411 }
1412
1413 /** @brief Get the sharing status of a playlist
1414  * @param c Client
1415  * @param playlist Playlist to modify
1416  * @param share New sharing status
1417  * @return 0 on success, non-0 on error
1418  *
1419  * Possible @p share values are @c public, @c private and @c shared.
1420  */
1421 int disorder_playlist_set_share(disorder_client *c, const char *playlist,
1422                                 const char *share) {
1423   return disorder_simple(c, 0,
1424                          "playlist-set-share", playlist, share, (char *)0);
1425 }
1426
1427 /** @brief Lock a playlist for modifications
1428  * @param c Client
1429  * @param playlist Playlist to lock
1430  * @return 0 on success, non-0 on error
1431  */
1432 int disorder_playlist_lock(disorder_client *c, const char *playlist) {
1433   return disorder_simple(c, 0,
1434                          "playlist-lock", playlist, (char *)0);
1435 }
1436
1437 /** @brief Unlock the locked playlist
1438  * @param c Client
1439  * @return 0 on success, non-0 on error
1440  */
1441 int disorder_playlist_unlock(disorder_client *c) {
1442   return disorder_simple(c, 0,
1443                          "playlist-unlock", (char *)0);
1444 }
1445
1446 /** @brief Set the contents of a playlst
1447  * @param c Client
1448  * @param playlist Playlist to modify
1449  * @param tracks List of tracks
1450  * @param ntracks Length of @p tracks (or -1 to count up to the first NULL)
1451  * @return 0 on success, non-0 on error
1452  */
1453 int disorder_playlist_set(disorder_client *c,
1454                           const char *playlist,
1455                           char **tracks,
1456                           int ntracks) {
1457   return disorder_simple_body(c, 0, tracks, ntracks,
1458                               "playlist-set", playlist, (char *)0);
1459 }
1460
1461 /*
1462 Local Variables:
1463 c-basic-offset:2
1464 comment-column:40
1465 End:
1466 */