From 49a773ebadd79145a20ad434caddcb7c4d5616b3 Mon Sep 17 00:00:00 2001 Message-Id: <49a773ebadd79145a20ad434caddcb7c4d5616b3.1714037830.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sat, 12 Apr 2008 12:00:37 +0100 Subject: [PATCH] Use disorder-choose to pick random tracks. Organization: Straylight/Edgeware From: Richard Kettlewell State and periodic activity handling is reorganized. Periodic activities started from main() are now handled in much more a consistent manner. The server will now check for new track to play once a second, and try to add a new random track once every ten seconds. In addition various bits of play.c attempt these operations when immmediate response is more appropriate, but are now freed from the responsibility of ensuring that they occur at a reasonable frequency. A couple of features have disappeared for now: - disorder_track_random() is gone form the server API. This has been useless for a while now. - The 'gap' configuration item no longer works. I'm not convinced it'll be missed; 0 is by far the most useful setting since otherwise album play is disrupted. --- CHANGES | 7 ++ doc/disorder.3 | 9 -- doc/disorder_config.5.in | 3 + lib/event.c | 2 +- lib/trackdb.c | 258 +++++++++++++-------------------------- lib/trackdb.h | 5 + server/api-server.c | 4 - server/disorderd.c | 101 ++++++++------- server/play.c | 106 +++++++--------- server/play.h | 5 +- server/server.c | 5 +- 11 files changed, 202 insertions(+), 303 deletions(-) diff --git a/CHANGES b/CHANGES index 2e98627..803358c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +* Changes up to version 3.1 + +** Server + +The 'gap' directive will no longer work. It could be restored if there +is real demand. + * Changes up to version 3.0 Important! See README.upgrades when upgrading. diff --git a/doc/disorder.3 b/doc/disorder.3 index f3bbe91..ae54acd 100644 --- a/doc/disorder.3 +++ b/doc/disorder.3 @@ -157,15 +157,6 @@ and are lost if the track is deleted; they should only ever have values that can be regenerated on demand. Other values are stored in the prefs database and never get automatically deleted. -.PP -.nf -\fBconst char *disorder_track_random(void) -.fi -.IP -Returns a pointer to a copy of the name of a randomly chosen track. -Each non-alias track has an equal probability of being chosen. -Aliases are never returned. -Only available in server plugins. .SH "PLUGIN FUNCTIONS" This section describes the functions that you must implement to write various plugins. diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index 3641adf..9027fb1 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -395,6 +395,9 @@ default is. .B gap \fISECONDS\fR Specifies the number of seconds to leave between tracks. The default is 0. +.IP +NB this option currently DOES NOT WORK. If there is genuine demand it might be +reinstated. .TP .B history \fIINTEGER\fR Specifies the number of recently played tracks to remember (including diff --git a/lib/event.c b/lib/event.c index db8b940..0e97596 100644 --- a/lib/event.c +++ b/lib/event.c @@ -1234,7 +1234,7 @@ int ev_writer_flush(ev_writer *w) { /* buffered reader ************************************************************/ -/** @brief Shut down a reader* +/** @brief Shut down a reader * * This is the only path through which we cancel and close the file descriptor. * As with the writer case it is given timeout signature to allow it be diff --git a/lib/trackdb.c b/lib/trackdb.c index ce00d1a..6a93fba 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -156,10 +156,6 @@ static pid_t db_deadlock_pid = -1; /* deadlock manager PID */ static pid_t rescan_pid = -1; /* rescanner PID */ static int initialized, opened; /* state */ -/* tracks matched by required_tags */ -static char **reqtracks; -static size_t nreqtracks; - /* comparison function for keys */ static int compare(DB attribute((unused)) *db_, const DBT *a, const DBT *b) { @@ -1038,7 +1034,6 @@ int trackdb_notice_tid(const char *track, for(n = 0; w[n]; ++n) if((err = register_tag(track, w[n], tid))) return err; - reqtracks = 0; /* only store the tracks.db entry if it has changed */ if(t_changed && (err = trackdb_putdata(trackdb_tracksdb, track, t, tid, 0))) return err; @@ -1096,7 +1091,6 @@ int trackdb_obsolete(const char *track, DB_TXN *tid) { if(trackdb_delkeydata(trackdb_tagsdb, w[n], track, tid) == DB_LOCK_DEADLOCK) return err; - reqtracks = 0; /* update tracks.db */ if(trackdb_delkey(trackdb_tracksdb, track, tid) == DB_LOCK_DEADLOCK) return err; @@ -1458,7 +1452,6 @@ int trackdb_set(const char *track, ++newtags; } } - reqtracks = 0; } } err = 0; @@ -1589,176 +1582,93 @@ int tag_intersection(char **a, char **b) { return 0; } -/* Check whether a track is suitable for random play. Returns 0 if it is, - * DB_NOTFOUND if it is not or DB_LOCK_DEADLOCK if the database gave us - * that. */ -static int check_suitable(const char *track, - DB_TXN *tid, - char **required_tags, - char **prohibited_tags) { - char **track_tags; - time_t last, now; - struct kvp *p, *t; - const char *pick_at_random, *played_time; - - /* don't pick tracks that aren't in any surviving collection (for instance - * you've edited the config but the rescan hasn't done its job yet) */ - if(!find_track_root(track)) { - info("found track not in any collection: %s", track); - return DB_NOTFOUND; - } - /* don't pick aliases - only pick the canonical form */ - if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK) - return DB_LOCK_DEADLOCK; - if(kvp_get(t, "_alias_for")) - return DB_NOTFOUND; - /* check that random play is not suppressed for this track */ - if((pick_at_random = kvp_get(p, "pick_at_random")) - && !strcmp(pick_at_random, "0")) - return DB_NOTFOUND; - /* don't pick a track that's been played in the last 8 hours */ - if((played_time = kvp_get(p, "played_time"))) { - last = atoll(played_time); - now = time(0); - if(now < last + 8 * 3600) /* TODO configurable */ - return DB_NOTFOUND; - } - track_tags = parsetags(kvp_get(p, "tags")); - /* check that no prohibited tag is present for this track */ - if(prohibited_tags && tag_intersection(track_tags, prohibited_tags)) - return DB_NOTFOUND; - /* check that at least one required tags is present for this track */ - if(*required_tags && !tag_intersection(track_tags, required_tags)) - return DB_NOTFOUND; +static pid_t choose_pid = -1; +static int choose_fd; +static random_callback *choose_callback; +static struct dynstr choose_output; +static unsigned choose_complete; +static int choose_status; +#define CHOOSE_RUNNING 1 +#define CHOOSE_READING 2 + +static void choose_finished(ev_source *ev, unsigned which) { + choose_complete |= which; + if(choose_complete != (CHOOSE_RUNNING|CHOOSE_READING)) + return; + choose_pid = -1; + if(choose_status == 0 && choose_output.nvec > 0) { + dynstr_terminate(&choose_output); + choose_callback(ev, xstrdup(choose_output.vec)); + } else + choose_callback(ev, 0); +} + +/** @brief Called when @c disorder-choose terminates */ +static int choose_exited(ev_source *ev, + pid_t attribute((unused)) pid, + int status, + const struct rusage attribute((unused)) *rusage, + void attribute((unused)) *u) { + if(status) + error(0, "disorder-choose %s", wstat(status)); + choose_status = status; + choose_finished(ev, CHOOSE_RUNNING); return 0; } -/* attempt to pick a random non-alias track */ -const char *trackdb_random(int tries) { - DBT key, data; - DB_BTREE_STAT *sp; - int err, n; - DB_TXN *tid; - const char *track, *candidate; - db_recno_t r; - const char *tags; - char **required_tags, **prohibited_tags, **tp; - hash *h; - DBC *c = 0; +/** @brief Called with data from @c disorder-choose pipe */ +static int choose_readable(ev_source *ev, + ev_reader *reader, + void *ptr, + size_t bytes, + int eof, + void attribute((unused)) *u) { + dynstr_append_bytes(&choose_output, ptr, bytes); + ev_reader_consume(reader, bytes); + if(eof) + choose_finished(ev, CHOOSE_READING); + return 0; +} - for(;;) { - tid = trackdb_begin_transaction(); - if((err = trackdb_get_global_tid("required-tags", tid, &tags))) - goto fail; - required_tags = parsetags(tags); - if((err = trackdb_get_global_tid("prohibited-tags", tid, &tags))) - goto fail; - prohibited_tags = parsetags(tags); - track = 0; - if(*required_tags) { - /* Bung all the suitable tracks into a hash and convert to a list of keys - * (to eliminate duplicates). We cache this list since it is possible - * that it will be very large. */ - if(!reqtracks) { - h = hash_new(0); - for(tp = required_tags; *tp; ++tp) { - c = trackdb_opencursor(trackdb_tagsdb, tid); - memset(&key, 0, sizeof key); - key.data = *tp; - key.size = strlen(*tp); - n = 0; - err = c->c_get(c, &key, prepare_data(&data), DB_SET); - while(err == 0) { - hash_add(h, xstrndup(data.data, data.size), 0, - HASH_INSERT_OR_REPLACE); - ++n; - err = c->c_get(c, &key, prepare_data(&data), DB_NEXT_DUP); - } - switch(err) { - case 0: - case DB_NOTFOUND: - break; - case DB_LOCK_DEADLOCK: - goto fail; - default: - fatal(0, "error querying tags.db: %s", db_strerror(err)); - } - trackdb_closecursor(c); - c = 0; - if(!n) - error(0, "required tag %s does not match any tracks", *tp); - } - nreqtracks = hash_count(h); - reqtracks = hash_keys(h); - } - while(nreqtracks && !track && tries-- > 0) { - r = (rand() * (double)nreqtracks / (RAND_MAX + 1.0)); - candidate = reqtracks[r]; - switch(check_suitable(candidate, tid, - required_tags, prohibited_tags)) { - case 0: - track = candidate; - break; - case DB_NOTFOUND: - break; - case DB_LOCK_DEADLOCK: - goto fail; - } - } - } else { - /* No required tags. We pick random record numbers in the database - * instead. */ - switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp, 0)) { - case 0: - break; - case DB_LOCK_DEADLOCK: - error(0, "error querying tracks.db: %s", db_strerror(err)); - goto fail; - default: - fatal(0, "error querying tracks.db: %s", db_strerror(err)); - } - if(!sp->bt_nkeys) - error(0, "cannot pick tracks at random from an empty database"); - while(sp->bt_nkeys && !track && tries-- > 0) { - /* record numbers count from 1 upwards */ - r = 1 + (rand() * (double)sp->bt_nkeys / (RAND_MAX + 1.0)); - memset(&key, sizeof key, 0); - key.flags = DB_DBT_MALLOC; - key.size = sizeof r; - key.data = &r; - switch(err = trackdb_tracksdb->get(trackdb_tracksdb, tid, &key, prepare_data(&data), - DB_SET_RECNO)) { - case 0: - break; - case DB_LOCK_DEADLOCK: - error(0, "error querying tracks.db: %s", db_strerror(err)); - goto fail; - default: - fatal(0, "error querying tracks.db: %s", db_strerror(err)); - } - candidate = xstrndup(key.data, key.size); - switch(check_suitable(candidate, tid, - required_tags, prohibited_tags)) { - case 0: - track = candidate; - break; - case DB_NOTFOUND: - break; - case DB_LOCK_DEADLOCK: - goto fail; - } - } - } - break; -fail: - trackdb_closecursor(c); - c = 0; - trackdb_abort_transaction(tid); - } - trackdb_commit_transaction(tid); - if(!track) - error(0, "could not pick a random track"); - return track; +static int choose_read_error(ev_source *ev, + int errno_value, + void attribute((unused)) *u) { + error(errno_value, "error reading disorder-choose pipe"); + choose_finished(ev, CHOOSE_READING); + return 0; +} + +/** @brief Request a random track + * @param ev Event source + * @param callback Called with random track or NULL + * @return 0 if a request was initiated, else -1 + * + * Initiates a random track choice. @p callback will later be called back with + * the choice (or NULL on error). If a choice is already underway then -1 is + * returned and there will be no additional callback. + * + * The caller shouldn't assume that the track returned actually exists (it + * might be removed between the choice and the callback, or between being added + * to the queue and being played). + */ +int trackdb_request_random(ev_source *ev, + random_callback *callback) { + int p[2]; + + if(choose_pid != -1) + return -1; /* don't run concurrent chooses */ + xpipe(p); + cloexec(p[0]); + choose_pid = subprogram(ev, p[1], "disorder-choose", (char *)0); + choose_fd = p[0]; + xclose(p[1]); + choose_callback = callback; + choose_output.nvec = 0; + choose_complete = 0; + ev_reader_new(ev, p[0], choose_readable, choose_read_error, 0, + "disorder-choose reader"); /* owns p[0] */ + ev_child(ev, choose_pid, 0, choose_exited, 0); /* owns the subprocess */ + return 0; } /* get a track name given the prefs. Set *used_db to 1 if we got the answer @@ -2253,8 +2163,6 @@ void trackdb_set_global(const char *name, who ? who : "-"); eventlog("state", state ? "enable_random" : "disable_random", (char *)0); } - if(!strcmp(name, "required-tags")) - reqtracks = 0; } int trackdb_set_global_tid(const char *name, diff --git a/lib/trackdb.h b/lib/trackdb.h index 804ff5a..e39de6e 100644 --- a/lib/trackdb.h +++ b/lib/trackdb.h @@ -172,6 +172,11 @@ char **trackdb_listusers(void); int trackdb_confirm(const char *user, const char *confirmation, rights_type *rightsp); +typedef void random_callback(struct ev_source *ev, + const char *track); +int trackdb_request_random(struct ev_source *ev, + random_callback *callback); + #endif /* TRACKDB_H */ /* diff --git a/server/api-server.c b/server/api-server.c index b1267f0..bc8144e 100644 --- a/server/api-server.c +++ b/server/api-server.c @@ -49,10 +49,6 @@ int disorder_track_set_data(const char *track, return trackdb_set(track, key, value); } -const char *disorder_track_random(void) { - return trackdb_random(16); -} - /* Local Variables: c-basic-offset:2 diff --git a/server/disorderd.c b/server/disorderd.c index 2fd317d..01d4efe 100644 --- a/server/disorderd.c +++ b/server/disorderd.c @@ -61,10 +61,6 @@ static ev_source *ev; -static void rescan_after(long offset); -static void dbgc_after(long offset); -static void volumecheck_after(long offset); - static const struct option options[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'V' }, @@ -94,6 +90,8 @@ static void help(void) { exit(0); } +/* signals ------------------------------------------------------------------ */ + /* SIGHUP callback */ static int handle_sighup(ev_source attribute((unused)) *ev_, int attribute((unused)) sig, @@ -119,41 +117,57 @@ static int handle_sigterm(ev_source attribute((unused)) *ev_, quit(ev); } -static int rescan_again(ev_source *ev_, - const struct timeval attribute((unused)) *now, - void attribute((unused)) *u) { - trackdb_rescan(ev_, 1/*check*/); - rescan_after(86400); - return 0; -} +/* periodic actions --------------------------------------------------------- */ + +struct periodic_data { + void (*callback)(ev_source *); + int period; +}; -static void rescan_after(long offset) { +static int periodic_callback(ev_source *ev_, + const struct timeval attribute((unused)) *now, + void *u) { struct timeval w; + struct periodic_data *const pd = u; + pd->callback(ev_); gettimeofday(&w, 0); - w.tv_sec += offset; - ev_timeout(ev, 0, &w, rescan_again, 0); -} - -static int dbgc_again(ev_source attribute((unused)) *ev_, - const struct timeval attribute((unused)) *now, - void attribute((unused)) *u) { - trackdb_gc(); - dbgc_after(60); + w.tv_sec += pd->period; + ev_timeout(ev, 0, &w, periodic_callback, pd); return 0; } -static void dbgc_after(long offset) { +/** @brief Create a periodic action + * @param ev Event loop + * @param callback Callback function + * @param period Interval between calls in seconds + * @param immediate If true, call @p callback straight away + */ +static void create_periodic(ev_source *ev_, + void (*callback)(ev_source *), + int period, + int immediate) { struct timeval w; + struct periodic_data *const pd = xmalloc(sizeof *pd); + pd->callback = callback; + pd->period = period; + if(immediate) + callback(ev_); gettimeofday(&w, 0); - w.tv_sec += offset; - ev_timeout(ev, 0, &w, dbgc_again, 0); + w.tv_sec += period; + ev_timeout(ev_, 0, &w, periodic_callback, pd); } -static int volumecheck_again(ev_source attribute((unused)) *ev_, - const struct timeval attribute((unused)) *now, - void attribute((unused)) *u) { +static void periodic_rescan(ev_source *ev_) { + trackdb_rescan(ev_, 1/*check*/); +} + +static void periodic_database_gc(ev_source attribute((unused)) *ev_) { + trackdb_gc(); +} + +static void periodic_volume_check(ev_source attribute((unused)) *ev_) { int l, r; char lb[32], rb[32]; @@ -166,16 +180,14 @@ static int volumecheck_again(ev_source attribute((unused)) *ev_, eventlog("volume", lb, rb, (char *)0); } } - volumecheck_after(60); - return 0; } -static void volumecheck_after(long offset) { - struct timeval w; +static void periodic_play_check(ev_source *ev_) { + play(ev_); +} - gettimeofday(&w, 0); - w.tv_sec += offset; - ev_timeout(ev, 0, &w, volumecheck_again, 0); +static void periodic_add_random(ev_source *ev_) { + add_random_track(ev_); } /* We fix the path to include the bindir and sbindir we were installed into */ @@ -284,17 +296,16 @@ int main(int argc, char **argv) { if(ev_signal(ev, SIGTERM, handle_sigterm, 0)) fatal(0, "ev_signal failed"); /* ignore SIGPIPE */ signal(SIGPIPE, SIG_IGN); - /* Start a rescan straight away */ - trackdb_rescan(ev, 1/*check*/); - /* We'll rescan again after a day */ - rescan_after(86400); - /* periodically tidy up the database */ - dbgc_after(60); - /* periodically check the volume */ - volumecheck_again(0, 0, 0); - /* set initial state */ - add_random_track(); - play(ev); + /* Rescan immediately and then daily */ + create_periodic(ev, periodic_rescan, 86400, 1/*immediate*/); + /* Tidy up the database once a minute */ + create_periodic(ev, periodic_database_gc, 60, 0); + /* Check the volume immediately and then once a minute */ + create_periodic(ev, periodic_volume_check, 60, 1); + /* Check for a playable track once a second */ + create_periodic(ev, periodic_play_check, 1, 0); + /* Try adding a random track immediately and once every ten seconds */ + create_periodic(ev, periodic_add_random, 10, 1); /* enter the event loop */ n = ev_run(ev); /* if we exit the event loop, something must have gone wrong */ diff --git a/server/play.c b/server/play.c index 2b6422d..df9158b 100644 --- a/server/play.c +++ b/server/play.c @@ -174,25 +174,6 @@ void speaker_reload(void) { speaker_send(speaker_fd, &sm); } -/* timeout for play retry */ -static int play_again(ev_source *ev, - const struct timeval attribute((unused)) *now, - void attribute((unused)) *u) { - D(("play_again")); - play(ev); - return 0; -} - -/* try calling play() again after @offset@ seconds */ -static void retry_play(ev_source *ev, int offset) { - struct timeval w; - - D(("retry_play(%d)", offset)); - gettimeofday(&w, 0); - w.tv_sec += offset; - ev_timeout(ev, 0, &w, play_again, 0); -} - /* Called when the currently playing track finishes playing. This * might be because the player finished or because the speaker process * told us so. */ @@ -219,7 +200,10 @@ static void finished(ev_source *ev) { recent_write(); forget_player_pid(playing->id); playing = 0; - if(ev) retry_play(ev, config->gap); + /* Try to play something else */ + /* TODO re-support config->gap? */ + if(ev) + play(ev); } /* Called when a player terminates. */ @@ -531,34 +515,40 @@ void abandon(ev_source attribute((unused)) *ev, speaker_send(speaker_fd, &sm); } -int add_random_track(void) { +/** @brief Called with a new random track + * @param track Track name + */ +static void chosen_random_track(ev_source *ev, + const char *track) { + struct queue_entry *q; + + if(!track) + return; + /* Add the track to the queue */ + q = queue_add(track, 0, WHERE_END); + q->state = playing_random; + D(("picked %p (%s) at random", (void *)q, q->track)); + queue_write(); + /* Maybe a track can now be played */ + play(ev); +} + +/** @brief Maybe add a randomly chosen track + * @param ev Event loop + */ +void add_random_track(ev_source *ev) { struct queue_entry *q; - const char *p; long qlen = 0; - int rc = 0; /* If random play is not enabled then do nothing. */ if(shutting_down || !random_is_enabled()) - return 0; + return; /* Count how big the queue is */ for(q = qhead.next; q != &qhead; q = q->next) ++qlen; - /* Add random tracks until the queue is at the right size */ - while(qlen < config->queue_pad) { - /* Try to pick a random track */ - if(!(p = trackdb_random(16))) { - rc = -1; - break; - } - /* Add it to the end of the queue. */ - q = queue_add(p, 0, WHERE_END); - q->state = playing_random; - D(("picked %p (%s) at random", (void *)q, q->track)); - ++qlen; - } - /* Commit the queue */ - queue_write(); - return rc; + /* If it's smaller than the desired size then add a track */ + if(qlen < config->queue_pad) + trackdb_request_random(ev, chosen_random_track); } /* try to play a track */ @@ -568,17 +558,15 @@ void play(ev_source *ev) { D(("play playing=%p", (void *)playing)); if(shutting_down || playing || !playing_is_enabled()) return; - /* If the queue is empty then add a random track. */ + /* See if there's anything to play */ if(qhead.next == &qhead) { - if(!random_enabled) - return; - if(add_random_track()) { - /* On error, try again in 10s. */ - retry_play(ev, 10); - return; - } - /* Now there must be at least one track in the queue. */ + /* Queue is empty. We could just wait around since there are periodic + * attempts to add a random track anyway. However they are rarer than + * attempts to force a track so we initiate one now. */ + add_random_track(ev); + return; } + /* There must be at least one track in the queue. */ q = qhead.next; /* If random play is disabled but the track is a random one then don't play * it. play() will be called again when random play is re-enabled. */ @@ -593,19 +581,11 @@ void play(ev_source *ev) { queue_played(q); recent_write(); } - if(qhead.next == &qhead) - /* Queue is empty, wait a bit before trying something else (so we don't - * sit there looping madly in the presence of persistent problem). Note - * that we might not reliably get a random track lookahead in this case, - * but if we get here then really there are bigger problems. */ - retry_play(ev, 1); - else - /* More in queue, try again now. */ - play(ev); + /* Oh well, try the next one */ + play(ev); break; case START_SOFTFAIL: - /* Try same track again in a bit. */ - retry_play(ev, 10); + /* We'll try the same track again shortly. */ break; case START_OK: if(q == qhead.next) { @@ -620,7 +600,7 @@ void play(ev_source *ev) { playing->submitter ? playing->submitter : (const char *)0, (const char *)0); /* Maybe add a random track. */ - add_random_track(); + add_random_track(ev); /* If there is another track in the queue prepare it now. This could * potentially be a just-added random track. */ if(qhead.next != &qhead) @@ -638,7 +618,7 @@ int playing_is_enabled(void) { void enable_playing(const char *who, ev_source *ev) { trackdb_set_global("playing", "yes", who); /* Add a random track if necessary. */ - add_random_track(); + add_random_track(ev); play(ev); } @@ -654,7 +634,7 @@ int random_is_enabled(void) { void enable_random(const char *who, ev_source *ev) { trackdb_set_global("random-play", "yes", who); - add_random_track(); + add_random_track(ev); play(ev); } diff --git a/server/play.h b/server/play.h index c402312..aff474c 100644 --- a/server/play.h +++ b/server/play.h @@ -75,9 +75,8 @@ void abandon(ev_source *ev, struct queue_entry *q); /* Abandon a possibly-prepared track. */ -int add_random_track(void); -/* If random play is enabled then try to add a track to the queue. On success - * (including deliberartely doing nothing) return 0. On error return -1. */ +void add_random_track(ev_source *ev); +/* If random play is enabled then try to add a track to the queue. */ #endif /* PLAY_H */ diff --git a/server/server.c b/server/server.c index 67b1fdc..79ac7b1 100644 --- a/server/server.c +++ b/server/server.c @@ -272,9 +272,8 @@ static int c_remove(struct conn *c, char **vec, queue_remove(q, c->who); /* De-prepare the track. */ abandon(c->ev, q); - /* If we removed a random track then add another one. */ - if(q->state == playing_random) - add_random_track(); + /* See about adding a new random track */ + add_random_track(c->ev); /* Prepare whatever the next head track is. */ if(qhead.next != &qhead) prepare(c->ev, qhead.next); -- [mdw]