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