From: Richard Kettlewell Date: Sat, 24 May 2008 11:54:12 +0000 (+0100) Subject: Support schedule-* commands from command-line client. Exiguously tested. X-Git-Tag: 4.0~65^2~10 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/758aa6c3c5b1768f35e503a00f374d0c52a55a6a Support schedule-* commands from command-line client. Exiguously tested. --- diff --git a/clients/disorder.c b/clients/disorder.c index 2088bc4..f20dbbc 100644 --- a/clients/disorder.c +++ b/clients/disorder.c @@ -468,6 +468,110 @@ static void cf_setup_guest(char **argv) { exit(EXIT_FAILURE); } +struct scheduled_event { + time_t when; + struct kvp *actiondata; + char *id; +}; + +static int compare_event(const void *av, const void *bv) { + struct scheduled_event *a = (void *)av, *b = (void *)bv; + + /* Primary sort key is the trigger time */ + if(a->when < b->when) + return -1; + else if(a->when > b->when) + return 1; + /* For events that go off at the same time just sort by ID */ + return strcmp(a->id, b->id); +} + +static void cf_schedule_list(char attribute((unused)) **argv) { + char **ids; + int nids, n; + struct scheduled_event *events; + char tb[128]; + const char *action, *key, *value, *priority; + int prichar; + + /* Get all known events */ + if(disorder_schedule_list(getclient(), &ids, &nids)) + exit(EXIT_FAILURE); + events = xcalloc(nids, sizeof *events); + for(n = 0; n < nids; ++n) { + events[n].id = ids[n]; + if(disorder_schedule_get(getclient(), ids[n], &events[n].actiondata)) + exit(EXIT_FAILURE); + events[n].when = atoll(kvp_get(events[n].actiondata, "when")); + } + /* Sort by trigger time */ + qsort(events, nids, sizeof *events, compare_event); + /* Display them */ + for(n = 0; n < nids; ++n) { + strftime(tb, sizeof tb, "%Y-%m-%d %H:%M:%S %Z", localtime(&events[n].when)); + action = kvp_get(events[n].actiondata, "action"); + priority = kvp_get(events[n].actiondata, "priority"); + if(!strcmp(priority, "junk")) + prichar = 'J'; + else if(!strcmp(priority, "normal")) + prichar = 'N'; + else + prichar = '?'; + xprintf("%11s %-25s %c %-8s %s", + events[n].id, tb, prichar, kvp_get(events[n].actiondata, "who"), + action); + if(!strcmp(action, "play")) + xprintf(" %s", + nullcheck(utf82mb(kvp_get(events[n].actiondata, "track")))); + else if(!strcmp(action, "set-global")) { + key = kvp_get(events[n].actiondata, "key"); + value = kvp_get(events[n].actiondata, "value"); + if(value) + xprintf(" %s=%s", + nullcheck(utf82mb(key)), + nullcheck(utf82mb(value))); + else + xprintf(" %s unset", + nullcheck(utf82mb(key))); + } + xprintf("\n"); + } +} + +static void cf_schedule_del(char **argv) { + if(disorder_schedule_del(getclient(), argv[0])) + exit(EXIT_FAILURE); +} + +static void cf_schedule_play(char **argv) { + if(disorder_schedule_add(getclient(), + atoll(argv[0]), + argv[1], + "play", + argv[2])) + exit(EXIT_FAILURE); +} + +static void cf_schedule_set_global(char **argv) { + if(disorder_schedule_add(getclient(), + atoll(argv[0]), + argv[1], + "set-global", + argv[2], + argv[3])) + exit(EXIT_FAILURE); +} + +static void cf_schedule_unset_global(char **argv) { + if(disorder_schedule_add(getclient(), + atoll(argv[0]), + argv[1], + "set-global", + argv[2], + (char *)0)) + exit(EXIT_FAILURE); +} + static const struct command { const char *name; int min, max; @@ -480,7 +584,7 @@ static const struct command { { "allfiles", 1, 2, cf_allfiles, isarg_regexp, "DIR [~REGEXP]", "List all files and directories in DIR" }, { "authorize", 1, 2, cf_authorize, isarg_rights, "USERNAME [RIGHTS]", - "Authorize user USERNAME to connect to the server" }, + "Authorize user USERNAME to connect" }, { "deluser", 1, 1, cf_deluser, 0, "USERNAME", "Delete user USERNAME" }, { "dirs", 1, 2, cf_dirs, isarg_regexp, "DIR [~REGEXP]", @@ -542,6 +646,16 @@ static const struct command { "Resume after a pause" }, { "rtp-address", 0, 0, cf_rtp_address, 0, "", "Report server's broadcast address" }, + { "schedule-del", 1, 1, cf_schedule_del, 0, "EVENT", + "Delete a scheduled event" }, + { "schedule-list", 0, 0, cf_schedule_list, 0, "", + "List scheduled events" }, + { "schedule-play", 3, 3, cf_schedule_play, 0, "WHEN PRI TRACK", + "Play TRACK later" }, + { "schedule-set-global", 4, 4, cf_schedule_set_global, 0, "WHEN PRI NAME VAL", + "Set a global preference later" }, + { "schedule-unset-global", 3, 3, cf_schedule_unset_global, 0, "WHEN PRI NAME", + "Unset a global preference later" }, { "scratch", 0, 0, cf_scratch, 0, "", "Scratch the currently playing track" }, { "scratch-id", 1, 1, cf_scratch, 0, "ID", diff --git a/doc/disorder.1.in b/doc/disorder.1.in index bd22e56..3156eb4 100644 --- a/doc/disorder.1.in +++ b/doc/disorder.1.in @@ -182,6 +182,30 @@ Resume the current track after a pause. .B rtp\-address Report the RTP brodcast address used by the server (if any). .TP +.B schedule-del \fIEVENT\fR +Delete a scheduled event. +.TP +.B schedule-list +List scheduled events. +Each line contains the ID, a timestamp, 'N' or 'J' for normal or junk priority, +the user, the action and action-specific data. +.TP +.B schedule-play \fIWHEN PRIORITY TRACK\fI +Play \fITRACK\fR at time \fIWHEN\fR (which must currently be a raw \fBtime_t\fR +value). +.IP +\fIPRIORITY\fR should be \fBjunk\fR or \fBnormal\fR. +This determines how the event is handled if it becomes due when the server is +down. +Junk events are just discarded in this case, while normal events will be +executed when the server comes back up, even if this is much later. +.TP +.B schedule-set-global \fIWHEN PRIORITY NAME VALUE\fI +Set global preference \fINAME\fR to \fIVALUE\fR at time \fIWHEN\fR. +.TP +.B schedule-unset-global \fIWHEN PRIORITY NAME\fI +Unset global preference \fINAME\fR at time \fIWHEN\fR. +.TP .B scratch Scratch the currently playing track. .TP diff --git a/lib/client.c b/lib/client.c index 4dfd10c..e7fba81 100644 --- a/lib/client.c +++ b/lib/client.c @@ -58,6 +58,7 @@ #include "client-common.h" #include "rights.h" #include "trackdb.h" +#include "kvp.h" /** @brief Client handle contents */ struct disorder_client { @@ -1215,6 +1216,90 @@ int disorder_reminder(disorder_client *c, const char *user) { return disorder_simple(c, 0, "reminder", user, (char *)0); } +/** @brief List scheduled events + * @param c Client + * @param idsp Where to put list of event IDs + * @param nidsp Where to put count of event IDs, or NULL + * @return 0 on success, non-0 on error + */ +int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp) { + return disorder_simple_list(c, idsp, nidsp, "schedule-list", (char *)0); +} + +/** @brief Delete a scheduled event + * @param c Client + * @param id Event ID to delete + * @return 0 on success, non-0 on error + */ +int disorder_schedule_del(disorder_client *c, const char *id) { + return disorder_simple(c, 0, "schedule-del", id, (char *)0); +} + +/** @brief Get details of a scheduled event + * @param c Client + * @param id Event ID + * @param actiondatap Where to put details + * @return 0 on success, non-0 on error + */ +int disorder_schedule_get(disorder_client *c, const char *id, + struct kvp **actiondatap) { + char **lines, **bits; + int rc, nbits; + + *actiondatap = 0; + if((rc = disorder_simple_list(c, &lines, NULL, + "schedule-get", id, (char *)0))) + return rc; + while(*lines) { + if(!(bits = split(*lines++, &nbits, SPLIT_QUOTES, 0, 0))) { + error(0, "invalid schedule-get reply: cannot split line"); + return -1; + } + if(nbits != 2) { + error(0, "invalid schedule-get reply: wrong number of fields"); + return -1; + } + kvp_set(actiondatap, bits[0], bits[1]); + } + return 0; +} + +/** @brief Add a scheduled event + * @param c Client + * @param when When to trigger the event + * @param priority Event priority ("normal" or "junk") + * @param action What action to perform + * @param ... Action-specific arguments + * @return 0 on success, non-0 on error + * + * For action @c "play" the next argument is the track. + * + * For action @c "set-global" next argument is the global preference name + * and the final argument the value to set it to, or (char *)0 to unset it. + */ +int disorder_schedule_add(disorder_client *c, + time_t when, + const char *priority, + const char *action, + ...) { + va_list ap; + char when_str[64]; + int rc; + + snprintf(when_str, sizeof when_str, "%lld", (long long)when); + va_start(ap, action); + if(!strcmp(action, "play")) + rc = disorder_simple(c, 0, when_str, priority, + action, va_arg(ap, char *)); + else if(!strcmp(action, "set-global")) + rc = disorder_simple(c, 0, when_str, priority, + action, va_arg(ap, char *), va_arg(ap, char *)); + else + fatal(0, "unknown action '%s'", action); + va_end(ap); + return rc; +} + /* Local Variables: c-basic-offset:2 diff --git a/lib/client.h b/lib/client.h index 674272c..abadef6 100644 --- a/lib/client.h +++ b/lib/client.h @@ -116,6 +116,15 @@ int disorder_make_cookie(disorder_client *c, char **cookiep); const char *disorder_last(disorder_client *c); int disorder_revoke(disorder_client *c); int disorder_reminder(disorder_client *c, const char *user); +int disorder_schedule_list(disorder_client *c, char ***idsp, int *nidsp); +int disorder_schedule_del(disorder_client *c, const char *id); +int disorder_schedule_get(disorder_client *c, const char *id, + struct kvp **actiondatap); +int disorder_schedule_add(disorder_client *c, + time_t when, + const char *priority, + const char *action, + ...); #endif /* CLIENT_H */ diff --git a/server/schedule.c b/server/schedule.c index 47cb6aa..7776c3c 100644 --- a/server/schedule.c +++ b/server/schedule.c @@ -363,6 +363,7 @@ char **schedule_list(int *neventsp) { int e; struct vector v[1]; + vector_init(v); WITH_TRANSACTION(trackdb_listkeys(trackdb_scheduledb, v, tid)); if(neventsp) *neventsp = v->nvec; diff --git a/tests/schedule.py b/tests/schedule.py index 2e5eaac..40b1b07 100755 --- a/tests/schedule.py +++ b/tests/schedule.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # -import dtest,disorder,time +import dtest,disorder,time,string def test(): """Exercise schedule support""" @@ -35,6 +35,11 @@ def test(): track = "%s/Joe Bloggs/First Album/05:Fifth track.ogg" % dtest.tracks print " scheduling a track for the future" c.schedule_add(now + 4, "normal", "play", track) + print " disorder schedule-list output:" + print string.join(dtest.command(["disorder", + "--config", disorder._configfile, + "--no-per-user-config", + "schedule-list"]), ""), print " waiting for it to play" waited = 0 p = c.playing() @@ -51,6 +56,11 @@ def test(): print " scheduling an enable-random for the future" now = int(time.time()) c.schedule_add(now + 4, "normal", "set-global", "random-play", "yes") + print " disorder schedule-list output:" + print string.join(dtest.command(["disorder", + "--config", disorder._configfile, + "--no-per-user-config", + "schedule-list"]), ""), print " waiting for it to take effect" waited = 0 p = c.playing()