The response contains the queue ID of the track.
Requires the \fBplay\fR right.
.TP
+.B playafter \fITARGET\fR \fITRACK\fR ...
+Add all the tracks in the \fITRACK\fR list to the queue after \fITARGET\fR
+(which should be a track ID).
+If \fITARGET\fR is the empty string then the listed tracks are put
+at the head of the queue.
+.IP
+Currently the success result does \fInot\fR include the new track IDs.
+.IP
+Requires the \fBplay\fR right.
+.TP
.B playing
Report what track is playing.
.IP
"play", track, (char *)0);
}
+int disorder_eclient_playafter(disorder_eclient *c,
+ const char *target,
+ int ntracks,
+ const char **tracks,
+ disorder_eclient_no_response *completed,
+ void *v) {
+ struct vector vec;
+ int n;
+
+ if(!target)
+ target = "";
+ vector_init(&vec);
+ vector_append(&vec, (char *)"playafter");
+ vector_append(&vec, (char *)target);
+ for(n = 0; n < ntracks; ++n)
+ vector_append(&vec, (char *)tracks[n]);
+ stash_command_vector(c, 0/*queuejump*/, no_response_opcallback, completed, v,
+ -1, 0, vec.nvec, vec.vec);
+ disorder_eclient_polled(c, 0);
+ return 0;
+}
+
int disorder_eclient_pause(disorder_eclient *c,
disorder_eclient_no_response *completed,
void *v) {
void *v);
/* add a track to the queue */
+int disorder_eclient_playafter(disorder_eclient *c,
+ const char *target,
+ int ntracks,
+ const char **tracks,
+ disorder_eclient_no_response *completed,
+ void *v);
+/* insert multiple tracks to an arbitrary point in the queue */
+
int disorder_eclient_pause(disorder_eclient *c,
disorder_eclient_no_response *completed,
void *v);
/*
* This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 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
#define VALUE(q, offset, type) *(type *)((char *)q + offset)
-/* add new entry @n@ to a doubly linked list just after @b@ */
+/** @brief Insert queue entry @p n just after @p b
+ * @param b Insert after this entry
+ * @param n New entry to insert
+ */
void queue_insert_entry(struct queue_entry *b, struct queue_entry *n) {
n->prev = b;
n->next = b->next;
res, details = self._simple("play", track)
return unicode(details) # because it's unicode in queue() output
+ def playafter(self, target, tracks):
+ """Insert tracks into a specific point in the queue.
+
+ Arguments:
+ target -- target ID or None to insert at start of queue
+ tracks -- a list of tracks to play"""
+ if target is None:
+ target = ''
+ self._simple("playafter", target, *tracks)
+
def remove(self, track):
"""Remove a track from the queue.
/* write the recently played list out. Calls @fatal@ on error. */
struct queue_entry *queue_add(const char *track, const char *submitter,
- int where, enum track_origin origin);
+ int where, const char *target,
+ enum track_origin origin);
#define WHERE_START 0 /* Add to head of queue */
#define WHERE_END 1 /* Add to end of queue */
#define WHERE_BEFORE_RANDOM 2 /* End, or before random track */
+#define WHERE_AFTER 3 /* After the target */
/* add an entry to the queue. Return a pointer to the new entry. */
void queue_remove(struct queue_entry *q, const char *who);
if(!track)
return;
/* Add the track to the queue */
- q = queue_add(track, 0, WHERE_END, origin_random);
+ q = queue_add(track, 0, WHERE_END, NULL, origin_random);
D(("picked %p (%s) at random", (void *)q, q->track));
queue_write();
/* Maybe a track can now be played */
* bother if playing is disabled) */
if(playing_is_enabled() && config->scratch.n) {
int r = rand() * (double)config->scratch.n / (RAND_MAX + 1.0);
- q = queue_add(config->scratch.s[r], who, WHERE_START, origin_scratch);
+ q = queue_add(config->scratch.s[r], who, WHERE_START, NULL,
+ origin_scratch);
}
notify_scratch(playing->track, playing->submitter, who,
xtime(0) - playing->played);
q->id = id;
}
+/** @brief Add a track to the queue
+ * @param track Track to add
+ * @param submitter Who added it, or NULL
+ * @param where Where to add it
+ * @param target ID to add after for @ref WHERE_AFTER
+ * @param origin Track origin
+ * @return New queue entry or NULL
+ *
+ * The queue is NOT saved to disk.
+ *
+ * NULL can only be returned if @ref WHERE_AFTER is used with an invalid
+ * queue ID.
+ */
struct queue_entry *queue_add(const char *track, const char *submitter,
- int where, enum track_origin origin) {
- struct queue_entry *q, *beforeme;
+ int where, const char *target,
+ enum track_origin origin) {
+ struct queue_entry *q, *beforeme, *afterme;
q = xmalloc(sizeof *q);
q->track = xstrdup(track);
beforeme = beforeme->prev;
queue_insert_entry(beforeme->prev, q);
break;
+ case WHERE_AFTER:
+ if(!*target)
+ /* Insert at start of queue */
+ afterme = &qhead;
+ else {
+ /* Insert after a specific track */
+ afterme = qhead.next;
+ while(afterme != &qhead && strcmp(afterme->id, target))
+ afterme = afterme->next;
+ if(afterme == &qhead)
+ return NULL;
+ }
+ queue_insert_entry(afterme, q);
+ break;
}
/* submitter will be a null pointer for a scratch */
if(submitter)
return;
}
info("scheduled event %s: %s play %s", id, who, track);
- q = queue_add(track, who, WHERE_START, origin_scheduled);
+ q = queue_add(track, who, WHERE_START, NULL, origin_scheduled);
queue_write();
if(q == qhead.next && playing)
prepare(ev, q);
sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
return 1;
}
- q = queue_add(track, c->who, WHERE_BEFORE_RANDOM, origin_picked);
+ q = queue_add(track, c->who, WHERE_BEFORE_RANDOM, NULL, origin_picked);
queue_write();
- /* If we added the first track, and something is playing, then prepare the
- * new track. If nothing is playing then we don't bother as it wouldn't gain
- * anything. */
- if(q == qhead.next && playing)
- prepare(c->ev, q);
sink_printf(ev_writer_sink(c->w), "252 %s\n", q->id);
+ /* We make sure the track at the head of the queue is prepared, just in case
+ * we added it. We could be more subtle but prepare() will ensure we don't
+ * prepare the same track twice so there's no point. */
+ if(qhead.next != &qhead)
+ prepare(c->ev, qhead.next);
/* If the queue was empty but we are for some reason paused then
* unpause. */
if(!playing) resume_playing(0);
return 1; /* completed */
}
+static int c_playafter(struct conn *c, char **vec,
+ int attribute((unused)) nvec) {
+ const char *track;
+ struct queue_entry *q;
+ const char *afterme = vec[0];
+
+ for(int n = 1; n < nvec; ++n) {
+ if(!trackdb_exists(vec[n])) {
+ sink_writes(ev_writer_sink(c->w), "550 track is not in database\n");
+ return 1;
+ }
+ if(!(track = trackdb_resolve(vec[n]))) {
+ sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
+ return 1;
+ }
+ q = queue_add(track, c->who, WHERE_AFTER, afterme, origin_picked);
+ if(!q) {
+ sink_printf(ev_writer_sink(c->w), "550 No such ID\n");
+ return 1;
+ }
+ info("added %s as %s after %s", track, q->id, afterme);
+ afterme = q->id;
+ }
+ queue_write();
+ sink_printf(ev_writer_sink(c->w), "252 OK\n");
+ /* We make sure the track at the head of the queue is prepared, just in case
+ * we added it. We could be more subtle but prepare() will ensure we don't
+ * prepare the same track twice so there's no point. */
+ if(qhead.next != &qhead) {
+ prepare(c->ev, qhead.next);
+ info("prepared %s", qhead.next->id);
+ }
+ /* If the queue was empty but we are for some reason paused then
+ * unpause. */
+ if(!playing)
+ resume_playing(0);
+ play(c->ev);
+ return 1; /* completed */
+}
+
static int c_remove(struct conn *c, char **vec,
int attribute((unused)) nvec) {
struct queue_entry *q;
{ "part", 3, 3, c_part, RIGHT_READ },
{ "pause", 0, 0, c_pause, RIGHT_PAUSE },
{ "play", 1, 1, c_play, RIGHT_PLAY },
+ { "playafter", 2, INT_MAX, c_playafter, RIGHT_PLAY },
{ "playing", 0, 0, c_playing, RIGHT_READ },
{ "playlist-delete", 1, 1, c_playlist_delete, RIGHT_PLAY },
{ "playlist-get", 1, 1, c_playlist_get, RIGHT_READ },
c.random_disable()
assert c.random_enabled() == False
track = u"%s/Joe Bloggs/First Album/02:Second track.ogg" % dtest.tracks
+ track2 = u"%s/Joe Bloggs/First Album/04:Fourth track.ogg" % dtest.tracks
+ track3 = u"%s/Joe Bloggs/First Album/05:Fifth track.ogg" % dtest.tracks
print " adding track to queue"
c.disable()
assert c.enabled() == False
t = ts[0]
assert t['submitter'] == u'fred', "check recent entry submitter"
+ print " ensuring queue is clear"
+ c.disable()
+ while c.playing() is not None:
+ time.sleep(1)
+ q = c.queue()
+ for qe in q:
+ c.remove(qe["id"])
+
+ print " testing playafter"
+ print " adding to empty queue"
+ c.playafter(None, [track])
+ q = c.queue()
+ print '\n'.join(map(lambda n: "%d: %s" % (n, q[n]["track"]),
+ range(0, len(q))))
+ assert len(q) == 1
+ assert q[0]['track'] == track
+ print " insert at start of queue"
+ c.playafter(None, [track2])
+ q = c.queue()
+ print '\n'.join(map(lambda n: "%d: %s" % (n, q[n]["track"]),
+ range(0, len(q))))
+ assert len(q) == 2
+ assert q[0]['track'] == track2
+ assert q[1]['track'] == track
+ print " insert in middle of queue"
+ c.playafter(q[0]['id'], [track3])
+ q = c.queue()
+ print '\n'.join(map(lambda n: "%d: %s" % (n, q[n]["track"]),
+ range(0, len(q))))
+ assert len(q) == 3
+ assert q[0]['track'] == track2
+ assert q[1]['track'] == track3
+ assert q[2]['track'] == track
+ print " insert multiple tracks at end of queue"
+ c.playafter(q[2]['id'], [track2, track])
+ q = c.queue()
+ print '\n'.join(map(lambda n: "%d: %s" % (n, q[n]["track"]),
+ range(0, len(q))))
+ assert len(q) == 5
+ assert q[0]['track'] == track2
+ assert q[1]['track'] == track3
+ assert q[2]['track'] == track
+ assert q[3]['track'] == track2
+ assert q[4]['track'] == track
+
+ print " clearing queue"
+ for qe in q:
+ c.remove(qe["id"])
+
print " testing scratches"
retry = False
scratchlimit = 5