+disorder (3.0.99.dev) unstable; urgency=low
+
+ * Bodge version number
+
+ -- Richard Kettlewell <rjk@greenend.org.uk> Sun, 18 May 2008 21:30:14 +0100
+
disorder (3.0) unstable; urgency=low
* DisOrder 3.0
\fBremove any\fR rights depending on how the
track came to be added to the queue.
.TP
-.B rescan
+.B rescan \fR[\fBwait\fR] \fR[\fBfresh\fR]
Rescan all roots for new or obsolete tracks.
Requires the \fBrescan\fR right.
+.IP
+If the \fBwait\fR flag is present then the response is delayed until the rescan
+completes.
+Otherwise the response arrives immediately.
+This is primarily intended for testing.
+.IP
+If the \fBfresh\fR flag is present a rescan is already underway then a second
+rescan will be started when it completes.
+The default behavior is to piggyback on the existing rescan.
+.IP
+NB that \fBfresh\fR is currently disabled in the server source, so using this
+flag will just provoke an error.
.TP
.B resolve \fITRACK\fR
Resolve a track name, i.e. if this is an alias then return the real track name.
/* trackdb_rescan ************************************************************/
+/** @brief Node in the list of rescan-complete callbacks */
+struct rescanned_node {
+ struct rescanned_node *next;
+ void (*rescanned)(void *ru);
+ void *ru;
+};
+
+/** @brief List of rescan-complete callbacks */
+static struct rescanned_node *rescanned_list;
+
+/** @brief Add a rescan completion callback */
+void trackdb_add_rescanned(void (*rescanned)(void *ru),
+ void *ru) {
+ if(rescanned) {
+ struct rescanned_node *n = xmalloc(sizeof *n);
+ n->next = rescanned_list;
+ n->rescanned = rescanned;
+ n->ru = ru;
+ rescanned_list = n;
+ }
+}
+
/* called when the rescanner terminates */
static int reap_rescan(ev_source attribute((unused)) *ev,
pid_t pid,
/* Our cache of file lookups is out of date now */
cache_clean(&cache_files_type);
eventlog("rescanned", (char *)0);
+ /* Call rescanned callbacks */
+ while(rescanned_list) {
+ void (*rescanned)(void *u) = rescanned_list->rescanned;
+ void *ru = rescanned_list->ru;
+
+ rescanned_list = rescanned_list->next;
+ rescanned(ru);
+ }
return 0;
}
/** @brief Initiate a rescan
* @param ev Event loop or 0 to block
* @param recheck 1 to recheck lengths, 0 to suppress check
+ * @param rescanned Called on completion (if not NULL)
+ * @param u Passed to @p rescanned
*/
-void trackdb_rescan(ev_source *ev, int recheck) {
+void trackdb_rescan(ev_source *ev, int recheck,
+ void (*rescanned)(void *ru),
+ void *ru) {
int w;
if(rescan_pid != -1) {
+ trackdb_add_rescanned(rescanned, ru);
error(0, "rescan already underway");
return;
}
rescan_pid = subprogram(ev, -1, RESCAN,
recheck ? "--check" : "--no-check",
(char *)0);
+ trackdb_add_rescanned(rescanned, ru);
if(ev) {
ev_child(ev, rescan_pid, 0, reap_rescan, 0);
D(("started rescanner"));
return 1;
}
+/** @brief Return true if a rescan is underway */
+int trackdb_rescan_underway(void) {
+ return rescan_pid != -1;
+}
+
/* global prefs **************************************************************/
void trackdb_set_global(const char *name,
/* return a list of tracks containing all of the words given. If you
* ask for only stopwords you get no tracks. */
-void trackdb_rescan(struct ev_source *ev, int recheck);
+void trackdb_rescan(struct ev_source *ev, int recheck,
+ void (*rescanned)(void *ru),
+ void *ru);
/* Start a rescan, if one is not running already */
int trackdb_rescan_cancel(void);
const char *track);
int trackdb_request_random(struct ev_source *ev,
random_callback *callback);
+void trackdb_add_rescanned(void (*rescanned)(void *ru),
+ void *ru);
+int trackdb_rescan_underway(void);
#endif /* TRACKDB_H */
"""
self._simple("reconfigure")
- def rescan(self):
+ def rescan(self, *flags):
"""Rescan one or more collections.
Only trusted users can perform this operation.
"""
- self._simple("rescan")
+ self._simple("rescan", *flags)
def version(self):
"""Return the server's version number."""
+++ /dev/null
-/*
- * This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <config.h>
-#include "types.h"
-
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <stddef.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <pcre.h>
-#include <limits.h>
-#include <fnmatch.h>
-#include <ctype.h>
-
-#include "mem.h"
-#include "log.h"
-#include "hex.h"
-#include "charset.h"
-#include "configuration.h"
-#include "table.h"
-#include "syscalls.h"
-#include "kvp.h"
-#include "vector.h"
-#include "split.h"
-#include "inputline.h"
-#include "regsub.h"
-#include "defs.h"
-#include "sink.h"
-#include "server-cgi.h"
-#include "printf.h"
-#include "mime.h"
-#include "unicode.h"
-#include "hash.h"
-
-void cgi_header(struct sink *output, const char *name, const char *value) {
- sink_printf(output, "%s: %s\r\n", name, value);
-}
-
-void cgi_body(struct sink *output) {
- sink_printf(output, "\r\n");
-}
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
+++ /dev/null
-
-#include <stdio.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
-#include <string.h>
-#include <sys/wait.h>
-#include <pcre.h>
-#include <assert.h>
-
-#include "client.h"
-#include "mem.h"
-#include "vector.h"
-#include "sink.h"
-#include "server-cgi.h"
-#include "log.h"
-#include "configuration.h"
-#include "table.h"
-#include "queue.h"
-#include "plugin.h"
-#include "split.h"
-#include "wstat.h"
-#include "kvp.h"
-#include "syscalls.h"
-#include "printf.h"
-#include "regsub.h"
-#include "defs.h"
-#include "trackname.h"
-#include "charset.h"
-#include "dcgi.h"
-#include "url.h"
-#include "mime.h"
-#include "sendmail.h"
-#include "base64.h"
-
-struct entry {
- const char *path;
- const char *sort;
- const char *display;
-};
-
-static int compare_entry(const void *a, const void *b) {
- const struct entry *ea = a, *eb = b;
-
- return compare_tracks(ea->sort, eb->sort,
- ea->display, eb->display,
- ea->path, eb->path);
-}
-
-static const char *front_url(void) {
- char *url;
- const char *mgmt;
-
- /* preserve management interface visibility */
- if((mgmt = cgi_get("mgmt")) && !strcmp(mgmt, "true")) {
- byte_xasprintf(&url, "%s?mgmt=true", config->url);
- return url;
- }
- return config->url;
-}
-
-static void redirect(struct sink *output) {
- const char *back;
-
- back = cgi_get("back");
- cgi_header(output, "Location", back && *back ? back : front_url());
- header_cookie(output);
- cgi_body(output);
-}
-
-static void expand_template(dcgi_state *ds, cgi_sink *output,
- const char *action) {
- cgi_header(output->sink, "Content-Type", "text/html");
- header_cookie(output->sink);
- cgi_body(output->sink);
- expand(output, action, ds);
-}
-
-/* expansions *****************************************************************/
-
-struct trackinfo_state {
- dcgi_state *ds;
- const struct queue_entry *q;
- long length;
- time_t when;
-};
-
-struct result {
- char *track;
- const char *sort;
-};
-
-static int compare_result(const void *a, const void *b) {
- const struct result *ra = a, *rb = b;
- int c;
-
- if(!(c = strcmp(ra->sort, rb->sort)))
- c = strcmp(ra->track, rb->track);
- return c;
-}
-
-static void exp_stats(int attribute((unused)) nargs,
- char attribute((unused)) **args,
- cgi_sink *output,
- void *u) {
- dcgi_state *ds = u;
- char **v;
-
- cgi_opentag(output->sink, "pre", "class", "stats", (char *)0);
- if(!disorder_stats(ds->g->client, &v, 0)) {
- while(*v)
- cgi_output(output, "%s\n", *v++);
- }
- cgi_closetag(output->sink, "pre");
-}
-
-static char *expandarg(const char *arg, dcgi_state *ds) {
- struct dynstr d;
- cgi_sink output;
-
- dynstr_init(&d);
- output.quote = 0;
- output.sink = sink_dynstr(&d);
- expandstring(&output, arg, ds);
- dynstr_terminate(&d);
- return d.vec;
-}
-
-static void exp_navigate(int attribute((unused)) nargs,
- char **args,
- cgi_sink *output,
- void *u) {
- dcgi_state *ds = u;
- dcgi_state substate;
- const char *path = expandarg(args[0], ds);
- const char *ptr;
- int dirlen;
-
- if(*path) {
- memset(&substate, 0, sizeof substate);
- substate.g = ds->g;
- ptr = path + 1; /* skip root */
- dirlen = 0;
- substate.nav_path = path;
- substate.first = 1;
- while(*ptr) {
- while(*ptr && *ptr != '/')
- ++ptr;
- substate.last = !*ptr;
- substate.nav_len = ptr - path;
- substate.nav_dirlen = dirlen;
- expandstring(output, args[1], &substate);
- dirlen = substate.nav_len;
- if(*ptr) ++ptr;
- substate.first = 0;
- }
- }
-}
-
-static void exp_fullname(int attribute((unused)) nargs,
- char attribute((unused)) **args,
- cgi_sink *output,
- void *u) {
- dcgi_state *ds = u;
- cgi_output(output, "%.*s", ds->nav_len, ds->nav_path);
-}
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#ifndef DCGI_H
-#define DCGI_H
-
-typedef struct dcgi_global {
- disorder_client *client;
- unsigned flags;
-#define DC_QUEUE 0x0001
-#define DC_PLAYING 0x0002
-#define DC_RECENT 0x0004
-#define DC_VOLUME 0x0008
-#define DC_DIRS 0x0010
-#define DC_FILES 0x0020
-#define DC_NEW 0x0040
-#define DC_RIGHTS 0x0080
- struct queue_entry *queue, *playing, *recent;
- int volume_left, volume_right;
- char **files, **dirs;
- int nfiles, ndirs;
- char **new;
- int nnew;
- rights_type rights;
-} dcgi_global;
-
-typedef struct dcgi_state {
- dcgi_global *g;
- struct queue_entry *track;
- struct kvp *pref;
- int index;
- int first, last;
- struct entry *entry;
- /* for searching */
- int ntracks;
- char **tracks;
- /* for @navigate@ */
- const char *nav_path;
- int nav_len, nav_dirlen;
-} dcgi_state;
-
-void disorder_cgi(cgi_sink *output, dcgi_state *ds);
-void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
- const char *msg);
-void disorder_cgi_login(dcgi_state *ds, cgi_sink *output);
-
-extern char *login_cookie;
-
-#endif /* DCGI_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
}
static void periodic_rescan(ev_source *ev_) {
- trackdb_rescan(ev_, 1/*check*/);
+ trackdb_rescan(ev_, 1/*check*/, 0, 0);
}
static void periodic_database_gc(ev_source attribute((unused)) *ev_) {
+++ /dev/null
-/*
- * This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#ifndef SERVER_CGI_H
-#define SERVER_CGI_H
-
-extern struct kvp *cgi_args;
-
-typedef struct {
- int quote;
- struct sink *sink;
-} cgi_sink;
-
-void cgi_parse(void);
-/* parse CGI args */
-
-const char *cgi_get(const char *name);
-/* get an argument */
-
-void cgi_header(struct sink *output, const char *name, const char *value);
-/* output a header. @name@ and @value@ are ASCII. */
-
-void cgi_body(struct sink *output);
-/* indicate the start of the body */
-
-void cgi_output(cgi_sink *output, const char *fmt, ...)
- attribute((format (printf, 2, 3)));
-/* SGML-quote formatted UTF-8 data and write it. Checks errors. */
-
-char *cgi_sgmlquote(const char *s, int raw);
-/* SGML-quote multibyte string @s@ */
-
-void cgi_attr(struct sink *output, const char *name, const char *value);
-/* write an attribute */
-
-void cgi_opentag(struct sink *output, const char *name, ...);
-/* write an open tag, including attribute name-value pairs terminate
- * by (char *)0 */
-
-void cgi_closetag(struct sink *output, const char *name);
-/* write a close tag */
-
-struct cgi_expansion {
- const char *name;
- int minargs, maxargs;
- unsigned flags;
-#define EXP_MAGIC 0x0001
- void (*handler)(int nargs, char **args, cgi_sink *output, void *u);
-};
-
-void cgi_define(const char *name,
- int nargs,
- char **args,
- const char *value);
-
-void cgi_expand(const char *name,
- const struct cgi_expansion *expansions,
- size_t nexpansions,
- cgi_sink *output,
- void *u);
-/* find @name@ and substitute for expansions */
-
-void cgi_expand_string(const char *name,
- const char *template,
- const struct cgi_expansion *expansions,
- size_t nexpansions,
- cgi_sink *output,
- void *u);
-/* same but @template@ is text of template */
-
-char *cgi_makeurl(const char *url, ...);
-/* make up a URL */
-
-const char *cgi_label(const char *key);
-/* look up the translated label @key@ */
-
-int cgi_label_exists(const char *key);
-
-char **cgi_columns(const char *name, int *nheadings);
-/* return the list of columns for @name@ */
-
-const char *cgi_transform(const char *type,
- const char *track,
- const char *context);
-/* transform a track or directory name for display */
-
-void cgi_set_option(const char *name, const char *value);
-/* set an option */
-
-#endif /* SERVER_CGI_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
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 wait = 0, fresh = 0, n;
+
+ /* Parse flags */
+ for(n = 0; n < nvec; ++n) {
+ if(!strcmp(vec[n], "wait"))
+ wait = 1; /* wait for rescan to complete */
+#if 0
+ /* Currently disabled because untested (and hard to test). */
+ else if(!strcmp(vec[n], "fresh"))
+ 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,
+ wait ? "wait" : "",
+ fresh ? "fresh" : "");
+ if(trackdb_rescan_underway()) {
+ if(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 = wait;
+ trackdb_add_rescanned(start_fresh_rescan, c);
+ if(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(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(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,
{ "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 },
/* We only allow for upgrade at startup */
trackdb_open(TRACKDB_CAN_UPGRADE);
if(need_another_rescan)
- trackdb_rescan(ev, 1/*check*/);
+ trackdb_rescan(ev, 1/*check*/, 0, 0);
if(!ret) {
queue_read();
recent_read();
"--user", "root", "edituser", username, "rights", "all"])
def rescan(c=None):
- class rescan_monitor(disorder.monitor):
- def rescanned(self):
- return False
+ print " initiating rescan"
if c is None:
c = disorder.client()
- m = rescan_monitor()
- print " initiating rescan"
- c.rescan()
- print " waiting for rescan to complete"
- m.run()
+ c.rescan('wait')
print " rescan completed"
def stop_daemon():