* This file is part of DisOrder.
* 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 <http://www.gnu.org/licenses/>.
*/
/** @file lib/eclient.c
* @brief Client code for event-driven programs
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 */
operation_callback *opcallback,
void (*completed)(),
void *v,
+ int nbody,
+ char **body,
const char *cmd,
...);
static void log_opcallback(disorder_eclient *c, struct operation *op);
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 ********************************************************************/
/** @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),
/* 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.
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. */
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;
/* Queue up a byte to send */
if(c->state == state_log
&& c->output.nvec == 0
- && time(&now) - c->last_prod > LOG_PROD_INTERVAL) {
+ && xtime(&now) - c->last_prod > LOG_PROD_INTERVAL) {
put(c, "x", 1);
c->last_prod = now;
}
socklen_t len;
D(("start_connect"));
- if((len = find_server(&sa, &c->ident)) == (socklen_t)-1)
+ 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);
return;
}
stash_command(c, 1/*queuejump*/, authuser_opcallback, 0/*completed*/, 0/*v*/,
+ -1/*nbody*/, 0/*body*/,
"user", quoteutf8(config->username), quoteutf8(res),
(char *)0);
}
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);
}
operation_callback *opcallback,
void (*completed)(),
void *v,
+ int nbody,
+ char **body,
int ncmd,
char **cmd) {
struct operation *op = xmalloc(sizeof *op);
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;
operation_callback *opcallback,
void (*completed)(),
void *v,
+ int nbody,
+ char **body,
const char *cmd, va_list ap) {
char *arg;
struct vector vec;
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,
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);
}
D(("list_response_callback"));
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);
}
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);
"play", track, (char *)0);
}
+int disorder_eclient_playafter(disorder_eclient *c,
+ const char *target,
+ int ntracks,
+ const char **tracks,
+ disorder_eclient_no_response *completed,
+ void *v) {
+ struct vector vec;
+ int n;
+
+ if(!target)
+ target = "";
+ vector_init(&vec);
+ vector_append(&vec, (char *)"playafter");
+ vector_append(&vec, (char *)target);
+ for(n = 0; n < ntracks; ++n)
+ vector_append(&vec, (char *)tracks[n]);
+ stash_command_vector(c, 0/*queuejump*/, no_response_opcallback, completed, v,
+ -1, 0, vec.nvec, vec.vec);
+ disorder_eclient_polled(c, 0);
+ return 0;
+}
+
int disorder_eclient_pause(disorder_eclient *c,
disorder_eclient_no_response *completed,
void *v) {
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;
}
"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
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;
}
}
}
+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;
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