chiark / gitweb /
client.c doxygen; kill some redundant code
[disorder] / lib / client.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder.
1020001c 3 * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
460b9539 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 */
c4e2cfdd
RK
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 */
460b9539 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>
f0feb22e 40#include <pcre.h>
460b9539 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"
5df73aeb 59#include "rights.h"
f0feb22e 60#include "trackdb.h"
460b9539 61
62struct disorder_client {
63 FILE *fpin, *fpout;
64 char *ident;
65 char *user;
66 int verbose;
67};
68
c4e2cfdd
RK
69static 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 */
460b9539 83disorder_client *disorder_new(int verbose) {
84 disorder_client *c = xmalloc(sizeof (struct disorder_client));
85
86 c->verbose = verbose;
87 return c;
88}
89
c4e2cfdd
RK
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 */
460b9539 95static 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
c4e2cfdd
RK
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.
460b9539 125 */
126static 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
c4e2cfdd
RK
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 */
460b9539 158static 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
c4e2cfdd
RK
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().
460b9539 198 */
199static 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
211static 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;
460b9539 217
218 if(!(username = config->username)) {
219 error(0, "no username configured");
220 return -1;
221 }
f0feb22e
RK
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;
460b9539 235 }
236 return disorder_connect_sock(c, sa, len, username, password, ident);
237}
238
c4e2cfdd
RK
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 */
460b9539 246int disorder_connect(disorder_client *c) {
247 return with_sockaddr(c, connect_sock);
248}
249
c4e2cfdd
RK
250static 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) {
637fdea3 256 int fd = -1, fd2 = -1, nrvec;
460b9539 257 unsigned char *nonce;
258 size_t nl;
259 const char *res;
637fdea3 260 char *r, **rvec;
7b32e917 261 const char *protocol, *algorithm, *challenge;
460b9539 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;
637fdea3
RK
293 if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
294 return -1;
7b32e917
RK
295 if(nrvec != 3) {
296 error(0, "cannot parse server greeting %s", r);
297 return -1;
637fdea3 298 }
7b32e917
RK
299 protocol = *rvec++;
300 if(strcmp(protocol, "2")) {
301 error(0, "unknown protocol version: %s", protocol);
637fdea3 302 return -1;
7b32e917
RK
303 }
304 algorithm = *rvec++;
305 challenge = *rvec++;
306 if(!(nonce = unhex(challenge, &nl)))
460b9539 307 return -1;
7b32e917 308 if(!(res = authhash(nonce, nl, password, algorithm))) goto error;
460b9539 309 if(disorder_simple(c, 0, "user", username, res, (char *)0))
310 return -1;
311 c->user = xstrdup(username);
312 return 0;
313error:
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
c4e2cfdd
RK
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 */
460b9539 328int 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
348int 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
c4e2cfdd
RK
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 */
460b9539 359int disorder_play(disorder_client *c, const char *track) {
360 return disorder_simple(c, 0, "play", track, (char *)0);
361}
362
c4e2cfdd
RK
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 */
460b9539 368int disorder_remove(disorder_client *c, const char *track) {
369 return disorder_simple(c, 0, "remove", track, (char *)0);
370}
371
c4e2cfdd
RK
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 */
460b9539 378int 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
c4e2cfdd
RK
385/** @brief Enable play
386 * @param c Client
387 * @return 0 on success, non-0 on error
388 */
460b9539 389int disorder_enable(disorder_client *c) {
390 return disorder_simple(c, 0, "enable", (char *)0);
391}
392
c4e2cfdd
RK
393/** @brief Disable play
394 * @param c Client
395 * @return 0 on success, non-0 on error
396 */
460b9539 397int disorder_disable(disorder_client *c) {
398 return disorder_simple(c, 0, "disable", (char *)0);
399}
400
c4e2cfdd
RK
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 */
460b9539 406int disorder_scratch(disorder_client *c, const char *id) {
407 return disorder_simple(c, 0, "scratch", id, (char *)0);
408}
409
c4e2cfdd
RK
410/** @brief Shut down the server
411 * @param c Client
412 * @return 0 on success, non-0 on error
413 */
460b9539 414int disorder_shutdown(disorder_client *c) {
415 return disorder_simple(c, 0, "shutdown", (char *)0);
416}
417
c4e2cfdd
RK
418/** @brief Make the server re-read its configuration
419 * @param c Client
420 * @return 0 on success, non-0 on error
421 */
460b9539 422int disorder_reconfigure(disorder_client *c) {
423 return disorder_simple(c, 0, "reconfigure", (char *)0);
424}
425
c4e2cfdd
RK
426/** @brief Rescan tracks
427 * @param c Client
428 * @return 0 on success, non-0 on error
429 */
460b9539 430int disorder_rescan(disorder_client *c) {
431 return disorder_simple(c, 0, "rescan", (char *)0);
432}
433
c4e2cfdd
RK
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 */
7b32e917
RK
442static int dequote(int rc, char **rp) {
443 char **rr;
c4e2cfdd 444 if(!rc) {
7b32e917
RK
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
c4e2cfdd
RK
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 */
460b9539 459int disorder_version(disorder_client *c, char **rp) {
7b32e917 460 return dequote(disorder_simple(c, rp, "version", (char *)0), rp);
460b9539 461}
462
463static void client_error(const char *msg,
464 void attribute((unused)) *u) {
465 error(0, "error parsing reply: %s", msg);
466}
467
c4e2cfdd
RK
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 */
460b9539 475int 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
491static 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
c4e2cfdd
RK
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 */
460b9539 524int disorder_recent(disorder_client *c, struct queue_entry **qp) {
525 return disorder_somequeue(c, "recent", qp);
526}
527
c4e2cfdd
RK
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 */
460b9539 535int disorder_queue(disorder_client *c, struct queue_entry **qp) {
536 return disorder_somequeue(c, "queue", qp);
537}
538
c4e2cfdd
RK
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 */
460b9539 547static 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
c4e2cfdd
RK
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 */
460b9539 581static 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
c4e2cfdd
RK
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 */
460b9539 602int 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
c4e2cfdd
RK
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 */
460b9539 615int 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
c4e2cfdd
RK
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 */
460b9539 628int 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
c4e2cfdd
RK
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 */
460b9539 637char *disorder_user(disorder_client *c) {
638 return c->user;
639}
640
c4e2cfdd
RK
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 */
460b9539 648int 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
c4e2cfdd
RK
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 */
460b9539 659int 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
c4e2cfdd
RK
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 */
460b9539 671int disorder_get(disorder_client *c,
672 const char *track, const char *key, char **valuep) {
7b32e917
RK
673 return dequote(disorder_simple(c, valuep, "get", track, key, (char *)0),
674 valuep);
460b9539 675}
676
677static void pref_error_handler(const char *msg,
678 void attribute((unused)) *u) {
679 error(0, "error handling 'prefs' reply: %s", msg);
680}
681
c4e2cfdd
RK
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 */
460b9539 688int 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
c4e2cfdd
RK
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 */
460b9539 717static 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
c4e2cfdd
RK
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 */
460b9539 734int 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
c4e2cfdd
RK
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 */
460b9539 746int 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
c4e2cfdd
RK
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 */
460b9539 761int 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
c4e2cfdd
RK
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 */
460b9539 777int 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
c4e2cfdd
RK
782/** @brief Enable random play
783 * @param c Client
784 * @return 0 on success, non-0 on error
785 */
460b9539 786int disorder_random_enable(disorder_client *c) {
787 return disorder_simple(c, 0, "random-enable", (char *)0);
788}
789
c4e2cfdd
RK
790/** @brief Disable random play
791 * @param c Client
792 * @return 0 on success, non-0 on error
793 */
460b9539 794int disorder_random_disable(disorder_client *c) {
795 return disorder_simple(c, 0, "random-disable", (char *)0);
796}
797
c4e2cfdd
RK
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 */
460b9539 803int 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
c4e2cfdd
RK
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 */
460b9539 816int disorder_stats(disorder_client *c,
817 char ***vecp, int *nvecp) {
818 return disorder_simple_list(c, vecp, nvecp, "stats", (char *)0);
819}
820
c4e2cfdd
RK
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 */
460b9539 827int 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
c4e2cfdd
RK
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 */
460b9539 843int 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
c4e2cfdd
RK
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 */
460b9539 859int 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
c4e2cfdd
RK
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 */
460b9539 877int disorder_part(disorder_client *c, char **partp,
878 const char *track, const char *context, const char *part) {
7b32e917
RK
879 return dequote(disorder_simple(c, partp, "part",
880 track, context, part, (char *)0), partp);
460b9539 881}
882
c4e2cfdd
RK
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 */
460b9539 889int disorder_resolve(disorder_client *c, char **trackp, const char *track) {
7b32e917
RK
890 return dequote(disorder_simple(c, trackp, "resolve", track, (char *)0),
891 trackp);
460b9539 892}
893
c4e2cfdd
RK
894/** @brief Pause the current track
895 * @param c Client
896 * @return 0 on success, non-0 on error
897 */
460b9539 898int disorder_pause(disorder_client *c) {
899 return disorder_simple(c, 0, "pause", (char *)0);
900}
901
c4e2cfdd
RK
902/** @brief Resume the current track
903 * @param c Client
904 * @return 0 on success, non-0 on error
905 */
460b9539 906int disorder_resume(disorder_client *c) {
907 return disorder_simple(c, 0, "resume", (char *)0);
908}
909
c4e2cfdd
RK
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 */
460b9539 916int disorder_tags(disorder_client *c,
917 char ***vecp, int *nvecp) {
918 return disorder_simple_list(c, vecp, nvecp, "tags", (char *)0);
919}
920
c4e2cfdd
RK
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 */
c3be4f19
RK
927int disorder_users(disorder_client *c,
928 char ***vecp, int *nvecp) {
929 return disorder_simple_list(c, vecp, nvecp, "users", (char *)0);
930}
931
c4e2cfdd 932/** @brief Get recently added tracks
2a10b70b 933 * @param c Client
c4e2cfdd 934 * @param vecp Where to store pointer to list (UTF-8)
2a10b70b
RK
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 */
939int 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
c4e2cfdd
RK
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 */
460b9539 954int 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
c4e2cfdd
RK
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 */
460b9539 964int disorder_unset_global(disorder_client *c, const char *key) {
965 return disorder_simple(c, 0, "unset-global", key, (char *)0);
966}
967
c4e2cfdd
RK
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 */
460b9539 974int disorder_get_global(disorder_client *c, const char *key, char **valuep) {
7b32e917
RK
975 return dequote(disorder_simple(c, valuep, "get-global", key, (char *)0),
976 valuep);
460b9539 977}
978
c4e2cfdd
RK
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 */
ca831831
RK
985int 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
c4e2cfdd
RK
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 */
f0feb22e 1009int disorder_adduser(disorder_client *c,
0f55e905
RK
1010 const char *user, const char *password,
1011 const char *rights) {
1012 return disorder_simple(c, 0, "adduser", user, password, rights, (char *)0);
f0feb22e
RK
1013}
1014
c4e2cfdd
RK
1015/** @brief Delete a user
1016 * @param c Client
1017 * @param user Username
1018 * @return 0 on success, non-0 on error
1019 */
f0feb22e
RK
1020int disorder_deluser(disorder_client *c, const char *user) {
1021 return disorder_simple(c, 0, "deluser", user, (char *)0);
1022}
1023
c4e2cfdd
RK
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 */
a55c70c7
RK
1031int disorder_userinfo(disorder_client *c, const char *user, const char *key,
1032 char **valuep) {
7b32e917
RK
1033 return dequote(disorder_simple(c, valuep, "userinfo", user, key, (char *)0),
1034 valuep);
5df73aeb
RK
1035}
1036
c4e2cfdd
RK
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 */
5df73aeb
RK
1044int 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
c4e2cfdd
RK
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 */
ba39faf6
RK
1058int 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
c4e2cfdd
RK
1066/** @brief Confirm a user
1067 * @param c Client
1068 * @param confirm Confirmation string
1069 * @return 0 on success, non-0 on error
1070 */
ba39faf6
RK
1071int disorder_confirm(disorder_client *c, const char *confirm) {
1072 return disorder_simple(c, 0, "confirm", confirm, (char *)0);
1073}
1074
c4e2cfdd
RK
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 */
07969f9b
RK
1080int disorder_make_cookie(disorder_client *c, char **cookiep) {
1081 return dequote(disorder_simple(c, cookiep, "make-cookie", (char *)0),
1082 cookiep);
1083}
1084
460b9539 1085/*
1086Local Variables:
1087c-basic-offset:2
1088comment-column:40
1089End:
1090*/