#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 --; }