From cd334d2f411577a1e4f8dfd5f3853401f91cad93 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Wed, 3 Oct 2018 23:24:14 +0100 Subject: [PATCH] Fix: jukebox mode now using the classic one-pass algorithm described in Knuth --- src/mpd_client.c | 219 +++++++++++++++++++++++++++-------------------- src/mpd_client.h | 7 ++ 2 files changed, 135 insertions(+), 91 deletions(-) diff --git a/src/mpd_client.c b/src/mpd_client.c index 3c1c450..36afd96 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -1008,11 +1008,12 @@ char* mympd_get_tag(struct mpd_song const *song, enum mpd_tag_type tag) { void mympd_jukebox() { struct mpd_status *status; status = mpd_run_status(mpd.conn); - int queue_length, num_songs, rand_song, i, j, addSongs; + int queue_length, addSongs, i; struct mpd_entity *entity; const struct mpd_song *song; struct mpd_pair *pair; - char *album; + int lineno = 1; + int nkeep = 0; if (!status) { LOG_ERROR_AND_RECOVER("mpd_run_status"); @@ -1022,30 +1023,73 @@ void mympd_jukebox() { mpd_status_free(status); if (queue_length > mympd_state.jukeboxQueueLength) return; - - srand((unsigned int)time(NULL)); - num_songs = 0; - if (mympd_state.jukeboxMode == 1 && strcmp(mympd_state.jukeboxPlaylist, "Database") == 0) { - struct mpd_stats *stats = mpd_run_stats(mpd.conn); - if (stats == NULL) { - LOG_ERROR_AND_RECOVER("mpd_run_stats"); - return; - } - num_songs = mpd_stats_get_number_of_songs(stats); - mpd_stats_free(stats); + if (mympd_state.jukeboxMode == 1) + addSongs = mympd_state.jukeboxQueueLength - queue_length; + else + addSongs = 1; + + if (addSongs < 1) + return; + + srand((unsigned int)time(NULL)); + struct aline *keepentries; + keepentries = malloc(addSongs * sizeof(*keepentries)); + if (keepentries == NULL) { + printf("Can't allocate space for %d lines\n", addSongs); + exit(1); } - else if (mympd_state.jukeboxMode == 1) { - if (!mpd_send_list_playlist(mpd.conn, mympd_state.jukeboxPlaylist)) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - return; + for (i = 0; i < addSongs; i++) { + keepentries[i].nalloc = 0; + keepentries[i].ptr = NULL; + } + + if (mympd_state.jukeboxMode == 1) { + //add songs + if (strcmp(mympd_state.jukeboxPlaylist, "Database") == 0) { + if (!mpd_send_list_all(mpd.conn, "/")) { + LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); + return; + } + } + else { + if (!mpd_send_list_playlist(mpd.conn, mympd_state.jukeboxPlaylist)) { + LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); + return; + } } while ((entity = mpd_recv_entity(mpd.conn)) != NULL) { - num_songs++; + if (randrange(lineno) < addSongs) { + if (nkeep < addSongs) { + song = mpd_entity_get_song(entity); + save_entry(&keepentries[nkeep++], mpd_song_get_uri(song)); + } + else { + i = 0; + if (addSongs > 1) + i = randrange(addSongs); + if (addSongs == 1) { + song = mpd_entity_get_song(entity); + save_entry(&keepentries[i], mpd_song_get_uri(song)); + } + else { + int nm = addSongs - i - 1; + if (nm > 0) { + struct aline tmp = keepentries[i]; + memmove(&keepentries[i], &keepentries[i+1], nm * sizeof(*keepentries)); + keepentries[addSongs - 1] = tmp; + } + song = mpd_entity_get_song(entity); + save_entry(&keepentries[nkeep - 1], mpd_song_get_uri(song)); + } + } + } + lineno++; mpd_entity_free(entity); - } + } } else if (mympd_state.jukeboxMode == 2) { + //add album if (!mpd_search_db_tags(mpd.conn, MPD_TAG_ALBUM)) { LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); return; @@ -1054,84 +1098,77 @@ void mympd_jukebox() { LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); return; } - while ((pair = mpd_recv_pair_tag(mpd.conn, MPD_TAG_ALBUM)) != NULL) { - num_songs++; + while ((pair = mpd_recv_pair_tag(mpd.conn, MPD_TAG_ALBUM )) != NULL) { + if (randrange(lineno) < addSongs) { + if (nkeep < addSongs) { + save_entry(&keepentries[nkeep++], strdup(pair->value)); + } + else { + i = 0; + if (addSongs > 1) + i = randrange(addSongs); + if (addSongs == 1) { + save_entry(&keepentries[i], strdup(pair->value)); + } + else { + int nm = addSongs - i - 1; + if (nm > 0) { + struct aline tmp = keepentries[i]; + memmove(&keepentries[i], &keepentries[i+1], nm * sizeof(*keepentries)); + keepentries[addSongs - 1] = tmp; + } + save_entry(&keepentries[nkeep - 1], strdup(pair->value)); + } + } + } + lineno++; mpd_return_pair(mpd.conn, pair); } - mpd_response_finish(mpd.conn); } - num_songs--; - if (mympd_state.jukeboxMode == 1) - addSongs = mympd_state.jukeboxQueueLength - queue_length; - else - addSongs = 1; - if (num_songs > 0) { - for (j = 0; j < addSongs; j++) { - rand_song = rand() % num_songs; - if (mympd_state.jukeboxMode == 1) { - //add songs - if (strcmp(mympd_state.jukeboxPlaylist, "Database") == 0) { - if (!mpd_send_list_all(mpd.conn, "/")) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - return; - } - } - else { - if (!mpd_send_list_playlist(mpd.conn, mympd_state.jukeboxPlaylist)) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - return; - } - } - i = 0; - while ((entity = mpd_recv_entity(mpd.conn)) != NULL) { - if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { - if (i == rand_song) - break; - i++; - } - mpd_entity_free(entity); - } - mpd_response_finish(mpd.conn); - song = mpd_entity_get_song(entity); - if (song != NULL) { - printf("Jukebox enabled, adding random song: %d/%d\n", rand_song, num_songs); - if (!mpd_run_add(mpd.conn, mpd_song_get_uri(song))) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - } - } - mpd_entity_free(entity); - } - else if (mympd_state.jukeboxMode == 2) { - //add album - if (!mpd_search_db_tags(mpd.conn, MPD_TAG_ALBUM)) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - return; - } - if (!mpd_search_commit(mpd.conn)) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - return; - } - i = 0; - while ((pair = mpd_recv_pair_tag(mpd.conn, MPD_TAG_ALBUM )) != NULL) { - if (i == rand_song) { - album = strdup(pair->value); - break; - } - i++; - mpd_return_pair(mpd.conn, pair); - } - mpd_return_pair(mpd.conn, pair); - mpd_response_finish(mpd.conn); - printf("Jukebox enabled, adding random album %s: %d/%d\n", album, rand_song, num_songs); - if (!mpd_send_command(mpd.conn, "searchadd", "Album", album, NULL)) { - LOG_ERROR_AND_RECOVER("mpd_send_list_playlist"); - return; - } - mpd_response_finish(mpd.conn); + if (nkeep < addSongs) { + fprintf(stderr, "Warning: input didn't contain %d lines\n", addSongs); + return; + } + for (i = 0; i < nkeep; i++) { + if (mympd_state.jukeboxMode == 1) { + printf("Jukebox adding song: %s\n", keepentries[i].ptr); + if (!mpd_run_add(mpd.conn, keepentries[i].ptr)) { + LOG_ERROR_AND_RECOVER("mpd_run_add"); } } - mpd_run_play(mpd.conn); + else { + printf("Jukebox adding album: %s\n", keepentries[i].ptr); + if (!mpd_send_command(mpd.conn, "searchadd", "Album", keepentries[i].ptr, NULL)) { + LOG_ERROR_AND_RECOVER("mpd_send_command"); + return; + } + mpd_response_finish(mpd.conn); + } + } + free(keepentries); + mpd_run_play(mpd.conn); +} + +int randrange(int n) { + return rand() / (RAND_MAX / (n + 1)); +} + +void save_entry(struct aline *alp, const char *entry) { + int need = strlen(entry) + 1; + if (need > alp->nalloc) { + alp->nalloc = need; + if (alp->nalloc < 80) /* arbitrary threshold */ + alp->nalloc = 80; + if (alp->ptr == NULL) + alp->ptr = malloc(alp->nalloc); + else + alp->ptr = realloc(alp->ptr, alp->nalloc); + if (alp->ptr == NULL) { + fprintf(stderr, "Can't (re)allocate space for saved entry\n"); + exit(1); + } } + strcpy(alp->ptr, entry); } int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, int *last_song_id, unsigned *queue_version, unsigned *queue_length) { diff --git a/src/mpd_client.h b/src/mpd_client.h index 5378098..c1ee153 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -186,10 +186,17 @@ typedef struct { t_mympd_state mympd_state; +struct aline { + char *ptr; + int nalloc; +}; + static int is_websocket(const struct mg_connection *nc) { return nc->flags & MG_F_IS_WEBSOCKET; } +int randrange(int n); +void save_entry(struct aline *alp, const char *entry); void mympd_idle(struct mg_mgr *sm, int timeout); void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask); void callback_mympd(struct mg_connection *nc, const struct mg_str msg);