}
if(!dcgi_playing
&& ((dcgi_queue
- && dcgi_queue->state != playing_random)
+ && dcgi_queue->origin != origin_random)
|| dcgi_random_enabled)
&& dcgi_enabled) {
/* no track playing but playing is enabled and there is something coming
return mx_bool_result(output, !!dcgi_queue);
}
-/*$ @isrecent@
+/*$ @isrecent
*
* Expands to "true" if there the recently played list is nonempty, otherwise
* "false".
return sink_writes(output, cgi_sgmlquote(t)) < 0 ? -1 : 0;
}
-/*$ @enabled@
+/*$ @enabled
*
* Expands to "true" if playing is enabled, otherwise "false".
*/
&& dcgi_playing->state == playing_paused));
}
-/*$ @state{ID}@
+/*$ @state{ID}
*
* Expands to the current state of track ID.
*/
return 0;
}
-/*$ @right{RIGHT}{WITH-RIGHT}{WITHOUT-RIGHT}@
+/*$ @origin{ID}
+ *
+ * Expands to the current origin of track ID.
+ */
+static int exp_origin(int attribute((unused)) nargs,
+ char **args,
+ struct sink *output,
+ void attribute((unused)) *u) {
+ struct queue_entry *q = dcgi_findtrack(args[0]);
+
+ if(q)
+ return sink_writes(output, track_origins[q->origin]) < 0 ? -1 : 0;
+ return 0;
+}
+
+/*$ @right{RIGHT}{WITH-RIGHT}{WITHOUT-RIGHT}
*
* Expands to WITH-RIGHT if the current user has right RIGHT, otherwise to
* WITHOUT-RIGHT. The WITHOUT-RIGHT argument may be left out.
mx_register("label", 1, 1, exp_label);
mx_register("length", 1, 1, exp_length);
mx_register("movable", 1, 2, exp_movable);
+ mx_register("origin", 1, 1, exp_origin);
mx_register("part", 2, 3, exp_part);
mx_register("paused", 0, 0, exp_paused);
mx_register("pref", 2, 2, exp_pref);
static void print_queue_entry(const struct queue_entry *q) {
if(q->track) xprintf("track %s\n", nullcheck(utf82mb(q->track)));
if(q->id) xprintf(" id %s\n", nullcheck(utf82mb(q->id)));
- if(q->submitter) xprintf(" submitted by %s at %s",
- nullcheck(utf82mb(q->submitter)), ctime(&q->when));
+ switch(q->origin) {
+ case origin_adopted:
+ case origin_picked:
+ case origin_scheduled:
+ xprintf(" %s by %s at %s",
+ track_origins[q->origin],
+ nullcheck(utf82mb(q->submitter)), ctime(&q->when));
+ break;
+ default:
+ break;
+ }
if(q->played) xprintf(" played at %s", ctime(&q->played));
if(q->state == playing_started
|| q->state == playing_paused) xprintf(" %lds so far", q->sofar);
return path;
}
-/*$ @include{TEMPLATE}@
+/*$ @include{TEMPLATE}
*
* Includes TEMPLATE.
*
return 0;
}
-/*$ @include{COMMAND}@
+/*$ @include{COMMAND}
*
* Executes COMMAND via the shell (using "sh -c") and copies its
* standard output to the template output. The shell command output
return 0;
}
-/*$ @if{CONDITION}{IF-TRUE}{IF-FALSE}@
+/*$ @if{CONDITION}{IF-TRUE}{IF-FALSE}
*
* If CONDITION is "true" then evaluates to IF-TRUE. Otherwise
* evaluates to IF-FALSE. The IF-FALSE part is optional.
return 0;
}
-/*$ @and{BRANCH}{BRANCH}...@
+/*$ @and{BRANCH}{BRANCH}...
*
* Expands to "true" if all the branches are "true" otherwise to "false". If
* there are no brances then the result is "true". Only as many branches as
return mx_bool_result(output, result);
}
-/*$ @or{BRANCH}{BRANCH}...@
+/*$ @or{BRANCH}{BRANCH}...
*
* Expands to "true" if any of the branches are "true" otherwise to "false".
* If there are no brances then the result is "false". Only as many branches
return mx_bool_result(output, result);
}
-/*$ @not{CONDITION}@
+/*$ @not{CONDITION}
*
* Expands to "true" unless CONDITION is "true" in which case "false".
*/
return mx_bool_result(output, !mx_str2bool(args[0]));
}
-/*$ @#{...}@
+/*$ @#{...}
*
* Expands to nothing. The argument(s) are not fully evaluated, and no side
* effects occur.
return 0;
}
-/*$ @urlquote{STRING}@
+/*$ @urlquote{STRING}
*
* URL-quotes a string, i.e. replaces any characters not safe to use unquoted
* in a URL with %-encoded form.
return 0;
}
-/*$ @eq{S1}{S2}...@
+/*$ @eq{S1}{S2}...
*
* Expands to "true" if all the arguments are identical, otherwise to "false"
* (i.e. if any pair of arguments differs).
return mx_bool_result(output, result);
}
-/*$ @ne{S1}{S2}...@
+/*$ @ne{S1}{S2}...
*
* Expands to "true" if all of the arguments differ from one another, otherwise
* to "false" (i.e. if any value appears more than once).
return mx_bool_result(output, result);
}
-/*$ @discard{...}@
+/*$ @discard{...}
*
* Expands to nothing. Unlike the comment expansion @#{...}, side effects of
* arguments are not suppressed. So this can be used to surround a collection
return 0;
}
-/*$ @define{NAME}{ARG1 ARG2...}{DEFINITION}@
+/*$ @define{NAME}{ARG1 ARG2...}{DEFINITION}
*
* Define a macro. The macro will be called NAME and will act like an
* expansion. When it is expanded, the expansion is replaced by DEFINITION,
#include "table.h"
#include "printf.h"
-const char *playing_states[] = {
+const char *const playing_states[] = {
"failed",
"isscratch",
"no_player",
"unplayed"
};
+/** @brief String values for @c origin field */
+const char *const track_origins[] = {
+ "adopted",
+ "picked",
+ "random",
+ "scheduled",
+ "scratch",
+};
+
#define VALUE(q, offset, type) *(type *)((char *)q + offset)
/* add new entry @n@ to a doubly linked list just after @b@ */
return 0;
}
+static int unmarshall_origin(char *data, struct queue_entry *q,
+ size_t offset,
+ void (*error_handler)(const char *, void *),
+ void *u) {
+ int n;
+
+ if((n = table_find(track_origins, 0, sizeof (char *),
+ sizeof track_origins / sizeof *track_origins,
+ data)) < 0) {
+ D(("origin=[%s] n=%d", data, n));
+ error_handler("invalid origin", u);
+ return -1;
+ }
+ VALUE(q, offset, enum track_origin) = n;
+ return 0;
+}
+
static const char *marshall_state(const struct queue_entry *q, size_t offset) {
return playing_states[VALUE(q, offset, enum playing_state)];
}
+static const char *marshall_origin(const struct queue_entry *q, size_t offset) {
+ return track_origins[VALUE(q, offset, enum track_origin)];
+}
+
#define F(n, h) { #n, offsetof(struct queue_entry, n), marshall_##h, unmarshall_##h }
static const struct field {
/* Keep this table sorted. */
F(expected, time_t),
F(id, string),
+ F(origin, origin),
F(played, time_t),
F(scratched, string),
F(sofar, long),
playing_unplayed /* haven't played this track yet */
};
-extern const char *playing_states[];
+extern const char *const playing_states[];
+
+/** @brief Possible track origins
+ *
+ * This is a newly introduced field. The aim is ultimately to separate the
+ * concepts of the track origin and its current state. NB that both are
+ * potentially mutable!
+ */
+enum track_origin {
+ /** @brief Track was picked at random and then adopted by a user
+ *
+ * @c submitter identifies who adopted it. This isn't implemented
+ * yet.
+ */
+ origin_adopted,
+
+ /** @brief Track was picked by a user
+ *
+ * @c submitter identifies who picked it
+ */
+ origin_picked,
+
+ /** @brief Track was picked at random
+ *
+ * @c submitter will be NULL
+ */
+ origin_random,
+
+ /** @brief Track was scheduled by a user
+ *
+ * @c submitter identifies who picked it
+ */
+ origin_scheduled,
+
+ /** @brief Track is a scratch
+ *
+ * @c submitter identifies who did the scratching
+ */
+ origin_scratch
+};
+
+extern const char *const track_origins[];
/* queue entries form a circular doubly-linked list */
struct queue_entry {
time_t when; /* time submitted */
time_t played; /* when played */
enum playing_state state; /* state */
+ enum track_origin origin; /* where track came from */
long wstat; /* wait status */
const char *scratched; /* scratched by */
const char *id; /* queue entry ID */
/* write the recently played list out. Calls @fatal@ on error. */
struct queue_entry *queue_add(const char *track, const char *submitter,
- int where);
+ int where, 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 */
if(!track)
return;
/* Add the track to the queue */
- q = queue_add(track, 0, WHERE_END);
+ q = queue_add(track, 0, WHERE_END, origin_random);
q->state = playing_random;
D(("picked %p (%s) at random", (void *)q, q->track));
queue_write();
}
/* 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. */
- if(!random_enabled && q->state == playing_random)
+ /* If random play is disabled but the track is a non-adopted random one
+ * then don't play it. play() will be called again when random play is
+ * re-enabled. */
+ if(!random_enabled && q->origin == origin_random)
return;
D(("taken %p (%s) from queue", (void *)q, q->track));
/* Try to start playing. */
* 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);
+ q = queue_add(config->scratch.s[r], who, WHERE_START, origin_scratch);
q->state = playing_isscratch;
}
notify_scratch(playing->track, playing->submitter, who,
}
struct queue_entry *queue_add(const char *track, const char *submitter,
- int where) {
+ int where, enum track_origin origin) {
struct queue_entry *q, *beforeme;
q = xmalloc(sizeof *q);
q->track = xstrdup(track);
q->submitter = submitter ? xstrdup(submitter) : 0;
q->state = playing_unplayed;
+ q->origin = origin;
queue_id(q);
time(&q->when);
switch(where) {
* at the end. */
beforeme = &qhead;
while(beforeme->prev != &qhead
- && beforeme->prev->state == playing_random)
+ && beforeme->prev->origin == origin_random)
beforeme = beforeme->prev;
queue_insert_entry(beforeme->prev, q);
break;
return;
}
info("scheduled event %s: %s play %s", id, who, track);
- q = queue_add(track, who, WHERE_START);
+ q = queue_add(track, who, WHERE_START, origin_scheduled);
queue_write();
if(q == qhead.next && playing)
prepare(ev, q);
#include "disorder-server.h"
/* the head of the queue is played next, so normally we add to the tail */
-struct queue_entry qhead = { &qhead, &qhead, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+struct queue_entry qhead = {
+ .next = &qhead,
+ .prev = &qhead
+};
/* the head of the recent list is the oldest thing, the tail the most recently
* played */
-struct queue_entry phead = { &phead, &phead, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+struct queue_entry phead = {
+ .next = &phead,
+ .prev = &phead
+};
long pcount;
sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
return 1;
}
- q = queue_add(track, c->who, WHERE_BEFORE_RANDOM);
+ q = queue_add(track, c->who, WHERE_BEFORE_RANDOM, 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