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