* USA
*/
-#include <config.h>
-#include "types.h"
-
-#include <pwd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <gcrypt.h>
-#include <stddef.h>
-#include <time.h>
-#include <limits.h>
-#include <pcre.h>
-#include <netdb.h>
-#include <netinet/in.h>
-
-#include "event.h"
-#include "server.h"
-#include "syscalls.h"
-#include "queue.h"
-#include "server-queue.h"
-#include "play.h"
-#include "log.h"
-#include "mem.h"
-#include "state.h"
-#include "charset.h"
-#include "split.h"
-#include "configuration.h"
-#include "hex.h"
-#include "rights.h"
-#include "trackdb.h"
-#include "table.h"
-#include "kvp.h"
-#include "mixer.h"
-#include "sink.h"
-#include "authhash.h"
-#include "plugin.h"
-#include "printf.h"
-#include "trackname.h"
-#include "eventlog.h"
-#include "defs.h"
-#include "cache.h"
-#include "unicode.h"
-#include "cookies.h"
-#include "base64.h"
-#include "hash.h"
-#include "mime.h"
-#include "sendmail.h"
-#include "wstat.h"
+#include "disorder-server.h"
#ifndef NONCE_SIZE
# define NONCE_SIZE 16
rights_type rights;
/** @brief Next connection */
struct conn *next;
+ /** @brief True if pending rescan had 'wait' set */
+ int rescan_wait;
};
/** @brief Linked list of connections */
return 1; /* completed */
}
+static void finished_rescan(void *ru) {
+ struct conn *const c = ru;
+
+ sink_writes(ev_writer_sink(c->w), "250 rescan completed\n");
+ /* Turn this connection back on */
+ ev_reader_enable(c->r);
+}
+
+static void start_fresh_rescan(void *ru) {
+ struct conn *const c = ru;
+
+ if(trackdb_rescan_underway()) {
+ /* Some other waiter beat us to it. However in this case we're happy to
+ * piggyback; the requirement is that a new rescan be started, not that it
+ * was _our_ rescan. */
+ if(c->rescan_wait) {
+ /* We block until the rescan completes */
+ trackdb_add_rescanned(finished_rescan, c);
+ } else {
+ /* We report that the new rescan has started */
+ sink_writes(ev_writer_sink(c->w), "250 rescan initiated\n");
+ /* Turn this connection back on */
+ ev_reader_enable(c->r);
+ }
+ } else {
+ /* We are the first connection to get a callback so we must start a
+ * rescan. */
+ if(c->rescan_wait) {
+ /* We want to block until the new rescan completes */
+ trackdb_rescan(c->ev, 1/*check*/, finished_rescan, c);
+ } else {
+ /* We can report back immediately */
+ trackdb_rescan(c->ev, 1/*check*/, 0, 0);
+ sink_writes(ev_writer_sink(c->w), "250 rescan initiated\n");
+ /* Turn this connection back on */
+ ev_reader_enable(c->r);
+ }
+ }
+}
+
static int c_rescan(struct conn *c,
- char attribute((unused)) **vec,
- int attribute((unused)) nvec) {
- info("S%x rescan by %s", c->tag, c->who);
- trackdb_rescan(c->ev, 1/*check*/);
- sink_writes(ev_writer_sink(c->w), "250 initiated rescan\n");
- return 1; /* completed */
+ char **vec,
+ int nvec) {
+ int flag_wait = 0, flag_fresh = 0, n;
+
+ /* Parse flags */
+ for(n = 0; n < nvec; ++n) {
+ if(!strcmp(vec[n], "wait"))
+ flag_wait = 1; /* wait for rescan to complete */
+#if 0
+ /* Currently disabled because untested (and hard to test). */
+ else if(!strcmp(vec[n], "fresh"))
+ flag_fresh = 1; /* don't piggyback underway rescan */
+#endif
+ else {
+ sink_writes(ev_writer_sink(c->w), "550 unknown flag\n");
+ return 1; /* completed */
+ }
+ }
+ /* Report what was requested */
+ info("S%x rescan by %s (%s %s)", c->tag, c->who,
+ flag_wait ? "wait" : "",
+ flag_fresh ? "fresh" : "");
+ if(trackdb_rescan_underway()) {
+ if(flag_fresh) {
+ /* We want a fresh rescan but there is already one underway. Arrange a
+ * callback when it completes and then set off a new one. */
+ c->rescan_wait = flag_wait;
+ trackdb_add_rescanned(start_fresh_rescan, c);
+ if(flag_wait)
+ return 0;
+ else {
+ sink_writes(ev_writer_sink(c->w), "250 rescan queued\n");
+ return 1;
+ }
+ } else {
+ /* There's a rescan underway, and it's acceptable to piggyback on it */
+ if(flag_wait) {
+ /* We want to block until completion. */
+ trackdb_add_rescanned(finished_rescan, c);
+ return 0;
+ } else {
+ /* We don't want to block. So we just report that things are in
+ * hand. */
+ sink_writes(ev_writer_sink(c->w), "250 rescan already underway\n");
+ return 1;
+ }
+ }
+ } else {
+ /* No rescan is underway. fresh is therefore irrelevant. */
+ if(flag_wait) {
+ /* We want to block until completion */
+ trackdb_rescan(c->ev, 1/*check*/, finished_rescan, c);
+ return 0;
+ } else {
+ /* We don't want to block. */
+ trackdb_rescan(c->ev, 1/*check*/, 0, 0);
+ sink_writes(ev_writer_sink(c->w), "250 rescan initiated\n");
+ return 1; /* completed */
+ }
+ }
}
static int c_version(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;
+ const 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;
{ "register", 3, 3, c_register, RIGHT_REGISTER|RIGHT__LOCAL },
{ "reminder", 1, 1, c_reminder, RIGHT__LOCAL },
{ "remove", 1, 1, c_remove, RIGHT_REMOVE__MASK },
- { "rescan", 0, 0, c_rescan, RIGHT_RESCAN },
+ { "rescan", 0, INT_MAX, c_rescan, RIGHT_RESCAN },
{ "resolve", 1, 1, c_resolve, RIGHT_READ },
{ "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, },
sink_writes(ev_writer_sink(c->w), "500 do what?\n");
return 1;
}
- if((n = TABLE_FIND(commands, struct command, name, vec[0])) < 0)
+ if((n = TABLE_FIND(commands, name, vec[0])) < 0)
sink_writes(ev_writer_sink(c->w), "500 unknown command\n");
else {
if(commands[n].rights