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