X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/bca4e2b7171bae3d818248057cdc4ffbb5683bf0..6d9dd8d922285bb78f6d5a77e428eedb8c1a5b3b:/server/actions.c diff --git a/server/actions.c b/server/actions.c index d6d227d..59d6bcc 100644 --- a/server/actions.c +++ b/server/actions.c @@ -17,12 +17,225 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file server/actions.c + * @brief DisOrder web actions + * + * Actions are anything that the web interface does beyond passive template + * expansion and inspection of state recieved from the server. This means + * playing tracks, editing prefs etc but also setting extra headers e.g. to + * auto-refresh the playing list. + */ + +#include "disorder-cgi.h" + +/** @brief Redirect to some other action or URL */ +static void redirect(const char *url) { + /* By default use the 'back' argument */ + if(!url) + url = cgi_get("back"); + if(url) { + if(!strncmp(url, "http", 4)) + /* If the target is not a full URL assume it's the action */ + url = cgi_makeurl(config->url, "action", url, (char *)0); + } else { + /* If back= is not set just go back to the front page */ + url = config->url; + } + if(printf("Location: %s\n" + "%s\n" + "\n", url, dcgi_cookie_header()) < 0) + fatal(errno, "error writing to stdout"); +} -#include -#include "types.h" +/* 'playing' and 'manage' just add a Refresh: header */ +static void act_playing(void) { + long refresh = config->refresh; + long length; + time_t now, fin; + char *url; + const char *action; -/** @brief Login cookie */ -char *login_cookie; + dcgi_lookup(DCGI_PLAYING|DCGI_QUEUE|DCGI_ENABLED|DCGI_RANDOM_ENABLED); + if(dcgi_playing + && dcgi_playing->state == playing_started /* i.e. not paused */ + && !disorder_length(dcgi_client, dcgi_playing->track, &length) + && length + && dcgi_playing->sofar >= 0) { + /* Try to put the next refresh at the start of the next track. */ + time(&now); + fin = now + length - dcgi_playing->sofar + config->gap; + if(now + refresh > fin) + refresh = fin - now; + } + if(dcgi_queue && dcgi_queue->state == playing_isscratch) { + /* next track is a scratch, don't leave more than the inter-track gap */ + if(refresh > config->gap) + refresh = config->gap; + } + if(!dcgi_playing + && ((dcgi_queue + && dcgi_queue->state != playing_random) + || dcgi_random_enabled) + && dcgi_enabled) { + /* no track playing but playing is enabled and there is something coming + * up, must be in a gap */ + if(refresh > config->gap) + refresh = config->gap; + } + if((action = cgi_get("action"))) + url = cgi_makeurl(config->url, "action", action, (char *)0); + else + url = config->url; + if(printf("Content-Type: text/html\n" + "Refresh: %ld;url=%s\n" + "%s\n" + "\n", + refresh, url, dcgi_cookie_header()) < 0) + fatal(errno, "error writing to stdout"); + dcgi_expand("playing"); +} + +static void act_disable(void) { + if(dcgi_client) + disorder_disable(dcgi_client); + redirect(0); +} + +static void act_enable(void) { + if(dcgi_client) + disorder_enable(dcgi_client); + redirect(0); +} + +static void act_random_disable(void) { + if(dcgi_client) + disorder_random_disable(dcgi_client); + redirect(0); +} + +static void act_random_enable(void) { + if(dcgi_client) + disorder_random_enable(dcgi_client); + redirect(0); +} + +static void act_pause(void) { + if(dcgi_client) + disorder_pause(dcgi_client); + redirect(0); +} + +static void act_resume(void) { + if(dcgi_client) + disorder_resume(dcgi_client); + redirect(0); +} + +static void act_remove(void) { + const char *id; + struct queue_entry *q; + + if(dcgi_client) { + if(!(id = cgi_get("id"))) + error(0, "missing 'id' argument"); + else if(!(q = dcgi_findtrack(id))) + error(0, "unknown queue id %s", id); + else switch(q->state) { + case playing_isscratch: + case playing_failed: + case playing_no_player: + case playing_ok: + case playing_quitting: + case playing_scratched: + error(0, "does not make sense to scratch %s", id); + break; + case playing_paused: /* started but paused */ + case playing_started: /* started to play */ + disorder_scratch(dcgi_client, id); + break; + case playing_random: /* unplayed randomly chosen track */ + case playing_unplayed: /* haven't played this track yet */ + disorder_remove(dcgi_client, id); + break; + } + } + redirect(0); +} + +static void act_move(void) { + const char *id, *delta; + struct queue_entry *q; + + if(dcgi_client) { + if(!(id = cgi_get("id"))) + error(0, "missing 'id' argument"); + else if(!(delta = cgi_get("delta"))) + error(0, "missing 'delta' argument"); + else if(!(q = dcgi_findtrack(id))) + error(0, "unknown queue id %s", id); + else switch(q->state) { + case playing_random: /* unplayed randomly chosen track */ + case playing_unplayed: /* haven't played this track yet */ + disorder_move(dcgi_client, id, atol(delta)); + break; + default: + error(0, "does not make sense to scratch %s", id); + break; + } + } + redirect(0); +} + +static void act_play(void) { + const char *track, *dir; + char **tracks; + int ntracks, n; + struct dcgi_entry *e; + + if(dcgi_client) { + if((track = cgi_get("file"))) { + disorder_play(dcgi_client, track); + } else if((dir = cgi_get("dir"))) { + if(disorder_files(dcgi_client, dir, 0, &tracks, &ntracks)) + ntracks = 0; + e = xmalloc(ntracks * sizeof (struct dcgi_entry)); + for(n = 0; n < ntracks; ++n) { + e[n].track = tracks[n]; + e[n].sort = trackname_transform("track", tracks[n], "sort"); + e[n].display = trackname_transform("track", tracks[n], "display"); + } + qsort(e, ntracks, sizeof (struct dcgi_entry), dcgi_compare_entry); + for(n = 0; n < ntracks; ++n) + disorder_play(dcgi_client, e[n].track); + } + } + redirect(0); +} + +static int clamp(int n, int min, int max) { + if(n < min) + return min; + if(n > max) + return max; + return n; +} + +static void act_volume(void) { + const char *l, *r, *d; + int nd; + + if(dcgi_client) { + if((d = cgi_get("delta"))) { + dcgi_lookup(DCGI_VOLUME); + nd = clamp(atoi(d), -255, 255); + disorder_set_volume(dcgi_client, + clamp(dcgi_volume_left + nd, 0, 255), + clamp(dcgi_volume_right + nd, 0, 255)); + } else if((l = cgi_get("left")) && (r = cgi_get("right"))) + disorder_set_volume(dcgi_client, atoi(l), atoi(r)); + } + redirect(0); +} /** @brief Table of actions */ static const struct action { @@ -31,41 +244,57 @@ static const struct action { /** @brief Action handler */ void (*handler)(void); } actions[] = { - { "confirm", act_confirm }, { "disable", act_disable }, - { "edituser", act_edituser }, { "enable", act_enable }, - { "login", act_login }, - { "logout", act_logout }, - { "manage", act_manage }, + { "manage", act_playing }, { "move", act_move }, { "pause", act_pause }, { "play", act_play }, { "playing", act_playing }, - { "prefs", act_prefs }, - { "random-disable", act_random_disable }, - { "random-enable", act_random_enable }, - { "register", act_register }, - { "reminder", act_reminder }, + { "randomdisable", act_random_disable }, + { "randomenable", act_random_enable }, { "remove", act_remove }, { "resume", act_resume }, - { "scratch", act_scratch }, { "volume", act_volume }, }; +/** @brief Check that an action name is valid + * @param name Action + * @return 1 if valid, 0 if not + */ +static int dcgi_valid_action(const char *name) { + int c; + + /* First character must be letter or digit (this also requires there to _be_ + * a first character) */ + if(!isalnum((unsigned char)*name)) + return 0; + /* Only letters, digits, '.' and '-' allowed */ + while((c = (unsigned char)*name++)) { + if(!(isalnum(c) + || c == '.' + || c == '_')) + return 0; + } + return 1; +} + /** @brief Expand a template * @param name Base name of template, or NULL to consult CGI args */ -void disorder_cgi_expand(const char *name) { - const char *p; - +void dcgi_expand(const char *name) { + const char *p, *found; + + /* Parse macros first */ + if((found = mx_find("macros.tmpl"))) + mx_expand_file(found, sink_discard(), 0); /* For unknown actions check that they aren't evil */ - for(p = name; *p && isalnum((unsigned char)*p); ++p) - ; - if(*p) - fatal(0, "invalid action name '%s'", action); - byte_xasprintf((char **)&p, "%s.tmpl", action); - if(mx_expand_file(p, sink_stdio(stdout), 0) == -1 + if(!dcgi_valid_action(name)) + fatal(0, "invalid action name '%s'", name); + byte_xasprintf((char **)&p, "%s.tmpl", name); + if(!(found = mx_find(p))) + fatal(errno, "cannot find %s", p); + if(mx_expand_file(found, sink_stdio("stdout", stdout), 0) == -1 || fflush(stdout) < 0) fatal(errno, "error writing to stdout"); } @@ -75,9 +304,8 @@ void disorder_cgi_expand(const char *name) { * * If no recognized action is specified then 'playing' is assumed. */ -void disorder_cgi_action(const char *action) { +void dcgi_action(const char *action) { int n; - char *s; /* Consult CGI args if caller had no view */ if(!action) @@ -91,37 +319,26 @@ void disorder_cgi_action(const char *action) { action = "confirm"; else action = "playing"; + /* Make sure 'action' is always set */ + cgi_set("action", action); } if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) /* Its a known action */ actions[n].handler(); - else + else { /* Just expand the template */ - disorder_cgi_expand(action); + if(printf("Content-Type: text/html\n" + "%s\n" + "\n", dcgi_cookie_header()) < 0) + fatal(errno, "error writing to stdout"); + dcgi_expand(action); + } } /** @brief Generate an error page */ -void disorder_cgi_error(const char *msg, ...) { - va_list ap; - - va_start(ap, msg); - byte_xvasprintf(&error_string, msg, ap); - va_end(ap); - disorder_cgi_expand("error"); -} - -/** @brief Log in as the current user or guest if none */ -void disorder_cgi_login(dcgi_state *ds, struct sink *output) { - /* Junk old data */ - disorder_macros_reset(); - /* Reconnect */ - if(disorder_connect_cookie(client, login_cookie)) { - disorder_cgi_error("Cannot connect to server"); - exit(0); - } - /* If there was a cookie but it went bad, we forget it */ - if(login_cookie && !strcmp(disorder_user(>client), "guest")) - login_cookie = 0; +void dcgi_error(const char *key) { + dcgi_error_string = xstrdup(key); + dcgi_expand("error"); } /*