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