+static int argvlen(char **argv) {
+ char **start = argv;
+
+ while(*argv)
+ ++argv;
+ return argv - start;
+}
+
+static const struct option setup_guest_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "online-registration", no_argument, 0, 'r' },
+ { "no-online-registration", no_argument, 0, 'R' },
+ { 0, 0, 0, 0 }
+};
+
+static void help_setup_guest(void) {
+ xprintf("Usage:\n"
+ " disorder setup-guest [OPTIONS]\n"
+ "Options:\n"
+ " --help, -h Display usage message\n"
+ " --online-registration Enable online registration (default)\n"
+ " --no-online-registration Disable online registration\n");
+ xfclose(stdout);
+ exit(0);
+}
+
+static void cf_setup_guest(char **argv) {
+ int n, online_registration = 1;
+
+ while((n = getopt_long(argvlen(argv) + 1, argv - 1,
+ "hrR", setup_guest_options, 0)) >= 0) {
+ switch(n) {
+ case 'h': help_setup_guest();
+ case 'r': online_registration = 1; break;
+ case 'R': online_registration = 0; break;
+ default: disorder_fatal(0, "invalid option");
+ }
+ }
+ if(online_registration && !config->mail_sender)
+ disorder_fatal(0, "you MUST set mail_sender if you want online registration");
+ if(disorder_adduser(getclient(), "guest", "",
+ online_registration ? "read,register" : "read"))
+ exit(EXIT_FAILURE);
+}
+
+/** @brief A scheduled event read from the server */
+struct scheduled_event {
+ /** @brief When event should occur */
+ time_t when;
+
+ /** @brief Details of action */
+ struct kvp *actiondata;
+
+ /** @brief Event ID */
+ 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_play(getclient(),
+ dateparse(argv[0]),
+ argv[1],
+ argv[2]))
+ exit(EXIT_FAILURE);
+}
+
+static void cf_schedule_set_global(char **argv) {
+ if(disorder_schedule_add_set_global(getclient(),
+ dateparse(argv[0]),
+ argv[1],
+ argv[2],
+ argv[3]))
+ exit(EXIT_FAILURE);
+}
+
+static void cf_schedule_unset_global(char **argv) {
+ if(disorder_schedule_add_unset_global(getclient(),
+ dateparse(argv[0]),
+ argv[1],
+ argv[2]))
+ exit(EXIT_FAILURE);