X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/3f74b5dcf2eacd39095d5ab0426b5b4874c28bdd..b88fd7912fc173b50b4638fb1e25155999568990:/lib/eclient.c diff --git a/lib/eclient.c b/lib/eclient.c index b434c6d..676aa06 100644 --- a/lib/eclient.c +++ b/lib/eclient.c @@ -1,42 +1,36 @@ /* * This file is part of DisOrder. - * Copyright (C) 2006, 2007 Richard Kettlewell + * Copyright (C) 2006-2008 Richard Kettlewell * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA + * along with this program. If not, see . */ /** @file lib/eclient.c * @brief Client code for event-driven programs */ -#include -#include "types.h" +#include "common.h" #include #include #include #include -#include -#include #include #include #include -#include -#include #include #include +#include #include "log.h" #include "mem.h" @@ -98,6 +92,7 @@ typedef void operation_callback(disorder_eclient *c, struct operation *op); struct operation { struct operation *next; /**< @brief next operation */ char *cmd; /**< @brief command to send or 0 */ + char **body; /**< @brief command body */ operation_callback *opcallback; /**< @brief internal completion callback */ void (*completed)(); /**< @brief user completion callback or 0 */ void *v; /**< @brief data for COMPLETED */ @@ -113,7 +108,7 @@ struct operation { /** @brief Client structure */ struct disorder_eclient { - const char *ident; + char *ident; int fd; /**< @brief connection to server */ enum client_state state; /**< @brief current state */ int authenticated; /**< @brief true when authenicated */ @@ -145,19 +140,18 @@ struct disorder_eclient { * time to time so that we detect broken connections reasonably quickly. The * server just ignores these bytes. */ + + /** @brief Protocol version */ + int protocol; + + /** @brief True if enabled */ + int enabled; }; /* Forward declarations ******************************************************/ -static int start_connect(void *cc, - const struct sockaddr *sa, - socklen_t len, - const char *ident); +static int start_connect(disorder_eclient *c); static void process_line(disorder_eclient *c, char *line); -static int start_connect(void *cc, - const struct sockaddr *sa, - socklen_t len, - const char *ident); static void maybe_connected(disorder_eclient *c); static void authbanner_opcallback(disorder_eclient *c, struct operation *op); @@ -172,6 +166,8 @@ static void stash_command(disorder_eclient *c, operation_callback *opcallback, void (*completed)(), void *v, + int nbody, + char **body, const char *cmd, ...); static void log_opcallback(disorder_eclient *c, struct operation *op); @@ -188,6 +184,15 @@ static void logentry_scratched(disorder_eclient *c, int nvec, char **vec); static void logentry_state(disorder_eclient *c, int nvec, char **vec); static void logentry_volume(disorder_eclient *c, int nvec, char **vec); static void logentry_rescanned(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_add(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_confirm(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_delete(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_edit(disorder_eclient *c, int nvec, char **vec); +static void logentry_rights_changed(disorder_eclient *c, int nvec, char **vec); +static void logentry_adopted(disorder_eclient *c, int nvec, char **vec); +static void logentry_playlist_created(disorder_eclient *c, int nvec, char **vec); +static void logentry_playlist_deleted(disorder_eclient *c, int nvec, char **vec); +static void logentry_playlist_modified(disorder_eclient *c, int nvec, char **vec); /* Tables ********************************************************************/ @@ -204,17 +209,26 @@ struct logentry_handler { /** @brief Table for parsing log entries */ static const struct logentry_handler logentry_handlers[] = { #define LE(X, MIN, MAX) { #X, MIN, MAX, logentry_##X } + LE(adopted, 2, 2), LE(completed, 1, 1), LE(failed, 2, 2), LE(moved, 1, 1), LE(playing, 1, 2), + LE(playlist_created, 2, 2), + LE(playlist_deleted, 1, 1), + LE(playlist_modified, 2, 2), LE(queue, 2, INT_MAX), LE(recent_added, 2, INT_MAX), LE(recent_removed, 1, 1), LE(removed, 1, 2), LE(rescanned, 0, 0), + LE(rights_changed, 1, 1), LE(scratched, 2, 2), LE(state, 1, 1), + LE(user_add, 1, 1), + LE(user_confirm, 1, 1), + LE(user_delete, 1, 1), + LE(user_edit, 2, 2), LE(volume, 2, 2) }; @@ -232,13 +246,10 @@ disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb, c->callbacks = cb; c->u = u; c->opstail = &c->ops; + c->enabled = 1; vector_init(&c->vec); dynstr_init(&c->input); dynstr_init(&c->output); - if(!config->password) { - error(0, "no password set"); - return 0; - } return c; } @@ -273,6 +284,16 @@ void disorder_eclient_close(disorder_eclient *c) { c->log_callbacks->state(c->log_v, c->statebits); } +/** @brief Permit new connection activity */ +void disorder_eclient_enable_connect(disorder_eclient *c) { + c->enabled = 1; +} + +/** @brief Suppress new connection activity */ +void disorder_eclient_disable_connect(disorder_eclient *c) { + c->enabled = 0; +} + /** @brief Return current state */ unsigned long disorder_eclient_state(const disorder_eclient *c) { return c->statebits | (c->state > state_connected ? DISORDER_CONNECTED : 0); @@ -314,6 +335,24 @@ static int protocol_error(disorder_eclient *c, struct operation *op, /* State machine *************************************************************/ +/** @brief Send an operation (into the output buffer) + * @param op Operation to send + */ +static void op_send(struct operation *op) { + disorder_eclient *const c = op->client; + put(c, op->cmd, strlen(op->cmd)); + if(op->body) { + for(int n = 0; op->body[n]; ++n) { + if(op->body[n][0] == '.') + put(c, ".", 1); + put(c, op->body[n], strlen(op->body[n])); + put(c, "\n", 1); + } + put(c, ".\n", 2); + } + op->sent = 1; +} + /** @brief Called when there's something to do * @param c Client * @param mode bitmap of @ref DISORDER_POLL_READ and/or @ref DISORDER_POLL_WRITE. @@ -339,7 +378,15 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { if(c->state == state_disconnected) { D(("state_disconnected")); - with_sockaddr(c, start_connect); + /* If there is no password yet then we cannot connect */ + if(!config->password) { + comms_error(c, "no password is configured"); + c->enabled = 0; + return; + } + /* Only try to connect if enabled */ + if(c->enabled) + start_connect(c); /* might now be state_disconnected (on error), state_connecting (slow * connect) or state_connected (fast connect). If state_disconnected then * we just rely on a periodic callback from the event loop sometime. */ @@ -359,7 +406,7 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { D(("state_connected")); /* We just connected. Initiate the authentication protocol. */ stash_command(c, 1/*queuejump*/, authbanner_opcallback, - 0/*completed*/, 0/*v*/, 0/*cmd*/); + 0/*completed*/, 0/*v*/, -1/*nbody*/, 0/*body*/, 0/*cmd*/); /* We never stay is state_connected very long. We could in principle jump * straight to state_cmdresponse since there's actually no command to * send, but that would arguably be cheating. */ @@ -375,17 +422,13 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { if(c->authenticated) { /* Transmit all unsent operations */ for(op = c->ops; op; op = op->next) { - if(!op->sent) { - put(c, op->cmd, strlen(op->cmd)); - op->sent = 1; - } + if(!op->sent) + op_send(op); } } else { /* Just send the head operation */ - if(c->ops->cmd && !c->ops->sent) { - put(c, c->ops->cmd, strlen(c->ops->cmd)); - c->ops->sent = 1; - } + if(c->ops->cmd && !c->ops->sent) + op_send(c->ops); } /* Awaiting response for the operation at the head of the list */ c->state = state_cmdresponse; @@ -465,14 +508,13 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { } /** @brief Called to start connecting */ -static int start_connect(void *cc, - const struct sockaddr *sa, - socklen_t len, - const char *ident) { - disorder_eclient *c = cc; +static int start_connect(disorder_eclient *c) { + struct sockaddr *sa; + socklen_t len; D(("start_connect")); - c->ident = xstrdup(ident); + if((len = find_server(config, &sa, &c->ident)) == (socklen_t)-1) + return comms_error(c, "cannot look up server"); /* TODO better error */ if(c->fd != -1) { xclose(c->fd); c->fd = -1; @@ -492,7 +534,7 @@ static int start_connect(void *cc, return 0; default: /* Signal the error to the caller. */ - return comms_error(c, "connecting to %s: %s", ident, strerror(errno)); + return comms_error(c, "connecting to %s: %s", c->ident, strerror(errno)); } } else c->state = state_connected; @@ -527,6 +569,7 @@ static void maybe_connected(disorder_eclient *c) { /* Authentication ************************************************************/ +/** @brief Called with the greeting from the server */ static void authbanner_opcallback(disorder_eclient *c, struct operation *op) { size_t nonce_len; @@ -534,7 +577,7 @@ static void authbanner_opcallback(disorder_eclient *c, const char *res; char **rvec; int nrvec; - const char *algo = "SHA1"; + const char *protocol, *algorithm, *challenge; D(("authbanner_opcallback")); if(c->rc / 100 != 2 @@ -545,23 +588,48 @@ static void authbanner_opcallback(disorder_eclient *c, disorder_eclient_close(c); return; } - if(nrvec > 1) { - algo = *rvec++; - --nrvec; + switch(nrvec) { + case 1: + protocol = "1"; + algorithm = "sha1"; + challenge = *rvec++; + break; + case 2: + protocol = "1"; + algorithm = *rvec++; + challenge = *rvec++; + break; + case 3: + protocol = *rvec++; + algorithm = *rvec++; + challenge = *rvec++; + break; + default: + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + disorder_eclient_close(c); + return; } - nonce = unhex(rvec[0], &nonce_len); - res = authhash(nonce, nonce_len, config->password, algo); + c->protocol = atoi(protocol); + if(c->protocol < 1 || c->protocol > 2) { + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + disorder_eclient_close(c); + return; + } + nonce = unhex(challenge, &nonce_len); + res = authhash(nonce, nonce_len, config->password, algorithm); if(!res) { protocol_error(c, op, c->rc, "%s: unknown authentication algorithm '%s'", - c->ident, algo); + c->ident, algorithm); disorder_eclient_close(c); return; } stash_command(c, 1/*queuejump*/, authuser_opcallback, 0/*completed*/, 0/*v*/, + -1/*nbody*/, 0/*body*/, "user", quoteutf8(config->username), quoteutf8(res), (char *)0); } +/** @brief Called with the response to the @c user command */ static void authuser_opcallback(disorder_eclient *c, struct operation *op) { char *r; @@ -570,6 +638,7 @@ static void authuser_opcallback(disorder_eclient *c, if(c->rc / 100 != 2) { /* Wrong password or something. We cannot proceed. */ protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + c->enabled = 0; disorder_eclient_close(c); return; } @@ -580,6 +649,7 @@ static void authuser_opcallback(disorder_eclient *c, if(c->log_callbacks && !(c->ops && c->ops->opcallback == log_opcallback)) /* We are a log client, switch to logging mode */ stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, c->log_v, + -1/*nbody*/, 0/*body*/, "log", (char *)0); } @@ -742,6 +812,8 @@ static void stash_command_vector(disorder_eclient *c, operation_callback *opcallback, void (*completed)(), void *v, + int nbody, + char **body, int ncmd, char **cmd) { struct operation *op = xmalloc(sizeof *op); @@ -760,6 +832,13 @@ static void stash_command_vector(disorder_eclient *c, op->cmd = d.vec; } else op->cmd = 0; /* usually, awaiting challenge */ + if(nbody >= 0) { + op->body = xcalloc(nbody + 1, sizeof (char *)); + for(n = 0; n < nbody; ++n) + op->body[n] = xstrdup(body[n]); + op->body[n] = 0; + } else + op->body = NULL; op->opcallback = opcallback; op->completed = completed; op->v = v; @@ -785,6 +864,8 @@ static void vstash_command(disorder_eclient *c, operation_callback *opcallback, void (*completed)(), void *v, + int nbody, + char **body, const char *cmd, va_list ap) { char *arg; struct vector vec; @@ -796,9 +877,11 @@ static void vstash_command(disorder_eclient *c, while((arg = va_arg(ap, char *))) vector_append(&vec, arg); stash_command_vector(c, queuejump, opcallback, completed, v, - vec.nvec, vec.vec); + nbody, body, vec.nvec, vec.vec); } else - stash_command_vector(c, queuejump, opcallback, completed, v, 0, 0); + stash_command_vector(c, queuejump, opcallback, completed, v, + nbody, body, + 0, 0); } static void stash_command(disorder_eclient *c, @@ -806,49 +889,84 @@ static void stash_command(disorder_eclient *c, operation_callback *opcallback, void (*completed)(), void *v, + int nbody, + char **body, const char *cmd, ...) { va_list ap; va_start(ap, cmd); - vstash_command(c, queuejump, opcallback, completed, v, cmd, ap); + vstash_command(c, queuejump, opcallback, completed, v, nbody, body, cmd, ap); va_end(ap); } /* Command support ***********************************************************/ -/* for commands with a simple string response */ +static const char *errorstring(disorder_eclient *c) { + char *s; + + byte_xasprintf(&s, "%s: %s: %d", c->ident, c->line, c->rc); + return s; +} + +/* for commands with a quoted string response */ static void string_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_string_response *completed + = (disorder_eclient_string_response *)op->completed; + D(("string_response_callback")); - if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_string_response *)op->completed)(op->v, c->line + 4); + if(c->rc / 100 == 2 || c->rc == 555) { + if(op->completed) { + if(c->rc == 555) + completed(op->v, NULL, NULL); + else if(c->protocol >= 2) { + char **rr = split(c->line + 4, 0, SPLIT_QUOTES, 0, 0); + + if(rr && *rr) + completed(op->v, NULL, *rr); + else + /* TODO error message a is bit lame but generally indicates a server + * bug rather than anything the user can address */ + completed(op->v, "error parsing response", NULL); + } else + completed(op->v, NULL, c->line + 4); + } } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), NULL); } /* for commands with a simple integer response */ static void integer_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_integer_response *completed + = (disorder_eclient_integer_response *)op->completed; + D(("string_response_callback")); if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_integer_response *)op->completed) - (op->v, strtol(c->line + 4, 0, 10)); + long n; + int e; + + e = xstrtol(&n, c->line + 4, 0, 10); + if(e) + completed(op->v, strerror(e), 0); + else + completed(op->v, 0, n); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0); } /* for commands with no response */ static void no_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_no_response *completed + = (disorder_eclient_no_response *)op->completed; + D(("no_response_callback")); - if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_no_response *)op->completed)(op->v); - } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + if(c->rc / 100 == 2) + completed(op->v, NULL); + else + completed(op->v, errorstring(c)); } /* error callback for queue_unmarshall */ @@ -856,13 +974,17 @@ static void eclient_queue_error(const char *msg, void *u) { struct operation *op = u; + /* TODO don't use protocol_error here */ protocol_error(op->client, op, -1, "error parsing queue entry: %s", msg); } /* for commands that expect a queue dump */ static void queue_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_queue_response *const completed + = (disorder_eclient_queue_response *)op->completed; int n; + int parse_failed = 0; struct queue_entry *q, *qh = 0, **qtail = &qh, *qlast = 0; D(("queue_response_callback")); @@ -871,22 +993,29 @@ static void queue_response_opcallback(disorder_eclient *c, for(n = 0; n < c->vec.nvec; ++n) { q = xmalloc(sizeof *q); D(("queue_unmarshall %s", c->vec.vec[n])); - if(!queue_unmarshall(q, c->vec.vec[n], eclient_queue_error, op)) { + if(!queue_unmarshall(q, c->vec.vec[n], NULL, op)) { q->prev = qlast; *qtail = q; qtail = &q->next; qlast = q; - } + } else + parse_failed = 1; } - if(op->completed) - ((disorder_eclient_queue_response *)op->completed)(op->v, qh); + /* Currently we pass the partial queue to the callback along with the + * error. This might not be very useful in practice... */ + if(parse_failed) + completed(op->v, "cannot parse result", qh); + else + completed(op->v, 0, qh); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0); } /* for 'playing' */ static void playing_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_queue_response *const completed + = (disorder_eclient_queue_response *)op->completed; struct queue_entry *q; D(("playing_response_callback")); @@ -894,51 +1023,54 @@ static void playing_response_opcallback(disorder_eclient *c, switch(c->rc % 10) { case 2: if(queue_unmarshall(q = xmalloc(sizeof *q), c->line + 4, - eclient_queue_error, c)) - return; + NULL, c)) + completed(op->v, "cannot parse result", 0); + else + completed(op->v, 0, q); break; case 9: - q = 0; + completed(op->v, 0, 0); break; default: - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); - return; + completed(op->v, errorstring(c), 0); + break; } - if(op->completed) - ((disorder_eclient_queue_response *)op->completed)(op->v, q); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0); } /* for commands that expect a list of some sort */ static void list_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_list_response *const completed = + (disorder_eclient_list_response *)op->completed; + D(("list_response_callback")); - if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_list_response *)op->completed)(op->v, - c->vec.nvec, - c->vec.vec); - } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + if(c->rc / 100 == 2) + completed(op->v, NULL, c->vec.nvec, c->vec.vec); + else if(c->rc == 555) + completed(op->v, NULL, -1, NULL); + else + completed(op->v, errorstring(c), 0, 0); } /* for volume */ static void volume_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_volume_response *completed + = (disorder_eclient_volume_response *)op->completed; int l, r; D(("volume_response_callback")); if(c->rc / 100 == 2) { if(op->completed) { if(sscanf(c->line + 4, "%d %d", &l, &r) != 2 || l < 0 || r < 0) - protocol_error(c, op, -1, "%s: invalid volume response: %s", - c->ident, c->line); + completed(op->v, "cannot parse volume response", 0, 0); else - ((disorder_eclient_volume_response *)op->completed)(op->v, l, r); + completed(op->v, 0, l, r); } } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0, 0); } static int simple(disorder_eclient *c, @@ -949,7 +1081,24 @@ static int simple(disorder_eclient *c, va_list ap; va_start(ap, cmd); - vstash_command(c, 0/*queuejump*/, opcallback, completed, v, cmd, ap); + vstash_command(c, 0/*queuejump*/, opcallback, completed, v, -1, 0, cmd, ap); + va_end(ap); + /* Give the state machine a kick, since we might be in state_idle */ + disorder_eclient_polled(c, 0); + return 0; +} + +static int simple_body(disorder_eclient *c, + operation_callback *opcallback, + void (*completed)(), + void *v, + int nbody, + char **body, + const char *cmd, ...) { + va_list ap; + + va_start(ap, cmd); + vstash_command(c, 0/*queuejump*/, opcallback, completed, v, nbody, body, cmd, ap); va_end(ap); /* Give the state machine a kick, since we might be in state_idle */ disorder_eclient_polled(c, 0); @@ -1034,7 +1183,7 @@ int disorder_eclient_moveafter(disorder_eclient *c, for(n = 0; n < nids; ++n) vector_append(&vec, (char *)ids[n]); stash_command_vector(c, 0/*queuejump*/, no_response_opcallback, completed, v, - vec.nvec, vec.vec); + -1, 0, vec.nvec, vec.vec); disorder_eclient_polled(c, 0); return 0; } @@ -1206,16 +1355,19 @@ int disorder_eclient_new_tracks(disorder_eclient *c, static void rtp_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_list_response *const completed = + (disorder_eclient_list_response *)op->completed; D(("rtp_response_opcallback")); if(c->rc / 100 == 2) { - if(op->completed) { - int nvec; - char **vec = split(c->line + 4, &nvec, SPLIT_QUOTES, 0, 0); + int nvec; + char **vec = split(c->line + 4, &nvec, SPLIT_QUOTES, 0, 0); - ((disorder_eclient_list_response *)op->completed)(op->v, nvec, vec); - } + if(vec) + completed(op->v, NULL, nvec, vec); + else + completed(op->v, "error parsing response", 0, 0); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0, 0); } /** @brief Determine the RTP target address @@ -1233,6 +1385,217 @@ int disorder_eclient_rtp_address(disorder_eclient *c, "rtp-address", (char *)0); } +/** @brief Get the list of users + * @param c Client + * @param completed Called with list of users + * @param v Passed to @p completed + * + * The user list is not sorted in any particular order. + */ +int disorder_eclient_users(disorder_eclient *c, + disorder_eclient_list_response *completed, + void *v) { + return simple(c, list_response_opcallback, (void (*)())completed, v, + "users", (char *)0); +} + +/** @brief Delete a user + * @param c Client + * @param completed Called on completion + * @param user User to delete + * @param v Passed to @p completed + */ +int disorder_eclient_deluser(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *user, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "deluser", user, (char *)0); +} + +/** @brief Get a user property + * @param c Client + * @param completed Called on completion + * @param user User to look up + * @param property Property to look up + * @param v Passed to @p completed + */ +int disorder_eclient_userinfo(disorder_eclient *c, + disorder_eclient_string_response *completed, + const char *user, + const char *property, + void *v) { + return simple(c, string_response_opcallback, (void (*)())completed, v, + "userinfo", user, property, (char *)0); +} + +/** @brief Modify a user property + * @param c Client + * @param completed Called on completion + * @param user User to modify + * @param property Property to modify + * @param value New property value + * @param v Passed to @p completed + */ +int disorder_eclient_edituser(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *user, + const char *property, + const char *value, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "edituser", user, property, value, (char *)0); +} + +/** @brief Create a new user + * @param c Client + * @param completed Called on completion + * @param user User to create + * @param password Initial password + * @param rights Initial rights or NULL + * @param v Passed to @p completed + */ +int disorder_eclient_adduser(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *user, + const char *password, + const char *rights, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "adduser", user, password, rights, (char *)0); +} + +/** @brief Adopt a track + * @param c Client + * @param completed Called on completion + * @param id Track ID + * @param v Passed to @p completed + */ +int disorder_eclient_adopt(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *id, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "adopt", id, (char *)0); +} + +/** @brief Get the list of playlists + * @param c Client + * @param completed Called with list of playlists + * @param v Passed to @p completed + * + * The playlist list is not sorted in any particular order. + */ +int disorder_eclient_playlists(disorder_eclient *c, + disorder_eclient_list_response *completed, + void *v) { + return simple(c, list_response_opcallback, (void (*)())completed, v, + "playlists", (char *)0); +} + +/** @brief Delete a playlist + * @param c Client + * @param completed Called on completion + * @param playlist Playlist to delete + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_delete(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *playlist, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "playlist-delete", playlist, (char *)0); +} + +/** @brief Lock a playlist + * @param c Client + * @param completed Called on completion + * @param playlist Playlist to lock + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_lock(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *playlist, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "playlist-lock", playlist, (char *)0); +} + +/** @brief Unlock the locked a playlist + * @param c Client + * @param completed Called on completion + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_unlock(disorder_eclient *c, + disorder_eclient_no_response *completed, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "playlist-unlock", (char *)0); +} + +/** @brief Set a playlist's sharing + * @param c Client + * @param completed Called on completion + * @param playlist Playlist to modify + * @param sharing @c "public" or @c "private" + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_set_share(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *playlist, + const char *sharing, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "playlist-set-share", playlist, sharing, (char *)0); +} + +/** @brief Get a playlist's sharing + * @param c Client + * @param completed Called with sharing status + * @param playlist Playlist to inspect + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_get_share(disorder_eclient *c, + disorder_eclient_string_response *completed, + const char *playlist, + void *v) { + return simple(c, string_response_opcallback, (void (*)())completed, v, + "playlist-get-share", playlist, (char *)0); +} + +/** @brief Set a playlist + * @param c Client + * @param completed Called on completion + * @param playlist Playlist to modify + * @param tracks List of tracks + * @param ntracks Number of tracks + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_set(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *playlist, + char **tracks, + int ntracks, + void *v) { + return simple_body(c, no_response_opcallback, (void (*)())completed, v, + ntracks, tracks, + "playlist-set", playlist, (char *)0); +} + +/** @brief Get a playlist's contents + * @param c Client + * @param completed Called with playlist contents + * @param playlist Playlist to inspect + * @param v Passed to @p completed + */ +int disorder_eclient_playlist_get(disorder_eclient *c, + disorder_eclient_list_response *completed, + const char *playlist, + void *v) { + return simple(c, list_response_opcallback, (void (*)())completed, v, + "playlist-get", playlist, (char *)0); +} + /* Log clients ***************************************************************/ /** @brief Monitor the server log @@ -1257,7 +1620,8 @@ int disorder_eclient_log(disorder_eclient *c, if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits); stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, v, - "log", (char *)0); + -1, 0, "log", (char *)0); + disorder_eclient_polled(c, 0); return 0; } @@ -1272,6 +1636,7 @@ static void log_opcallback(disorder_eclient *c, /* error callback for log line parsing */ static void logline_error(const char *msg, void *u) { disorder_eclient *c = u; + /* TODO don't use protocol_error here */ protocol_error(c, c->ops, -1, "error parsing log line: %s", msg); } @@ -1287,48 +1652,55 @@ static void logline(disorder_eclient *c, const char *line) { * reported */ if(sscanf(vec[0], "%"SCNxMAX, &when) != 1) { /* probably the wrong side of a format change */ + /* TODO don't use protocol_error here */ protocol_error(c, c->ops, -1, "invalid log timestamp '%s'", vec[0]); return; } /* TODO: do something with the time */ - n = TABLE_FIND(logentry_handlers, struct logentry_handler, name, vec[1]); - if(n < 0) return; /* probably a future command */ + //fprintf(stderr, "log key: %s\n", vec[1]); + n = TABLE_FIND(logentry_handlers, name, vec[1]); + if(n < 0) { + //fprintf(stderr, "...not found\n"); + return; /* probably a future command */ + } vec += 2; nvec -= 2; - if(nvec < logentry_handlers[n].min || nvec > logentry_handlers[n].max) + if(nvec < logentry_handlers[n].min || nvec > logentry_handlers[n].max) { + //fprintf(stderr, "...wrong # args\n"); return; + } logentry_handlers[n].handler(c, nvec, vec); } static void logentry_completed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->completed) return; c->statebits &= ~DISORDER_PLAYING; - c->log_callbacks->completed(c->log_v, vec[0]); + if(c->log_callbacks->completed) + c->log_callbacks->completed(c->log_v, vec[0]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } static void logentry_failed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->failed)return; c->statebits &= ~DISORDER_PLAYING; - c->log_callbacks->failed(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->failed) + c->log_callbacks->failed(c->log_v, vec[0], vec[1]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } static void logentry_moved(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->moved) return; - c->log_callbacks->moved(c->log_v, vec[0]); + if(c->log_callbacks->moved) + c->log_callbacks->moved(c->log_v, vec[0]); } static void logentry_playing(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->playing) return; c->statebits |= DISORDER_PLAYING; - c->log_callbacks->playing(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->playing) + c->log_callbacks->playing(c->log_v, vec[0], vec[1]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } @@ -1337,7 +1709,7 @@ static void logentry_queue(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { struct queue_entry *q; - if(!c->log_callbacks->completed) return; + if(!c->log_callbacks->queue) return; q = xmalloc(sizeof *q); if(queue_unmarshall_vec(q, nvec, vec, eclient_queue_error, c)) return; /* bogus */ @@ -1357,32 +1729,86 @@ static void logentry_recent_added(disorder_eclient *c, static void logentry_recent_removed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->recent_removed) return; - c->log_callbacks->recent_removed(c->log_v, vec[0]); + if(c->log_callbacks->recent_removed) + c->log_callbacks->recent_removed(c->log_v, vec[0]); } static void logentry_removed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->removed) return; - c->log_callbacks->removed(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->removed) + c->log_callbacks->removed(c->log_v, vec[0], vec[1]); } static void logentry_rescanned(disorder_eclient *c, int attribute((unused)) nvec, char attribute((unused)) **vec) { - if(!c->log_callbacks->rescanned) return; - c->log_callbacks->rescanned(c->log_v); + if(c->log_callbacks->rescanned) + c->log_callbacks->rescanned(c->log_v); } static void logentry_scratched(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->scratched) return; c->statebits &= ~DISORDER_PLAYING; - c->log_callbacks->scratched(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->scratched) + c->log_callbacks->scratched(c->log_v, vec[0], vec[1]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } +static void logentry_user_add(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_add) + c->log_callbacks->user_add(c->log_v, vec[0]); +} + +static void logentry_user_confirm(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_confirm) + c->log_callbacks->user_confirm(c->log_v, vec[0]); +} + +static void logentry_user_delete(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_delete) + c->log_callbacks->user_delete(c->log_v, vec[0]); +} + +static void logentry_user_edit(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_edit) + c->log_callbacks->user_edit(c->log_v, vec[0], vec[1]); +} + +static void logentry_rights_changed(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->rights_changed) { + rights_type r; + if(!parse_rights(vec[0], &r, 0/*report*/)) + c->log_callbacks->rights_changed(c->log_v, r); + } +} + +static void logentry_playlist_created(disorder_eclient *c, + int attribute((unused)) nvec, + char **vec) { + if(c->log_callbacks->playlist_created) + c->log_callbacks->playlist_created(c->log_v, vec[0], vec[1]); +} + +static void logentry_playlist_deleted(disorder_eclient *c, + int attribute((unused)) nvec, + char **vec) { + if(c->log_callbacks->playlist_deleted) + c->log_callbacks->playlist_deleted(c->log_v, vec[0]); +} + +static void logentry_playlist_modified(disorder_eclient *c, + int attribute((unused)) nvec, + char **vec) { + if(c->log_callbacks->playlist_modified) + c->log_callbacks->playlist_modified(c->log_v, vec[0], vec[1]); +} + static const struct { unsigned long bit; const char *enable; @@ -1409,8 +1835,8 @@ static void logentry_state(disorder_eclient *c, c->statebits &= ~statestrings[n].bit; break; } - if(!c->log_callbacks->state) return; - c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); + if(c->log_callbacks->state) + c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } static void logentry_volume(disorder_eclient *c, @@ -1465,6 +1891,12 @@ char *disorder_eclient_interpret_state(unsigned long statebits) { return d->vec; } +static void logentry_adopted(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->adopted) + c->log_callbacks->adopted(c->log_v, vec[0], vec[1]); +} + /* Local Variables: c-basic-offset:2