summaryrefslogtreecommitdiff
path: root/playlist.c
diff options
context:
space:
mode:
authorbbergeron <[email protected]>2024-04-03 17:32:01 -0400
committerbbergeron <[email protected]>2024-04-03 17:32:01 -0400
commitc1cb78d574c0429aa5e3ff3a2b3886e4bc153212 (patch)
treebf68806bcbddcafafc015b28c25550ea457eeecc /playlist.c
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).
Diffstat (limited to 'playlist.c')
-rw-r--r--playlist.c269
1 files changed, 269 insertions, 0 deletions
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 <asm-generic/errno-base.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 --;
+}