X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/763d5e6ad88ef3ba1cd1d7742d060e4f1e54c6b8..92db088e5b292c1180a090ed369b9851e933e610:/lib/queue.c diff --git a/lib/queue.c b/lib/queue.c index 1e5c1d4..789d2ea 100644 --- a/lib/queue.c +++ b/lib/queue.c @@ -1,52 +1,38 @@ /* * This file is part of DisOrder. - * Copyright (C) 2004, 2005, 2006 Richard Kettlewell + * Copyright (C) 2004-2009, 2011, 2013 Richard Kettlewell * - * This program is free software; you can redistribute it and/or modify + * 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 + * the Free Software Foundation, either version 3 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. - * + * + * 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 + * along with this program. If not, see . */ +/** @file lib/queue.c + * @brief Track queues + */ +#include "common.h" -#include -#include "types.h" - -#include -#include #include -#include -#include #include -#include -#include -#include #include "mem.h" #include "queue.h" #include "log.h" -#include "configuration.h" #include "split.h" #include "syscalls.h" -#include "charset.h" #include "table.h" -#include "inputline.h" #include "printf.h" -#include "plugin.h" -#include "basen.h" -#include "eventlog.h" -#include "disorder.h" +#include "vector.h" -const char *playing_states[] = { +const char *const playing_states[] = { "failed", "isscratch", "no_player", @@ -59,17 +45,22 @@ const char *playing_states[] = { "unplayed" }; -/* 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 }; - -/* 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 }; +/** @brief String values for @c origin field */ +const char *const track_origins[] = { + "adopted", + "picked", + "random", + "scheduled", + "scratch", +}; -static long pcount; +#define VALUE(q, offset, type) *(type *)((char *)q + offset) -/* add new entry @n@ to a doubly linked list just after @b@ */ -static void l_add(struct queue_entry *b, struct queue_entry *n) { +/** @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; n->next->prev = n; @@ -77,19 +68,18 @@ static void l_add(struct queue_entry *b, struct queue_entry *n) { } /* remove an entry from a doubly-linked list */ -static void l_remove(struct queue_entry *node) { +void queue_delete_entry(struct queue_entry *node) { node->next->prev = node->prev; node->prev->next = node->next; } -#define VALUE(q, offset, type) *(type *)((char *)q + offset) - static int unmarshall_long(char *data, struct queue_entry *q, size_t offset, void (*error_handler)(const char *, void *), void *u) { + char errbuf[1024]; if(xstrtol(&VALUE(q, offset, long), data, 0, 0)) { - error_handler(strerror(errno), u); + error_handler(format_error(ec_errno, errno, errbuf, sizeof errbuf), u); return -1; } return 0; @@ -101,17 +91,23 @@ static const char *marshall_long(const struct queue_entry *q, size_t offset) { n = byte_snprintf(buffer, sizeof buffer, "%ld", VALUE(q, offset, long)); if(n < 0) - fatal(errno, "error converting int"); + disorder_fatal(errno, "error converting int"); else if((size_t)n >= sizeof buffer) - fatal(0, "long converted to decimal is too long"); + disorder_fatal(0, "long converted to decimal is too long"); return xstrdup(buffer); } +static void free_none(struct queue_entry attribute((unused)) *q, + size_t attribute((unused)) offset) { +} + +#define free_long free_none + static int unmarshall_string(char *data, struct queue_entry *q, size_t offset, void attribute((unused)) (*error_handler)(const char *, void *), void attribute((unused)) *u) { - VALUE(q, offset, char *) = data; + VALUE(q, offset, char *) = xstrdup(data); return 0; } @@ -119,14 +115,19 @@ static const char *marshall_string(const struct queue_entry *q, size_t offset) { return VALUE(q, offset, char *); } +static void free_string(struct queue_entry *q, size_t offset) { + xfree(VALUE(q, offset, char *)); +} + static int unmarshall_time_t(char *data, struct queue_entry *q, size_t offset, void (*error_handler)(const char *, void *), void *u) { long_long ul; + char errbuf[1024]; if(xstrtoll(&ul, data, 0, 0)) { - error_handler(strerror(errno), u); + error_handler(format_error(ec_errno, errno, errbuf, sizeof errbuf), u); return -1; } VALUE(q, offset, time_t) = ul; @@ -140,12 +141,14 @@ static const char *marshall_time_t(const struct queue_entry *q, size_t offset) { n = byte_snprintf(buffer, sizeof buffer, "%"PRIdMAX, (intmax_t)VALUE(q, offset, time_t)); if(n < 0) - fatal(errno, "error converting time"); + disorder_fatal(errno, "error converting time"); else if((size_t)n >= sizeof buffer) - fatal(0, "time converted to decimal is too long"); + disorder_fatal(0, "time converted to decimal is too long"); return xstrdup(buffer); } +#define free_time_t free_none + static int unmarshall_state(char *data, struct queue_entry *q, size_t offset, void (*error_handler)(const char *, void *), @@ -163,23 +166,59 @@ static int unmarshall_state(char *data, struct queue_entry *q, 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)]; } -#define F(n, h) { #n, offsetof(struct queue_entry, n), marshall_##h, unmarshall_##h } +static const char *marshall_origin(const struct queue_entry *q, size_t offset) { + return track_origins[VALUE(q, offset, enum track_origin)]; +} + +#define free_state free_none +#define free_origin free_none + +#define F(n, h) { #n, offsetof(struct queue_entry, n), marshall_##h, unmarshall_##h, free_##h } -static const struct field { +/** @brief A field in a @ref queue_entry */ +static const struct queue_field { + /** @brief Field name */ const char *name; + + /** @brief Offset of value in @ref queue_entry structure */ size_t offset; + + /** @brief Marshaling function */ const char *(*marshall)(const struct queue_entry *q, size_t offset); + + /** @brief Unmarshaling function */ int (*unmarshall)(char *data, struct queue_entry *q, size_t offset, void (*error_handler)(const char *, void *), void *u); + + /** @brief Destructor */ + void (*free)(struct queue_entry *q, size_t offset); } fields[] = { /* Keep this table sorted. */ F(expected, time_t), F(id, string), + F(origin, origin), F(played, time_t), F(scratched, string), F(sofar, long), @@ -189,6 +228,7 @@ static const struct field { F(when, time_t), F(wstat, long) }; +#define NFIELDS (sizeof fields / sizeof *fields) int queue_unmarshall(struct queue_entry *q, const char *s, void (*error_handler)(const char *, void *), @@ -196,9 +236,12 @@ int queue_unmarshall(struct queue_entry *q, const char *s, char **vec; int nvec; + q->pid = -1; /* =none */ if(!(vec = split(s, &nvec, SPLIT_QUOTES, error_handler, u))) return -1; - return queue_unmarshall_vec(q, nvec, vec, error_handler, u); + int rc = queue_unmarshall_vec(q, nvec, vec, error_handler, u); + free_strings(nvec, vec); + return rc; } int queue_unmarshall_vec(struct queue_entry *q, int nvec, char **vec, @@ -212,7 +255,7 @@ int queue_unmarshall_vec(struct queue_entry *q, int nvec, char **vec, } while(*vec) { D(("key %s value %s", vec[0], vec[1])); - if((n = TABLE_FIND(fields, struct field, name, *vec)) < 0) { + if((n = TABLE_FIND(fields, name, *vec)) < 0) { error_handler("unknown key in queue data", u); return -1; } else { @@ -224,30 +267,6 @@ int queue_unmarshall_vec(struct queue_entry *q, int nvec, char **vec, return 0; } -void queue_fix_sofar(struct queue_entry *q) { - long sofar; - - /* Fake up SOFAR field for currently-playing tracks that don't have it filled - * in by the speaker process. XXX this horrible bodge should go away when we - * have a more general implementation of pausing as that field will always - * have to be right for the playing track. */ - if((q->state == playing_started - || q->state == playing_paused) - && q->type & DISORDER_PLAYER_PAUSES - && (q->type & DISORDER_PLAYER_TYPEMASK) != DISORDER_PLAYER_RAW) { - if(q->lastpaused) { - if(q->uptopause == -1) /* Don't know how far thru. */ - sofar = -1; - else if(q->lastresumed) /* Has been paused and resumed. */ - sofar = q->uptopause + time(0) - q->lastresumed; - else /* Currently paused. */ - sofar = q->uptopause; - } else /* Never been paused. */ - sofar = time(0) - q->played; - q->sofar = sofar; - } -} - char *queue_marshall(const struct queue_entry *q) { unsigned n; const char *vec[sizeof fields / sizeof *fields], *v; @@ -271,235 +290,14 @@ char *queue_marshall(const struct queue_entry *q) { return r; } -static void queue_read_error(const char *msg, - void *u) { - fatal(0, "error parsing queue %s: %s", (const char *)u, msg); -} - -static void queue_do_read(struct queue_entry *head, const char *path) { - char *buffer; - FILE *fp; - struct queue_entry *q; - - if(!(fp = fopen(path, "r"))) { - if(errno == ENOENT) - return; /* no queue */ - fatal(errno, "error opening %s", path); - } - head->next = head->prev = head; - while(!inputline(path, fp, &buffer, '\n')) { - q = xmalloc(sizeof *q); - queue_unmarshall(q, buffer, queue_read_error, (void *)path); - if(head == &qhead - && (!q->track - || !q->when)) - fatal(0, "incomplete queue entry in %s", path); - l_add(head->prev, q); - } - if(ferror(fp)) fatal(errno, "error reading %s", path); - fclose(fp); -} - -void queue_read(void) { - queue_do_read(&qhead, config_get_file("queue")); -} - -void recent_read(void) { - struct queue_entry *q; - - queue_do_read(&phead, config_get_file("recent")); - /* reset pcount after loading */ - pcount = 0; - q = phead.next; - while(q != &phead) { - ++pcount; - q = q->next; - } -} - -static void queue_do_write(const struct queue_entry *head, const char *path) { - char *tmp; - FILE *fp; - struct queue_entry *q; - - byte_xasprintf(&tmp, "%s.new", path); - if(!(fp = fopen(tmp, "w"))) fatal(errno, "error opening %s", tmp); - for(q = head->next; q != head; q = q->next) - if(fprintf(fp, "%s\n", queue_marshall(q)) < 0) - fatal(errno, "error writing %s", tmp); - if(fclose(fp) < 0) fatal(errno, "error closing %s", tmp); - if(rename(tmp, path) < 0) fatal(errno, "error replacing %s", path); -} - -void queue_write(void) { - queue_do_write(&qhead, config_get_file("queue")); -} - -void recent_write(void) { - queue_do_write(&phead, config_get_file("recent")); -} - -void queue_id(struct queue_entry *q) { - static unsigned long serial; - unsigned long a[3]; - char buffer[128]; - - a[0] = serial++ & 0xFFFFFFFFUL; - a[1] = time(0) & 0xFFFFFFFFUL; - a[2] = getpid() & 0xFFFFFFFFUL; - basen(a, 3, buffer, sizeof buffer, 62); - q->id = xstrdup(buffer); -} - -struct queue_entry *queue_add(const char *track, const char *submitter, - int where) { - struct queue_entry *q; - - q = xmalloc(sizeof *q); - q->track = xstrdup(track); - q->submitter = submitter ? xstrdup(submitter) : 0; - q->state = playing_unplayed; - queue_id(q); - time(&q->when); - switch(where) { - case WHERE_START: - l_add(&qhead, q); - break; - case WHERE_END: - l_add(qhead.prev, q); - break; - case WHERE_BEFORE_RANDOM: - if(qhead.prev == &qhead /* Empty queue. */ - || qhead.prev->state != playing_random) /* No random track */ - l_add(qhead.prev, q); - else - l_add(qhead.prev->prev, q); /* Before random track. */ - break; - } - /* submitter will be a null pointer for a scratch */ - if(submitter) - notify_queue(track, submitter); - eventlog_raw("queue", queue_marshall(q), (const char *)0); - return q; -} - -int queue_move(struct queue_entry *q, int delta, const char *who) { - int moved = 0; - char buffer[20]; - - /* not the most efficient approach but hopefuly relatively comprehensible: - * the idea is that for each step we determine which nodes are affected, and - * fill in all the links starting at the 'prev' end and moving towards the - * 'next' end. */ - - while(delta > 0 && q->prev != &qhead) { - struct queue_entry *n, *p, *pp; - - n = q->next; - p = q->prev; - pp = p->prev; - pp->next = q; - q->prev = pp; - q->next = p; - p->prev = q; - p->next = n; - n->prev = p; - --delta; - ++moved; - } - - while(delta < 0 && q->next != &qhead) { - struct queue_entry *n, *p, *nn; - - p = q->prev; - n = q->next; - nn = n->next; - p->next = n; - n->prev = p; - n->next = q; - q->prev = n; - q->next = nn; - nn->prev = q; - ++delta; - --moved; - } - - if(moved) { - info("user %s moved %s", who, q->id); - notify_queue_move(q->track, who); - sprintf(buffer, "%d", moved); - eventlog("moved", who, (char *)0); - } - - return delta; -} - -static int find_in_list(struct queue_entry *needle, - int nqs, struct queue_entry **qs) { - int n; - - for(n = 0; n < nqs; ++n) - if(qs[n] == needle) - return 1; - return 0; -} - -void queue_moveafter(struct queue_entry *target, - int nqs, struct queue_entry **qs, - const char *who) { - struct queue_entry *q; - int n; - - /* Normalize */ - if(!target) - target = &qhead; - else - while(find_in_list(target, nqs, qs)) - target = target->prev; - /* Do the move */ - for(n = 0; n < nqs; ++n) { - q = qs[n]; - l_remove(q); - l_add(target, q); - target = q; - /* Log the individual tracks */ - info("user %s moved %s", who, q->id); - notify_queue_move(q->track, who); - } - /* Report that the queue changed to the event log */ - eventlog("moved", who, (char *)0); -} - -void queue_remove(struct queue_entry *which, const char *who) { - if(who) { - info("user %s removed %s", who, which->id); - notify_queue_move(which->track, who); - } - eventlog("removed", which->id, who, (const char *)0); - l_remove(which); -} - -struct queue_entry *queue_find(const char *key) { - struct queue_entry *q; - - for(q = qhead.next; - q != &qhead && strcmp(q->track, key) && strcmp(q->id, key); - q = q->next) - ; - return q != &qhead ? q : 0; -} - -void queue_played(struct queue_entry *q) { - while(pcount && pcount >= config->history) { - eventlog("recent_removed", phead.next->id, (char *)0); - l_remove(phead.next); - pcount--; - } - if(config->history) { - eventlog_raw("recent_added", queue_marshall(q), (char *)0); - l_add(phead.prev, q); - ++pcount; - } +void queue_free(struct queue_entry *q, int rest) { + if(!q) + return; + if(rest) + queue_free(q->next, rest); + for(unsigned n = 0; n < NFIELDS; ++n) + fields[n].free(q, fields[n].offset); + xfree(q); } /*