From bea6f6d52db537bf2c0dbe826aedcddaa8de482c Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Thu, 10 Apr 2008 19:48:42 +0100 Subject: [PATCH] New disorder-choose program for performing random selection. Currently honors pick_at_random but not the required/prohibited tags prefs, and not used by the server. Organization: Straylight/Edgeware From: Richard Kettlewell --- .bzrignore | 1 + configure.ac | 4 +- lib/trackdb-int.h | 1 + lib/trackdb.c | 30 +++++- server/Makefile.am | 11 ++- server/choose.c | 227 +++++++++++++++++++++++++++++++++++++++++++++ server/rescan.c | 1 + 7 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 server/choose.c diff --git a/.bzrignore b/.bzrignore index 5c8238b..e4a97a8 100644 --- a/.bzrignore +++ b/.bzrignore @@ -146,3 +146,4 @@ examples/disorder.rc scripts/teardown sounds/long.ogg sounds/slap.raw +server/disorder-choose diff --git a/configure.ac b/configure.ac index 091f96a..ece08f1 100644 --- a/configure.ac +++ b/configure.ac @@ -20,9 +20,9 @@ # USA # -AC_INIT([disorder], [3.0], [richard+disorder@sfere.greenend.org.uk]) +AC_INIT([disorder], [3.0+], [richard+disorder@sfere.greenend.org.uk]) AC_CONFIG_AUX_DIR([config.aux]) -AM_INIT_AUTOMAKE(disorder, [3.0]) +AM_INIT_AUTOMAKE(disorder, [3.0+]) AC_CONFIG_SRCDIR([server/disorderd.c]) AM_CONFIG_HEADER([config.h]) diff --git a/lib/trackdb-int.h b/lib/trackdb-int.h index 9ec328e..2528c67 100644 --- a/lib/trackdb-int.h +++ b/lib/trackdb-int.h @@ -102,6 +102,7 @@ int trackdb_delkeydata(DB *db, int trackdb_scan(const char *root, int (*callback)(const char *track, struct kvp *data, + struct kvp *prefs, void *u, DB_TXN *tid), void *u, diff --git a/lib/trackdb.c b/lib/trackdb.c index 8945ec3..1b29740 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -2091,15 +2091,16 @@ char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) { int trackdb_scan(const char *root, int (*callback)(const char *track, struct kvp *data, + struct kvp *prefs, void *u, DB_TXN *tid), void *u, DB_TXN *tid) { DBC *cursor; - DBT k, d; + DBT k, d, pd; const size_t root_len = root ? strlen(root) : 0; int err, cberr; - struct kvp *data; + struct kvp *data, *prefs; const char *track; cursor = trackdb_opencursor(trackdb_tracksdb, tid); @@ -2119,10 +2120,33 @@ int trackdb_scan(const char *root, data = kvp_urldecode(d.data, d.size); if(kvp_get(data, "_path")) { track = xstrndup(k.data, k.size); + /* TODO: trackdb_prefsdb is currently a DB_HASH. This means we have to + * do a lookup for every single track. In fact this is quite quick: + * with around 10,000 tracks a complete scan is around 0.3s on my + * 2.2GHz Athlon. However, if it were a DB_BTREE, we could do the same + * linear walk as we already do over trackdb_tracksdb, and probably get + * even higher performance. That would require upgrade logic to + * translate old databases though. + */ + switch(err = trackdb_prefsdb->get(trackdb_prefsdb, tid, &k, + prepare_data(&pd), 0)) { + case 0: + prefs = kvp_urldecode(pd.data, pd.size); + break; + case DB_NOTFOUND: + prefs = 0; + break; + case DB_LOCK_DEADLOCK: + error(0, "getting prefs: %s", db_strerror(err)); + trackdb_closecursor(cursor); + return err; + default: + fatal(0, "getting prefs: %s", db_strerror(err)); + } /* Advance to the next track before the callback so that the callback * may safely delete the track */ err = cursor->c_get(cursor, &k, &d, DB_NEXT); - if((cberr = callback(track, data, u, tid))) { + if((cberr = callback(track, data, prefs, u, tid))) { err = cberr; break; } diff --git a/server/Makefile.am b/server/Makefile.am index bc58b84..72dcc36 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -20,7 +20,7 @@ sbin_PROGRAMS=disorderd disorder-deadlock disorder-rescan disorder-dump \ disorder-speaker disorder-decode disorder-normalize \ - disorder-stats disorder-dbupgrade + disorder-stats disorder-dbupgrade disorder-choose noinst_PROGRAMS=disorder.cgi trackname AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib @@ -74,6 +74,15 @@ disorder_rescan_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ disorder_rescan_LDFLAGS=-export-dynamic disorder_rescan_DEPENDENCIES=../lib/libdisorder.a +disorder_choose_SOURCES=choose.c \ + api.c api-server.c \ + exports.c \ + ../lib/memgc.c +disorder_choose_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ + $(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBGCRYPT) +disorder_choose_LDFLAGS=-export-dynamic +disorder_choose_DEPENDENCIES=../lib/libdisorder.a + disorder_stats_SOURCES=stats.c disorder_stats_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ $(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) diff --git a/server/choose.c b/server/choose.c new file mode 100644 index 0000000..fa424ee --- /dev/null +++ b/server/choose.c @@ -0,0 +1,227 @@ +/* + * This file is part of DisOrder + * Copyright (C) 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 + */ +/** @file choose.c + * @brief Random track chooser + * + * Picks a track at random and writes it to standard output. If for + * any reason no track can be picked - even a trivial reason like a + * deadlock - it just exits and expects the server to try again. + */ + +#include +#include "types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "configuration.h" +#include "log.h" +#include "defs.h" +#include "mem.h" +#include "kvp.h" +#include "syscalls.h" +#include "printf.h" +#include "trackdb.h" +#include "trackdb-int.h" +#include "version.h" + +static DB_TXN *global_tid; + +static const struct option options[] = { + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "config", required_argument, 0, 'c' }, + { "debug", no_argument, 0, 'd' }, + { "no-debug", no_argument, 0, 'D' }, + { "syslog", no_argument, 0, 's' }, + { "no-syslog", no_argument, 0, 'S' }, + { 0, 0, 0, 0 } +}; + +/* display usage message and terminate */ +static void help(void) { + xprintf("Usage:\n" + " disorder-choose [OPTIONS]\n" + "Options:\n" + " --help, -h Display usage message\n" + " --version, -V Display version number\n" + " --config PATH, -c PATH Set configuration file\n" + " --debug, -d Turn on debugging\n" + " --[no-]syslog Enable/disable logging to syslog\n" + "\n" + "Track choose for DisOrder. Not intended to be run\n" + "directly.\n"); + xfclose(stdout); + exit(0); +} + +/** @brief Weighted track record */ +struct weighted_track { + /** @brief Next track in the list */ + struct weighted_track *next; + /** @brief Track name */ + const char *track; + /** @brief Weight for this track (always positive) */ + unsigned long weight; +}; + +/** @brief List of tracks with nonzero weight */ +static struct weighted_track *tracks; + +/** @brief Sum of all weights */ +static unsigned long long total_weight; + +/** @brief Count of tracks */ +static long ntracks; + +/** @brief Compute the weight of a track + * @param track Track name (UTF-8) + * @param data Track data + * @param prefs Track preferences + * @return Track weight (non-negative) + * + * Tracks to be excluded entirely are given a weight of 0. + */ +static unsigned long compute_weight(const char attribute((unused)) *track, + struct kvp attribute((unused)) *data, + struct kvp *prefs) { + const char *s; + + /* Firstly, tracks with random play disabled always have weight 0 and that's + * that */ + if((s = kvp_get(prefs, "pick_at_random")) + && !strcmp(s, "0")) + return 0; + return 90000; +} + +/** @brief Called for each track */ +static int collect_tracks_callback(const char *track, + struct kvp *data, + struct kvp *prefs, + void attribute((unused)) *u, + DB_TXN attribute((unused)) *tid) { + const unsigned long weight = compute_weight(track, data, prefs); + + if(weight) { + struct weighted_track *const t = xmalloc(sizeof *t); + + t->next = tracks; + t->track = track; + t->weight = weight; + tracks = t; + total_weight += weight; + ++ntracks; + } + return 0; +} + +/** @brief Pick a random integer uniformly from [0, limit) */ +static unsigned long long pick_weight(unsigned long long limit) { + unsigned long long n; + static int fd = -1; + int r; + + if(fd < 0) { + if((fd = open("/dev/urandom", O_RDONLY)) < 0) + fatal(errno, "opening /dev/urandom"); + } + if((r = read(fd, &n, sizeof n)) < 0) + fatal(errno, "reading /dev/urandom"); + if((size_t)r < sizeof n) + fatal(0, "short read from /dev/urandom"); + return n % limit; +} + +/** @brief Pick a track at random and write it to stdout */ +static void pick_track(void) { + long long w; + struct weighted_track *t; + + w = pick_weight(total_weight); + t = tracks; + while(t && w >= t->weight) { + w -= t->weight; + t = t->next; + } + if(!t) + fatal(0, "ran out of tracks but %lld weighting left", w); + xprintf("%s", t->track); +} + +int main(int argc, char **argv) { + int n, logsyslog = !isatty(2); + + set_progname(argv); + mem_init(); + if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale"); + while((n = getopt_long(argc, argv, "hVc:dDSs", options, 0)) >= 0) { + switch(n) { + case 'h': help(); + case 'V': version("disorder-choose"); + case 'c': configfile = optarg; break; + case 'd': debugging = 1; break; + case 'D': debugging = 0; break; + case 'S': logsyslog = 0; break; + case 's': logsyslog = 1; break; + default: fatal(0, "invalid option"); + } + } + if(logsyslog) { + openlog(progname, LOG_PID, LOG_DAEMON); + log_default = &log_syslog; + } + if(config_read(0)) fatal(0, "cannot read configuration"); + /* Generate the candidate track list */ + trackdb_init(TRACKDB_NO_RECOVER); + trackdb_open(TRACKDB_NO_UPGRADE|TRACKDB_READ_ONLY); + global_tid = trackdb_begin_transaction(); + if(trackdb_scan(0, collect_tracks_callback, 0, global_tid)) + exit(1); + trackdb_commit_transaction(global_tid); + trackdb_close(); + trackdb_deinit(); + //info("ntracks=%ld total_weight=%lld", ntracks, total_weight); + if(!total_weight) + fatal(0, "no tracks match random choice criteria"); + /* Pick a track */ + pick_track(); + xfclose(stdout); + return 0; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/server/rescan.c b/server/rescan.c index c715e91..2d390f8 100644 --- a/server/rescan.c +++ b/server/rescan.c @@ -207,6 +207,7 @@ struct recheck_track { /* called for each non-alias track */ static int recheck_list_callback(const char *track, struct kvp attribute((unused)) *data, + struct kvp attribute((unused)) *prefs, void *u, DB_TXN attribute((unused)) *tid) { struct recheck_state *cs = u; -- [mdw]