From c1cb78d574c0429aa5e3ff3a2b3886e4bc153212 Mon Sep 17 00:00:00 2001 From: bbergeron Date: Wed, 3 Apr 2024 17:32:01 -0400 Subject: Reset Git repo and use a pseudonym to sign commits I used to sign my commits with my real name and my personal email address, which I wanted scrubbed off the "B." pseudosphere. Re-creating a new git repository was safer than simpler than re-writing the history (although the latter could've also worked but, oh well). --- playlist.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 playlist.c (limited to 'playlist.c') diff --git a/playlist.c b/playlist.c new file mode 100644 index 0000000..5d7a697 --- /dev/null +++ b/playlist.c @@ -0,0 +1,269 @@ +#include +#include +#include +#include +#include +#include +#include "playlist.h" + +#define PLAYLIST_INIT_SIZE 2 // TODO: Bump to 32 + +struct Playlist { + struct Entry *entries; + int front, rear, next_id, len, size; + pthread_mutex_t mutex; + pthread_cond_t ready; +}; + +static int playlist_grow (Playlist *pl); +static int playlist_is_full (Playlist *pl); +static int playlist_is_empty (Playlist *pl); +static int playlist_get_index (Playlist *pl, unsigned int id); +static void playlist_unsafe_dequeue (Playlist *pl, struct Entry *entry); + +Playlist *playlist_init(void) +{ + Playlist *pl; + + if ((pl = malloc(sizeof(*pl))) == NULL) + return NULL; + + pl->entries = calloc(PLAYLIST_INIT_SIZE, sizeof(*pl->entries)); + if (pl->entries == NULL) { + free(pl); + return NULL; + } + + pl->next_id = 0; + pl->front = 0; + pl->rear = 0; + pl->len = 0; + pl->size = PLAYLIST_INIT_SIZE; + pl->mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; + pl->ready = (pthread_cond_t) PTHREAD_COND_INITIALIZER; + return pl; +} + +void playlist_free (Playlist *pl) +{ + int i; + + if (pl == NULL) + return; + + for (i = 0; i < pl->size; i ++) + free(pl->entries[0].file); + + free(pl->entries); + free(pl); +} + +int playlist_enqueue (Playlist *pl, const char *entry) +{ + int id; + char *copy; + + if ((copy = strdup(entry)) == NULL) + return -1; + + pthread_mutex_lock(&pl->mutex); + if (playlist_is_full(pl) && playlist_grow(pl)) { + pthread_mutex_unlock(&pl->mutex); + free(copy); + return -1; + } + + pl->entries[pl->rear].file = copy; + id = pl->entries[pl->rear].id = pl->next_id ++; + pl->rear = (pl->rear + 1) % pl->size; + pl->len ++; + + pthread_cond_signal(&pl->ready); + pthread_mutex_unlock(&pl->mutex); + return id; +} + +int playlist_dequeue (Playlist *pl, struct Entry *entry) +{ + /* playlist_dequeue take hold of the playlist's mutex, relinquishing it + * either after a song has been dequeued, or, in the scenario where the + * playlist is currently empty, atomically while waiting for the pl->ready + * condition to signal true, indicating that a song has been queued. + * + * However, it is documented that POSIX condition waiters may be subjects to + * "spurious wakeup", spontaneously unlocking themself for no appearent + * reasons. + * + * To prevent undefined behaviours, the lock condition checker and the + * waiter are placed in a loop to ensure that any return-to-context occured + * legitimately, and to keep waiting othwerwise. + */ + + pthread_mutex_lock(&pl->mutex); + errno = 0; + while (playlist_is_empty(pl)) { + pthread_cond_wait(&pl->ready, &pl->mutex); + if (errno) { + pthread_mutex_unlock(&pl->mutex); + return -1; + } + } + + playlist_unsafe_dequeue(pl, entry); + pthread_mutex_unlock(&pl->mutex); + return 0; +} + +int playlist_try_dequeue(Playlist *pl, struct Entry *entry) +{ + pthread_mutex_lock(&pl->mutex); + if (playlist_is_empty(pl)) { + pthread_mutex_unlock(&pl->mutex); + return -1; + } + + playlist_unsafe_dequeue(pl, entry); + pthread_mutex_unlock(&pl->mutex); + return 0; +} + +int playlist_remove(Playlist *pl, unsigned int id, struct Entry *entry) +{ + int index; + + pthread_mutex_lock(&pl->mutex); + if ((index = playlist_get_index(pl, id)) == -1) { + pthread_mutex_unlock(&pl->mutex); + return -1; + } + + *entry = pl->entries[index]; + while (index != pl->rear) { + pl->entries[index] = pl->entries[index + 1]; + index = (index + 1) % pl->len; + } + + if (pl->rear -- < 0) + pl->rear = pl->len - 1; + pl->len --; + + pthread_mutex_unlock(&pl->mutex); + return 0; +} + +int playlist_insert(Playlist *pl, unsigned int id, const char *value) +{ + char *copy; + int index, new_id; + struct Entry a, b; + + pthread_mutex_lock(&pl->mutex); + if ((index = playlist_get_index(pl, id)) == -1) { + pthread_mutex_unlock(&pl->mutex); + return -1; + } + + if ((copy = strdup(value)) == NULL) + goto enomem; + + if (playlist_is_full(pl) && playlist_grow(pl)) + goto enomem; + + pl->rear = (pl->rear + 1) % pl->len; + + a = (struct Entry){ new_id = pl->next_id ++, copy }; + while (index != pl->rear) { + b = pl->entries[index]; + pl->entries[index] = a; + a = b; + index = (index + 1) % pl->len; + } + + pl->len ++; + + pthread_mutex_unlock(&pl->mutex); + return new_id; + +enomem: + pthread_mutex_unlock(&pl->mutex); + free(copy); + return -2; +} + +int playlist_list(Playlist *pl, struct StateInfo *list) +{ + int i, j; + + pthread_mutex_lock(&pl->mutex); + list->song_count = pl->len; + if ((list->next_songs = malloc(sizeof(list[0]) * pl->len)) == NULL) + return -1; + + for (i = 0, j = pl->front; i < pl->len; i ++, j ++) { + j %= pl->len; + list->next_songs[i].id = pl->entries[j].id; + list->next_songs[i].file = strdup(pl->entries[j].file); + } + pthread_mutex_unlock(&pl->mutex); + + return 0; +} + +int playlist_get_index(Playlist *pl, unsigned int id) +{ + int index; + + index = pl->front; + + // Break on a null entry + while (pl->entries[index].file) { + // Bingo + if (pl->entries[index].id == id) + return index; + + // Advance the index, and break if we're back at the front + index = (index + 1) % pl->len; + if (index == pl->front) + break; + } + + return -1; +} + +static int playlist_is_full (Playlist *pl) +{ + return pl->len == pl->size; +} + +static int playlist_is_empty (Playlist *pl) +{ + return pl->entries[pl->front].file == NULL; +} + +static int playlist_grow(Playlist *pl) +{ + int split; + struct Entry *new_buff; + + new_buff = calloc(pl->size * 2, sizeof(*new_buff)); + if (new_buff == NULL) return -1; + + split = pl->len - pl->front; + memcpy(&new_buff[0], &pl->entries[pl->front], split); + memcpy(&new_buff[split], &pl->entries[0], pl->len - split); + + free(pl->entries); + pl->front = 0; + pl->rear = pl->len; + pl->entries = new_buff; + pl->size *= 2; + return 0; +} + +static void playlist_unsafe_dequeue(Playlist *pl, struct Entry *entry) +{ + *entry = pl->entries[pl->front]; + pl->entries[pl->front].file = NULL; + pl->front = (pl->front + 1) % pl->size; + pl->len --; +} -- cgit v1.2.3