chiark / gitweb /
Support schedule-* commands from command-line client. Exiguously tested.
[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 <config.h>
28 #include "types.h"
29
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <sys/un.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <netdb.h>
39 #include <stdlib.h>
40 #include <pcre.h>
41
42 #include "log.h"
43 #include "mem.h"
44 #include "configuration.h"
45 #include "queue.h"
46 #include "client.h"
47 #include "charset.h"
48 #include "hex.h"
49 #include "split.h"
50 #include "vector.h"
51 #include "inputline.h"
52 #include "kvp.h"
53 #include "syscalls.h"
54 #include "printf.h"
55 #include "sink.h"
56 #include "addr.h"
57 #include "authhash.h"
58 #include "client-common.h"
59 #include "rights.h"
60 #include "trackdb.h"
61 #include "kvp.h"
62
63 /** @brief Client handle contents */
64 struct disorder_client {
65   /** @brief Stream to read from */
66   FILE *fpin;
67   /** @brief Stream to write to */
68   FILE *fpout;
69   /** @brief Peer description */
70   char *ident;
71   /** @brief Username */
72   char *user;
73   /** @brief Report errors to @c stderr */
74   int verbose;
75   /** @brief Last error string */
76   const char *last;
77 };
78
79 /** @brief Create a new client
80  * @param verbose If nonzero, write extra junk to stderr
81  * @return Pointer to new client
82  *
83  * You must call disorder_connect(), disorder_connect_user() or
84  * disorder_connect_cookie() to connect it.  Use disorder_close() to
85  * dispose of the client when finished with it.
86  */
87 disorder_client *disorder_new(int verbose) {
88   disorder_client *c = xmalloc(sizeof (struct disorder_client));
89
90   c->verbose = verbose;
91   return c;
92 }
93
94 /** @brief Read a response line
95  * @param c Client
96  * @param rp Where to store response, or NULL (UTF-8)
97  * @return Response code 0-999 or -1 on error
98  */
99 static int response(disorder_client *c, char **rp) {
100   char *r;
101
102   if(inputline(c->ident, c->fpin, &r, '\n')) {
103     byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
104     return -1;
105   }
106   D(("response: %s", r));
107   if(rp)
108     *rp = r;
109   if(r[0] >= '0' && r[0] <= '9'
110      && r[1] >= '0' && r[1] <= '9'
111      && r[2] >= '0' && r[2] <= '9'
112      && r[3] == ' ') {
113     c->last = r + 4;
114     return (r[0] * 10 + r[1]) * 10 + r[2] - 111 * '0';
115   } else {
116     c->last = "invalid reply format";
117     error(0, "invalid reply format from %s", c->ident);
118     return -1;
119   }
120 }
121
122 /** @brief Return last response string
123  * @param c Client
124  * @return Last response string (UTF-8, English) or NULL
125  */
126 const char *disorder_last(disorder_client *c) {
127   return c->last;
128 }
129
130 /** @brief Read and partially parse a response
131  * @param c Client
132  * @param rp Where to store response text (or NULL) (UTF-8)
133  * @return 0 on success, non-0 on error
134  *
135  * 5xx responses count as errors.
136  *
137  * @p rp will NOT be filled in for xx9 responses (where it is just
138  * commentary for a command where it would normally be meaningful).
139  *
140  * NB that the response will NOT be converted to the local encoding.
141  */
142 static int check_response(disorder_client *c, char **rp) {
143   int rc;
144   char *r;
145
146   if((rc = response(c, &r)) == -1)
147     return -1;
148   else if(rc / 100 == 2) {
149     if(rp)
150       *rp = (rc % 10 == 9) ? 0 : xstrdup(r + 4);
151     return 0;
152   } else {
153     if(c->verbose)
154       error(0, "from %s: %s", c->ident, utf82mb(r));
155     return rc;
156   }
157 }
158
159 /** @brief Issue a command and parse a simple response
160  * @param c Client
161  * @param rp Where to store result, or NULL
162  * @param cmd Command
163  * @param ap Arguments (UTF-8), terminated by (char *)0
164  * @return 0 on success, non-0 on error
165  *
166  * 5xx responses count as errors.
167  *
168  * @p rp will NOT be filled in for xx9 responses (where it is just
169  * commentary for a command where it would normally be meaningful).
170  *
171  * NB that the response will NOT be converted to the local encoding
172  * nor will quotes be stripped.  See dequote().
173  */
174 static int disorder_simple_v(disorder_client *c,
175                              char **rp,
176                              const char *cmd, va_list ap) {
177   const char *arg;
178   struct dynstr d;
179
180   if(!c->fpout) {
181     c->last = "not connected";
182     error(0, "not connected to server");
183     return -1;
184   }
185   if(cmd) {
186     dynstr_init(&d);
187     dynstr_append_string(&d, cmd);
188     while((arg = va_arg(ap, const char *))) {
189       dynstr_append(&d, ' ');
190       dynstr_append_string(&d, quoteutf8(arg));
191     }
192     dynstr_append(&d, '\n');
193     dynstr_terminate(&d);
194     D(("command: %s", d.vec));
195     if(fputs(d.vec, c->fpout) < 0 || fflush(c->fpout)) {
196       byte_xasprintf((char **)&c->last, "write error: %s", strerror(errno));
197       error(errno, "error writing to %s", c->ident);
198       return -1;
199     }
200   }
201   return check_response(c, rp);
202 }
203
204 /** @brief Issue a command and parse a simple response
205  * @param c Client
206  * @param rp Where to store result, or NULL (UTF-8)
207  * @param cmd Command
208  * @return 0 on success, non-0 on error
209  *
210  * The remaining arguments are command arguments, terminated by (char
211  * *)0.  They should be in UTF-8.
212  *
213  * 5xx responses count as errors.
214  *
215  * @p rp will NOT be filled in for xx9 responses (where it is just
216  * commentary for a command where it would normally be meaningful).
217  *
218  * NB that the response will NOT be converted to the local encoding
219  * nor will quotes be stripped.  See dequote().
220  */
221 static int disorder_simple(disorder_client *c,
222                            char **rp,
223                            const char *cmd, ...) {
224   va_list ap;
225   int ret;
226
227   va_start(ap, cmd);
228   ret = disorder_simple_v(c, rp, cmd, ap);
229   va_end(ap);
230   return ret;
231 }
232
233 /** @brief Dequote a result string
234  * @param rc 0 on success, non-0 on error
235  * @param rp Where result string is stored (UTF-8)
236  * @return @p rc
237  *
238  * This is used as a wrapper around disorder_simple() to dequote
239  * results in place.
240  */
241 static int dequote(int rc, char **rp) {
242   char **rr;
243
244   if(!rc) {
245     if((rr = split(*rp, 0, SPLIT_QUOTES, 0, 0)) && *rr) {
246       *rp = *rr;
247       return 0;
248     }
249     error(0, "invalid reply: %s", *rp);
250   }
251   return rc;
252 }
253
254 /** @brief Generic connection routine
255  * @param c Client
256  * @param username Username to log in with or NULL
257  * @param password Password to log in with or NULL
258  * @param cookie Cookie to log in with or NULL
259  * @return 0 on success, non-0 on error
260  *
261  * @p cookie is tried first if not NULL.  If it is NULL then @p
262  * username must not be.  If @p username is not NULL then nor may @p
263  * password be.
264  */
265 static int disorder_connect_generic(disorder_client *c,
266                                     const char *username,
267                                     const char *password,
268                                     const char *cookie) {
269   int fd = -1, fd2 = -1, nrvec, rc;
270   unsigned char *nonce;
271   size_t nl;
272   const char *res;
273   char *r, **rvec;
274   const char *protocol, *algorithm, *challenge;
275   struct sockaddr *sa;
276   socklen_t salen;
277
278   if((salen = find_server(&sa, &c->ident)) == (socklen_t)-1)
279     return -1;
280   c->fpin = c->fpout = 0;
281   if((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
282     byte_xasprintf((char **)&c->last, "socket: %s", strerror(errno));
283     error(errno, "error calling socket");
284     return -1;
285   }
286   if(connect(fd, sa, salen) < 0) {
287     byte_xasprintf((char **)&c->last, "connect: %s", strerror(errno));
288     error(errno, "error calling connect");
289     goto error;
290   }
291   if((fd2 = dup(fd)) < 0) {
292     byte_xasprintf((char **)&c->last, "dup: %s", strerror(errno));
293     error(errno, "error calling dup");
294     goto error;
295   }
296   if(!(c->fpin = fdopen(fd, "rb"))) {
297     byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
298     error(errno, "error calling fdopen");
299     goto error;
300   }
301   fd = -1;
302   if(!(c->fpout = fdopen(fd2, "wb"))) {
303     byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
304     error(errno, "error calling fdopen");
305     goto error;
306   }
307   fd2 = -1;
308   if((rc = disorder_simple(c, &r, 0, (const char *)0)))
309     goto error_rc;
310   if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
311     goto error;
312   if(nrvec != 3) {
313     c->last = "cannot parse server greeting";
314     error(0, "cannot parse server greeting %s", r);
315     goto error;
316   }
317   protocol = *rvec++;
318   if(strcmp(protocol, "2")) {
319     c->last = "unknown protocol version";
320     error(0, "unknown protocol version: %s", protocol);
321     goto error;
322   }
323   algorithm = *rvec++;
324   challenge = *rvec++;
325   if(!(nonce = unhex(challenge, &nl)))
326     goto error;
327   if(cookie) {
328     if(!dequote(disorder_simple(c, &c->user, "cookie", cookie, (char *)0),
329                 &c->user))
330       return 0;                         /* success */
331     if(!username) {
332       c->last = "cookie failed and no username";
333       error(0, "cookie did not work and no username available");
334       goto error;
335     }
336   }
337   if(!(res = authhash(nonce, nl, password, algorithm))) {
338     c->last = "error computing authorization hash";
339     goto error;
340   }
341   if((rc = disorder_simple(c, 0, "user", username, res, (char *)0)))
342     goto error_rc;
343   c->user = xstrdup(username);
344   return 0;
345 error:
346   rc = -1;
347 error_rc:
348   if(c->fpin) {
349     fclose(c->fpin);
350     c->fpin = 0;
351   }
352   if(c->fpout) {
353     fclose(c->fpout);
354     c->fpout = 0;
355   }
356   if(fd2 != -1) close(fd2);
357   if(fd != -1) close(fd);
358   return rc;
359 }
360
361 /** @brief Connect a client with a specified username and password
362  * @param c Client
363  * @param username Username to log in with
364  * @param password Password to log in with
365  * @return 0 on success, non-0 on error
366  */
367 int disorder_connect_user(disorder_client *c,
368                           const char *username,
369                           const char *password) {
370   return disorder_connect_generic(c,
371                                   username,
372                                   password,
373                                   0);
374 }
375
376 /** @brief Connect a client
377  * @param c Client
378  * @return 0 on success, non-0 on error
379  *
380  * The connection will use the username and password found in @ref
381  * config, or directly from the database if no password is found and
382  * the database is readable (usually only for root).
383  */
384 int disorder_connect(disorder_client *c) {
385   const char *username, *password;
386
387   if(!(username = config->username)) {
388     c->last = "no username";
389     error(0, "no username configured");
390     return -1;
391   }
392   password = config->password;
393   /* Maybe we can read the database */
394   if(!password && trackdb_readable()) {
395     trackdb_init(TRACKDB_NO_RECOVER|TRACKDB_NO_UPGRADE);
396     trackdb_open(TRACKDB_READ_ONLY);
397     password = trackdb_get_password(username);
398     trackdb_close();
399   }
400   if(!password) {
401     /* Oh well */
402     c->last = "no password";
403     error(0, "no password configured");
404     return -1;
405   }
406   return disorder_connect_generic(c,
407                                   username,
408                                   password,
409                                   0);
410 }
411
412 /** @brief Connect a client
413  * @param c Client
414  * @param cookie Cookie to log in with, or NULL
415  * @return 0 on success, non-0 on error
416  *
417  * If @p cookie is NULL or does not work then we attempt to log in as
418  * guest instead (so when the cookie expires only an extra round trip
419  * is needed rathre than a complete new login).
420  */
421 int disorder_connect_cookie(disorder_client *c,
422                             const char *cookie) {
423   return disorder_connect_generic(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, when_str, priority,
1293                          action, va_arg(ap, char *));
1294   else if(!strcmp(action, "set-global"))
1295     rc = disorder_simple(c, 0, when_str, priority,
1296                          action, va_arg(ap, char *), va_arg(ap, char *));
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 */