From 2b33c4cf27ec6f4bd73c16ed1395eab209dec51f Mon Sep 17 00:00:00 2001 Message-Id: <2b33c4cf27ec6f4bd73c16ed1395eab209dec51f.1715151452.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sat, 24 May 2008 11:44:08 +0100 Subject: [PATCH] Server and Python interface now support schedule-* commands. Tests to follow. Organization: Straylight/Edgeware From: Richard Kettlewell --- doc/disorder_protocol.5.in | 55 ++++++++++++++++++++ python/disorder.py.in | 24 +++++++++ server/server.c | 104 +++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) diff --git a/doc/disorder_protocol.5.in b/doc/disorder_protocol.5.in index 0847050..f4c5b2f 100644 --- a/doc/disorder_protocol.5.in +++ b/doc/disorder_protocol.5.in @@ -294,6 +294,61 @@ Requires one of the \fBscratch mine\fR, \fBscratch random\fR or \fBscratch any\fR rights depending on how the track came to be added to the queue. .TP +.B schedule-add \fIWHEN\fR \fIPRIORITY\fR \fIACTION\fR ... +Schedule an event for the future. +.IP +.I WHEN +is the time when it should happen, as \fBtime_t\fR value. +It must refer to a time in the future. +.IP +.I PRIORITY +is the event priority. +This can be \fBnormal\fR, in which case the event will be run at startup if its +time has past, or \fBjunk\fR in which case it will be discarded if it is found +to be in the past at startup. +The meaning of other values is not defined. +.IP +.I ACTION +is the action to perform. +The choice of action determines the meaning of the remaining arguments. +Possible actions are: +.RS +.TP +.B play +Play a track. +The next argument is the track name. +Requires the \fBplay\fR right. +.TP +.B set-global +Set a global preference. +The next argument is the preference name and the final argument is the value to +set it to (omit it to unset it). +Requires the \fBglobal prefs\fR right. +.RE +.IP +You need the right at the point you create the event. +It is not possible to create scheduled events in expectation of a future change +in rights. +.TP +.B schedule-del \fIEVENT\fR +Deletes a scheduled event. +Users can always delete their own scheduled events; with the \fBadmin\fR +right you can delete any event. +.TP +.B schedule-get \fIEVENT\fR +Sends the details of scheduled event \fIEVENT\fR in a response body. +Each line is a pair of strings quoted in the usual way, the first being the key +ane the second the value. +No particular order is used. +.IP +Scheduled events are considered public information. +Right \fBread\fR is sufficient to see details of all events. +.TP +.B schedule-list +Sends the event IDs of all scheduled events in a response body, in no +particular order. +Use \fBschedule-get\fR to get the details of each event. +.TP .B search \fITERMS\fR Search for tracks matching the search terms. The results are put in a response body, one to a line. diff --git a/python/disorder.py.in b/python/disorder.py.in index ef33106..867b901 100644 --- a/python/disorder.py.in +++ b/python/disorder.py.in @@ -883,6 +883,30 @@ class client: """Confirm a user registration""" res, details = self._simple("confirm", confirmation) + def schedule_list(self): + """Get a list of scheduled events """ + self._simple("schedule-list") + return self._body() + + def schedule_del(self, event): + """Delete a scheduled event""" + self._simple("schedule-del", event) + + def schedule_get(self, event): + """Get the details for an event as a dict (returns None if event not found)""" + res, details = self._simple("schedule-get", event) + if res == 555: + return None + d = {} + for line in self._body(): + bits = _split(line) + d[bits[0]] = bits[1] + return d + + def schedule_add(self, when, priority, action, *rest): + """Add a scheduled event""" + self._simple("schedule-add", when, priorty, action, *rest) + ######################################################################## # I/O infrastructure diff --git a/server/server.c b/server/server.c index 60cb869..e8e6a68 100644 --- a/server/server.c +++ b/server/server.c @@ -71,6 +71,7 @@ #include "mime.h" #include "sendmail.h" #include "wstat.h" +#include "schedule.h" #ifndef NONCE_SIZE # define NONCE_SIZE 16 @@ -1506,6 +1507,105 @@ static int c_reminder(struct conn *c, return 0; } +static int c_schedule_list(struct conn *c, + char attribute((unused)) **vec, + int attribute((unused)) nvec) { + char **ids = schedule_list(0); + sink_writes(ev_writer_sink(c->w), "253 ID list follows\n"); + while(*ids) + sink_printf(ev_writer_sink(c->w), "%s\n", *ids++); + sink_writes(ev_writer_sink(c->w), ".\n"); + return 1; /* completed */ +} + +static int c_schedule_get(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + struct kvp *actiondata = schedule_get(vec[0]), *k; + + if(!actiondata) { + sink_writes(ev_writer_sink(c->w), "555 No such event\n"); + return 1; /* completed */ + } + /* Scheduled events are public information. Anyone with RIGHT_READ can see + * them. */ + sink_writes(ev_writer_sink(c->w), "253 Event information follows\n"); + for(k = actiondata; k; k = k->next) + sink_printf(ev_writer_sink(c->w), " %s %s\n", + quoteutf8(k->name), quoteutf8(k->value)); + sink_writes(ev_writer_sink(c->w), ".\n"); + return 1; /* completed */ +} + +static int c_schedule_del(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + struct kvp *actiondata = schedule_get(vec[0]); + + if(!actiondata) { + sink_writes(ev_writer_sink(c->w), "555 No such event\n"); + return 1; /* completed */ + } + /* If you have admin rights you can delete anything. If you don't then you + * can only delete your own scheduled events. */ + if(!(c->rights & RIGHT_ADMIN)) { + const char *who = kvp_get(actiondata, "who"); + + if(!who || !c->who || strcmp(who, c->who)) { + sink_writes(ev_writer_sink(c->w), "551 Not authorized\n"); + return 1; /* completed */ + } + } + if(schedule_del(vec[0])) + sink_writes(ev_writer_sink(c->w), "550 Could not delete scheduled event\n"); + else + sink_writes(ev_writer_sink(c->w), "250 Deleted\n"); + return 1; /* completed */ +} + +static int c_schedule_add(struct conn *c, + char **vec, + int nvec) { + struct kvp *actiondata = 0; + char *id; + + /* Standard fields */ + kvp_set(&actiondata, "who", c->who); + kvp_set(&actiondata, "when", vec[0]); + kvp_set(&actiondata, "priority", vec[1]); + kvp_set(&actiondata, "action", vec[2]); + /* Action-dependent fields */ + if(!strcmp(vec[2], "play")) { + if(nvec != 4) { + sink_writes(ev_writer_sink(c->w), "550 Wrong number of arguments\n"); + return 1; + } + if(!trackdb_exists(vec[3])) { + sink_writes(ev_writer_sink(c->w), "550 Track is not in database\n"); + return 1; + } + kvp_set(&actiondata, "track", vec[3]); + } else if(!strcmp(vec[2], "set-global")) { + if(nvec < 4 || nvec > 5) { + sink_writes(ev_writer_sink(c->w), "550 Wrong number of arguments\n"); + return 1; + } + kvp_set(&actiondata, "key", vec[3]); + if(nvec > 4) + kvp_set(&actiondata, "value", vec[4]); + } else { + sink_writes(ev_writer_sink(c->w), "550 Unknown action\n"); + return 1; + } + /* schedule_add() checks user rights */ + id = schedule_add(c->ev, actiondata); + if(!id) + sink_writes(ev_writer_sink(c->w), "550 Cannot add scheduled event\n"); + else + sink_printf(ev_writer_sink(c->w), "252 %s\n", id); + return 1; +} + static const struct command { /** @brief Command name */ const char *name; @@ -1566,6 +1666,10 @@ static const struct command { { "resume", 0, 0, c_resume, RIGHT_PAUSE }, { "revoke", 0, 0, c_revoke, RIGHT_READ }, { "rtp-address", 0, 0, c_rtp_address, 0 }, + { "schedule-add", 3, INT_MAX, c_schedule_add, RIGHT_READ }, + { "schedule-del", 1, 1, c_schedule_del, RIGHT_READ }, + { "schedule-get", 1, 1, c_schedule_get, RIGHT_READ }, + { "schedule-list", 0, 0, c_schedule_list, RIGHT_READ }, { "scratch", 0, 1, c_scratch, RIGHT_SCRATCH__MASK }, { "search", 1, 1, c_search, RIGHT_READ }, { "set", 3, 3, c_set, RIGHT_PREFS, }, -- [mdw]