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