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;
{ "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]",
"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",
.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
#include "client-common.h"
#include "rights.h"
#include "trackdb.h"
+#include "kvp.h"
/** @brief Client handle contents */
struct disorder_client {
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
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 */
int e;
struct vector v[1];
+ vector_init(v);
WITH_TRANSACTION(trackdb_listkeys(trackdb_scheduledb, v, tid));
if(neventsp)
*neventsp = v->nvec;
# 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"""
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()
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()