diff --git a/src/Makefile.am b/src/Makefile.am index 06d46b44..cc0149d5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -105,7 +105,6 @@ forked_daapd_SOURCES = main.c \ rsp_query.c rsp_query.h \ daap_query.c daap_query.h \ player.c player.h \ - queue.c queue.h \ worker.c worker.h \ outputs.h outputs.c \ outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \ diff --git a/src/db.c b/src/db.c index 60340bcc..62592ef7 100644 --- a/src/db.c +++ b/src/db.c @@ -42,10 +42,12 @@ #include "conffile.h" #include "logger.h" #include "cache.h" +#include "listener.h" #include "misc.h" #include "db.h" #include "db_init.h" #include "db_upgrade.h" +#include "rng.h" #define STR(x) ((x) ? (x) : "") @@ -297,8 +299,13 @@ static const char *sort_clause[] = "ORDER BY f.disc ASC", "ORDER BY f.track ASC", "ORDER BY f.virtual_path ASC", + "ORDER BY pos ASC", + "ORDER BY shuffle_pos ASC", }; +/* Shuffle RNG state */ +struct rng_ctx shuffle_rng; + static char *db_path; static __thread sqlite3 *hdl; @@ -887,6 +894,24 @@ db_transaction_end(void) } } +void +db_transaction_rollback(void) +{ + char *query = "ROLLBACK TRANSACTION;"; + char *errmsg; + int ret; + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + + ret = db_exec(query, &errmsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "SQL error running '%s': %s\n", query, errmsg); + + sqlite3_free(errmsg); + } +} + /* Queries */ static int @@ -2686,27 +2711,6 @@ db_file_update(struct media_file_info *mfi) #undef Q_TMPL } -void -db_file_update_icy(int id, char *artist, char *album) -{ -#define Q_TMPL "UPDATE files SET artist = TRIM(%Q), album = TRIM(%Q) WHERE id = %d;" - char *query; - - if (id == 0) - return; - - query = sqlite3_mprintf(Q_TMPL, artist, album, id); - if (!query) - { - DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); - - return; - } - - db_query_run(query, 1, 0); -#undef Q_TMPL -} - void db_file_save_seek(int id, uint32_t seek) { @@ -4138,6 +4142,1452 @@ db_speaker_clear_all(void) db_query_run("UPDATE speakers SET selected = 0;", 0, 0); } +/* Queue */ + +void +free_queue_item(struct db_queue_item *queue_item, int content_only) +{ + if (queue_item->path) + free(queue_item->path); + if (queue_item->virtual_path) + free(queue_item->virtual_path); + if (queue_item->title) + free(queue_item->title); + if (queue_item->artist) + free(queue_item->artist); + if (queue_item->album_artist) + free(queue_item->album_artist); + if (queue_item->album) + free(queue_item->album); + if (queue_item->genre) + free(queue_item->genre); + if (queue_item->artist_sort) + free(queue_item->artist_sort); + if (queue_item->album_sort) + free(queue_item->album_sort); + if (queue_item->album_artist_sort) + free(queue_item->album_artist_sort); + + if (!content_only && queue_item) + free(queue_item); +} + +/* + * Returns the queue version from the admin table + * + * @return queue version + */ +int +db_queue_get_version() +{ + char *version; + int32_t queue_version; + int ret; + + queue_version = 0; + version = db_admin_get("queue_version"); + if (version) + { + ret = safe_atoi32(version, &queue_version); + free(version); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Could not get playlist version\n"); + return -1; + } + } + + return queue_version; +} + +/* + * Increments the version of the queue in the admin table and notifies listener of LISTENER_PLAYLIST + * about the change. + * + * This function must be called after successfully modifying the queue table in order to send + * notification messages to the clients (e. g. dacp or mpd clients). + */ +static void +queue_inc_version_and_notify() +{ + int queue_version; + char version[10]; + int ret; + + db_transaction_begin(); + + queue_version = db_queue_get_version(); + if (queue_version < 0) + queue_version = 0; + + queue_version++; + ret = snprintf(version, sizeof(version), "%d", queue_version); + if (ret >= sizeof(version)) + { + DPRINTF(E_LOG, L_DB, "Error incrementing queue version. Could not convert version to string: %d\n", queue_version); + db_transaction_rollback(); + return; + } + + ret = db_admin_update("queue_version", version); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Error incrementing queue version. Could not update version in admin table: %d\n", queue_version); + db_transaction_rollback(); + return; + } + + db_transaction_end(); + + listener_notify(LISTENER_PLAYLIST); +} + +void +db_queue_update_icymetadata(int id, char *artist, char *album) +{ +#define Q_TMPL "UPDATE queue SET artist = TRIM(%Q), album = TRIM(%Q) WHERE id = %d;" + char *query; + + if (id == 0) + return; + + query = sqlite3_mprintf(Q_TMPL, artist, album, id); + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + + return; + } + + db_query_run(query, 1, 0); + queue_inc_version_and_notify(); + +#undef Q_TMPL +} + +static int +queue_add_file(struct db_media_file_info *dbmfi, int pos, int shuffle_pos) +{ +#define Q_TMPL "INSERT INTO queue " \ + "(id, file_id, song_length, data_kind, media_kind, " \ + "pos, shuffle_pos, path, virtual_path, title, " \ + "artist, album_artist, album, genre, songalbumid, " \ + "time_modified, artist_sort, album_sort, album_artist_sort, year, " \ + "track, disc)" \ + "VALUES" \ + "(NULL, %s, %s, %s, %s, " \ + "%d, %d, %Q, %Q, %Q, " \ + "%Q, %Q, %Q, %Q, %s, " \ + "%s, %Q, %Q, %Q, %s, " \ + "%s, %s);" + + char *query; + int ret; + + query = sqlite3_mprintf(Q_TMPL, + dbmfi->id, dbmfi->song_length, dbmfi->data_kind, dbmfi->media_kind, + pos, pos, dbmfi->path, dbmfi->virtual_path, dbmfi->title, + dbmfi->artist, dbmfi->album_artist, dbmfi->album, dbmfi->genre, dbmfi->songalbumid, + dbmfi->time_modified, dbmfi->artist_sort, dbmfi->album_sort, dbmfi->album_artist_sort, dbmfi->year, + dbmfi->track, dbmfi->disc); + ret = db_query_run(query, 1, 0); + + return ret; + +#undef Q_TMPL +} + +/* + * Adds the files matching the given query to the queue after the item with the given item id + * + * The files table is queried with the given parameters and all found files are added after the + * item with the given item id to the "normal" queue. They are appended to end of the shuffled queue + * (assuming that the shuffled queue will get reshuffled after adding new items). + * + * The function returns -1 on failure (e. g. error reading from database) and if the given item id + * does not exist. It wraps all database access in a transaction and performs a rollback if an error + * occurs, leaving the queue in a consistent state. + * + * @param qp Query parameters for the files table + * @param item_id Files are added after item with this id + * @return 0 on success, -1 on failure + */ +int +db_queue_add_by_queryafteritemid(struct query_params *qp, uint32_t item_id) +{ + struct db_media_file_info dbmfi; + char *query; + int shuffle_pos; + int pos; + int ret; + + db_transaction_begin(); + + // Position of the first new item + pos = db_queue_get_pos(item_id, 0); + if (pos < 0) + { + DPRINTF(E_LOG, L_DB, "Could not fetch queue item for item-id %d\n", item_id); + db_transaction_rollback(); + return -1; + } + pos++; + + // Shuffle position of the first new item + shuffle_pos = db_queue_get_count(); + if (shuffle_pos < 0) + { + DPRINTF(E_LOG, L_DB, "Could not get count from queue\n"); + db_transaction_rollback(); + return -1; + } + + // Start query for new items from files table + ret = db_query_start(qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Could not start query\n"); + db_transaction_rollback(); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Player queue query returned %d items\n", qp->results); + + // Update pos for all items after the item with item_id + query = sqlite3_mprintf("UPDATE queue SET pos = pos + %d WHERE pos > %d;", qp->results, (pos - 1)); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Iterate over new items from files table and insert into queue + while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id)) + { + ret = queue_add_file(&dbmfi, pos, shuffle_pos); + + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Failed to add song with id %s (%s) to queue\n", dbmfi.id, dbmfi.title); + break; + } + + DPRINTF(E_DBG, L_DB, "Added song with id %s (%s) to queue\n", dbmfi.id, dbmfi.title); + shuffle_pos++; + pos++; + } + + db_query_end(qp); + + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Error fetching results\n"); + db_transaction_rollback(); + return -1; + } + + db_transaction_end(); + + queue_inc_version_and_notify(); + + return 0; +} + +/* + * Adds the files matching the given query to the queue + * + * The files table is queried with the given parameters and all found files are added to the end of the + * "normal" queue and the shuffled queue. + * + * The function returns -1 on failure (e. g. error reading from database). It wraps all database access + * in a transaction and performs a rollback if an error occurs, leaving the queue in a consistent state. + * + * @param qp Query parameters for the files table + * @param reshuffle If 1 queue will be reshuffled after adding new items + * @param item_id The base item id, all items after this will be reshuffled + * @return 0 on success, -1 on failure + */ +int +db_queue_add_by_query(struct query_params *qp, char reshuffle, uint32_t item_id) +{ + struct db_media_file_info dbmfi; + int pos; + int ret; + + db_transaction_begin(); + + pos = db_queue_get_count(); + if (pos < 0) + { + DPRINTF(E_LOG, L_DB, "Could not get count from queue\n"); + db_transaction_rollback(); + return -1; + } + + ret = db_query_start(qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Could not start query\n"); + db_transaction_rollback(); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Player queue query returned %d items\n", qp->results); + + while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id)) + { + ret = queue_add_file(&dbmfi, pos, pos); + + if (ret < 0) + { + DPRINTF(E_DBG, L_DB, "Failed to add song id %s (%s)\n", dbmfi.id, dbmfi.title); + break; + } + + DPRINTF(E_DBG, L_DB, "Added song id %s (%s) to queue\n", dbmfi.id, dbmfi.title); + pos++; + } + + db_query_end(qp); + + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Error fetching results\n"); + db_transaction_rollback(); + return -1; + } + + ret = (int) sqlite3_last_insert_rowid(hdl); + + db_transaction_end(); + + // Reshuffle after adding new items + if (reshuffle) + { + db_queue_reshuffle(item_id); + } + else + { + queue_inc_version_and_notify(); + } + + return ret; +} + +/* + * Adds the items of the stored playlist with the given id to the end of the queue + * + * @param plid Id of the stored playlist + * @param reshuffle If 1 queue will be reshuffled after adding new items + * @param item_id The base item id, all items after this will be reshuffled + * @return 0 on success, -1 on failure + */ +int +db_queue_add_by_playlistid(int plid, char reshuffle, uint32_t item_id) +{ + struct query_params qp; + int ret; + + memset(&qp, 0, sizeof(struct query_params)); + + qp.id = plid; + qp.type = Q_PLITEMS; + + ret = db_queue_add_by_query(&qp, reshuffle, item_id); + + return ret; +} + +/* + * Adds the file with the given id to the queue + * + * @param id Id of the file + * @param reshuffle If 1 queue will be reshuffled after adding new items + * @param item_id The base item id, all items after this will be reshuffled + * @return 0 on success, -1 on failure + */ +int +db_queue_add_by_fileid(int id, char reshuffle, uint32_t item_id) +{ + struct query_params qp; + char buf[124]; + int ret; + + memset(&qp, 0, sizeof(struct query_params)); + + qp.type = Q_ITEMS; + snprintf(buf, sizeof(buf), "f.id = %" PRIu32, id); + qp.filter = buf; + + ret = db_queue_add_by_query(&qp, reshuffle, item_id); + + return ret; +} + +static int +queue_enum_start(struct query_params *query_params) +{ +#define Q_TMPL "SELECT * FROM queue WHERE %s %s;" + char *query; + const char *orderby; + int ret; + + query_params->stmt = NULL; + + if (query_params->sort) + orderby = sort_clause[query_params->sort]; + else + orderby = sort_clause[S_POS]; + + if (query_params->filter) + query = sqlite3_mprintf(Q_TMPL, query_params->filter, orderby); + else + query = sqlite3_mprintf(Q_TMPL, "1=1", orderby); + + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + + return -1; + } + + DPRINTF(E_DBG, L_DB, "Starting enum '%s'\n", query); + + ret = db_blocking_prepare_v2(query, -1, &query_params->stmt, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); + + sqlite3_free(query); + return -1; + } + + sqlite3_free(query); + + return 0; + +#undef Q_TMPL +} + +static inline char * +strdup_if(char *str, int cond) +{ + if (str == NULL) + return NULL; + + if (cond) + return strdup(str); + + return str; +} + +static int +queue_enum_fetch(struct query_params *query_params, struct db_queue_item *queue_item, int keep_item) +{ + int ret; + + memset(queue_item, 0, sizeof(struct db_queue_item)); + + if (!query_params->stmt) + { + DPRINTF(E_LOG, L_DB, "Queue enum not started!\n"); + return -1; + } + + ret = db_blocking_step(query_params->stmt); + if (ret == SQLITE_DONE) + { + DPRINTF(E_DBG, L_DB, "End of queue enum results\n"); + return 0; + } + else if (ret != SQLITE_ROW) + { + DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + return -1; + } + + queue_item->id = (uint32_t)sqlite3_column_int(query_params->stmt, 0); + queue_item->file_id = (uint32_t)sqlite3_column_int(query_params->stmt, 1); + queue_item->pos = (uint32_t)sqlite3_column_int(query_params->stmt, 2); + queue_item->shuffle_pos = (uint32_t)sqlite3_column_int(query_params->stmt, 3); + queue_item->data_kind = sqlite3_column_int(query_params->stmt, 4); + queue_item->media_kind = sqlite3_column_int(query_params->stmt, 5); + queue_item->song_length = (uint32_t)sqlite3_column_int(query_params->stmt, 6); + queue_item->path = strdup_if((char *)sqlite3_column_text(query_params->stmt, 7), keep_item); + queue_item->virtual_path = strdup_if((char *)sqlite3_column_text(query_params->stmt, 8), keep_item); + queue_item->title = strdup_if((char *)sqlite3_column_text(query_params->stmt, 9), keep_item); + queue_item->artist = strdup_if((char *)sqlite3_column_text(query_params->stmt, 10), keep_item); + queue_item->album_artist = strdup_if((char *)sqlite3_column_text(query_params->stmt, 11), keep_item); + queue_item->album = strdup_if((char *)sqlite3_column_text(query_params->stmt, 12), keep_item); + queue_item->genre = strdup_if((char *)sqlite3_column_text(query_params->stmt, 13), keep_item); + queue_item->songalbumid = sqlite3_column_int64(query_params->stmt, 14); + queue_item->time_modified = sqlite3_column_int(query_params->stmt, 15); + queue_item->artist_sort = strdup_if((char *)sqlite3_column_text(query_params->stmt, 16), keep_item); + queue_item->album_sort = strdup_if((char *)sqlite3_column_text(query_params->stmt, 17), keep_item); + queue_item->album_artist_sort = strdup_if((char *)sqlite3_column_text(query_params->stmt, 18), keep_item); + queue_item->year = sqlite3_column_int(query_params->stmt, 19); + queue_item->track = sqlite3_column_int(query_params->stmt, 20); + queue_item->disc = sqlite3_column_int(query_params->stmt, 21); + + return 0; +} + +int +db_queue_enum_start(struct query_params *query_params) +{ + int ret; + + db_transaction_begin(); + + ret = queue_enum_start(query_params); + + if (ret < 0) + db_transaction_rollback(); + + return ret; +} + +void +db_queue_enum_end(struct query_params *query_params) +{ + db_query_end(query_params); + db_transaction_end(); +} + +int +db_queue_enum_fetch(struct query_params *query_params, struct db_queue_item *queue_item) +{ + return queue_enum_fetch(query_params, queue_item, 0); +} + +int +db_queue_get_pos(uint32_t item_id, char shuffle) +{ +#define Q_TMPL "SELECT pos FROM queue WHERE id = %d;" +#define Q_TMPL_SHUFFLE "SELECT shuffle_pos FROM queue WHERE id = %d;" + + char *query; + int pos; + + if (shuffle) + query = sqlite3_mprintf(Q_TMPL_SHUFFLE, item_id); + else + query = sqlite3_mprintf(Q_TMPL, item_id); + + pos = db_get_count(query); + + sqlite3_free(query); + + return pos; + +#undef Q_TMPL +#undef Q_TMPL_SHUFFLE +} + +int +db_queue_get_pos_byfileid(uint32_t file_id, char shuffle) +{ +#define Q_TMPL "SELECT pos FROM queue WHERE file_id = %d LIMIT 1;" +#define Q_TMPL_SHUFFLE "SELECT shuffle_pos FROM queue WHERE file_id = %d LIMIT 1;" + + char *query; + int pos; + + if (shuffle) + query = sqlite3_mprintf(Q_TMPL_SHUFFLE, file_id); + else + query = sqlite3_mprintf(Q_TMPL, file_id); + + pos = db_get_count(query); + + sqlite3_free(query); + + return pos; + +#undef Q_TMPL +#undef Q_TMPL_SHUFFLE +} + +static int +queue_fetch_byitemid(uint32_t item_id, struct db_queue_item *queue_item, int with_metadata) +{ + struct query_params query_params; + int ret; + + memset(&query_params, 0, sizeof(struct query_params)); + query_params.filter = sqlite3_mprintf("id = %d", item_id); + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + sqlite3_free(query_params.filter); + return -1; + } + + ret = queue_enum_fetch(&query_params, queue_item, with_metadata); + db_query_end(&query_params); + sqlite3_free(query_params.filter); + return ret; +} + +struct db_queue_item * +db_queue_fetch_byitemid(uint32_t item_id) +{ + struct db_queue_item *queue_item; + int ret; + + queue_item = calloc(1, sizeof(struct db_queue_item)); + if (!queue_item) + { + DPRINTF(E_LOG, L_DB, "Out of memory for queue_item\n"); + return NULL; + } + + db_transaction_begin(); + ret = queue_fetch_byitemid(item_id, queue_item, 1); + db_transaction_end(); + + if (ret < 0) + { + free_queue_item(queue_item, 0); + DPRINTF(E_LOG, L_DB, "Error fetching queue item by item id\n"); + return NULL; + } + else if (queue_item->id == 0) + { + // No item found + free_queue_item(queue_item, 0); + return NULL; + } + + return queue_item; +} + +struct db_queue_item * +db_queue_fetch_byfileid(uint32_t file_id) +{ + struct db_queue_item *queue_item; + struct query_params query_params; + int ret; + + memset(&query_params, 0, sizeof(struct query_params)); + queue_item = calloc(1, sizeof(struct db_queue_item)); + if (!queue_item) + { + DPRINTF(E_LOG, L_DB, "Out of memory for queue_item\n"); + return NULL; + } + + db_transaction_begin(); + + query_params.filter = sqlite3_mprintf("file_id = %d", file_id); + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + sqlite3_free(query_params.filter); + db_transaction_end(); + free_queue_item(queue_item, 0); + DPRINTF(E_LOG, L_DB, "Error fetching queue item by file id\n"); + return NULL; + } + + ret = queue_enum_fetch(&query_params, queue_item, 1); + db_query_end(&query_params); + sqlite3_free(query_params.filter); + db_transaction_end(); + + if (ret < 0) + { + free_queue_item(queue_item, 0); + DPRINTF(E_LOG, L_DB, "Error fetching queue item by file id\n"); + return NULL; + } + else if (queue_item->id == 0) + { + // No item found + free_queue_item(queue_item, 0); + return NULL; + } + + return queue_item; +} + +static int +queue_fetch_bypos(uint32_t pos, char shuffle, struct db_queue_item *queue_item, int with_metadata) +{ + struct query_params query_params; + int ret; + + memset(&query_params, 0, sizeof(struct query_params)); + if (shuffle) + query_params.filter = sqlite3_mprintf("shuffle_pos = %d", pos); + else + query_params.filter = sqlite3_mprintf("pos = %d", pos); + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + sqlite3_free(query_params.filter); + return -1; + } + + ret = queue_enum_fetch(&query_params, queue_item, with_metadata); + db_query_end(&query_params); + sqlite3_free(query_params.filter); + return ret; +} + +struct db_queue_item * +db_queue_fetch_bypos(uint32_t pos, char shuffle) +{ + struct db_queue_item *queue_item; + int ret; + + queue_item = calloc(1, sizeof(struct db_queue_item)); + if (!queue_item) + { + DPRINTF(E_LOG, L_MAIN, "Out of memory for queue_item\n"); + return NULL; + } + + db_transaction_begin(); + ret = queue_fetch_bypos(pos, shuffle, queue_item, 1); + db_transaction_end(); + + if (ret < 0) + { + free_queue_item(queue_item, 0); + DPRINTF(E_LOG, L_DB, "Error fetching queue item by pos id\n"); + return NULL; + } + else if (queue_item->id == 0) + { + // No item found + free_queue_item(queue_item, 0); + return NULL; + } + + return queue_item; +} + +static int +queue_fetch_byposrelativetoitem(int pos, uint32_t item_id, char shuffle, struct db_queue_item *queue_item, int with_metadata) +{ + int pos_absolute; + int ret; + + DPRINTF(E_DBG, L_DB, "Fetch by pos: pos (%d) relative to item with id (%d)\n", pos, item_id); + + pos_absolute = db_queue_get_pos(item_id, shuffle); + if (pos_absolute < 0) + { + return -1; + } + + DPRINTF(E_DBG, L_DB, "Fetch by pos: item (%d) has absolute pos %d\n", item_id, pos_absolute); + + pos_absolute += pos; + + ret = queue_fetch_bypos(pos_absolute, shuffle, queue_item, with_metadata); + + DPRINTF(E_DBG, L_DB, "Fetch by pos: fetched item (id=%d, pos=%d, file-id=%d)\n", queue_item->id, queue_item->pos, queue_item->file_id); + + return ret; +} + +struct db_queue_item * +db_queue_fetch_byposrelativetoitem(int pos, uint32_t item_id, char shuffle) +{ + struct db_queue_item *queue_item; + int ret; + + DPRINTF(E_DBG, L_DB, "Fetch by pos: pos (%d) relative to item with id (%d)\n", pos, item_id); + + queue_item = calloc(1, sizeof(struct db_queue_item)); + if (!queue_item) + { + DPRINTF(E_LOG, L_MAIN, "Out of memory for queue_item\n"); + return NULL; + } + + db_transaction_begin(); + + ret = queue_fetch_byposrelativetoitem(pos, item_id, shuffle, queue_item, 1); + + db_transaction_end(); + + if (ret < 0) + { + free_queue_item(queue_item, 0); + DPRINTF(E_LOG, L_DB, "Error fetching queue item by pos relative to item id\n"); + return NULL; + } + else if (queue_item->id == 0) + { + // No item found + free_queue_item(queue_item, 0); + return NULL; + } + + DPRINTF(E_DBG, L_DB, "Fetch by pos: fetched item (id=%d, pos=%d, file-id=%d)\n", queue_item->id, queue_item->pos, queue_item->file_id); + + return queue_item; +} + +struct db_queue_item * +db_queue_fetch_next(uint32_t item_id, char shuffle) +{ + return db_queue_fetch_byposrelativetoitem(1, item_id, shuffle); +} + +struct db_queue_item * +db_queue_fetch_prev(uint32_t item_id, char shuffle) +{ + return db_queue_fetch_byposrelativetoitem(-1, item_id, shuffle); +} + +/* + * Remove files that are disabled or non existant in the library and repair ordering of + * the queue (shuffle and normal) + */ +int +db_queue_cleanup() +{ +#define Q_TMPL "DELETE FROM queue WHERE NOT file_id IN (SELECT id from files WHERE disabled = 0);" +#define Q_TMPL_UPDATE "UPDATE queue SET pos = %d WHERE id = %d;" +#define Q_TMPL_UPDATE_SHUFFLE "UPDATE queue SET shuffle_pos = %d WHERE id = %d;" + + struct query_params query_params; + struct db_queue_item queue_item; + char *query; + int pos; + int deleted; + int ret; + + db_transaction_begin(); + + ret = db_query_run(Q_TMPL, 0, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + deleted = sqlite3_changes(hdl); + if (deleted <= 0) + { + // Nothing to do + db_transaction_end(); + return 0; + } + + // Update position of normal queue + memset(&query_params, 0, sizeof(struct query_params)); + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + pos = 0; + while ((ret = queue_enum_fetch(&query_params, &queue_item, 0)) == 0 && (queue_item.id > 0)) + { + if (queue_item.pos != pos) + { + query = sqlite3_mprintf(Q_TMPL_UPDATE, pos, queue_item.id); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Failed to update item with item-id: %d\n", queue_item.id); + break; + } + } + + pos++; + } + + db_query_end(&query_params); + + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update position of shuffle queue + memset(&query_params, 0, sizeof(struct query_params)); + query_params.sort = S_SHUFFLE_POS; + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + pos = 0; + while ((ret = queue_enum_fetch(&query_params, &queue_item, 0)) == 0 && (queue_item.id > 0)) + { + if (queue_item.shuffle_pos != pos) + { + query = sqlite3_mprintf(Q_TMPL_UPDATE_SHUFFLE, pos, queue_item.id); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Failed to update item with item-id: %d\n", queue_item.id); + break; + } + } + + pos++; + } + + db_query_end(&query_params); + + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + db_transaction_end(); + queue_inc_version_and_notify(); + + return 0; + +#undef Q_TMPL_UPDATE_SHUFFLE +#undef Q_TMPL_UPDATE +#undef Q_TMPL +} + +int +db_queue_clear() +{ + int ret; + + db_transaction_begin(); + ret = db_query_run("DELETE FROM queue;", 0, 0); + + if (ret < 0) + { + db_transaction_rollback(); + } + else + { + db_transaction_end(); + queue_inc_version_and_notify(); + } + return ret; +} + +static int +queue_delete_item(struct db_queue_item *queue_item) +{ + char *query; + int ret; + + // Remove item with the given item_id + query = sqlite3_mprintf("DELETE FROM queue where id = %d;", queue_item->id); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + return -1; + } + + // Update pos for all items after the item with given item_id + query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", queue_item->pos); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + return -1; + } + + // Update shuffle_pos for all items after the item with given item_id + query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos - 1 WHERE shuffle_pos > %d;", queue_item->shuffle_pos); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + return -1; + } + + return 0; +} + +int +db_queue_delete_byitemid(uint32_t item_id) +{ + struct db_queue_item queue_item; + int ret; + + db_transaction_begin(); + + ret = queue_fetch_byitemid(item_id, &queue_item, 0); + + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + if (queue_item.id == 0) + { + db_transaction_end(); + return 0; + } + + ret = queue_delete_item(&queue_item); + + if (ret < 0) + { + db_transaction_rollback(); + } + else + { + db_transaction_end(); + queue_inc_version_and_notify(); + } + + return ret; +} + +int +db_queue_delete_bypos(uint32_t pos, int count) +{ + struct query_params query_params; + struct db_queue_item queue_item; + int to_pos; + int ret; + + // Find items with the given position + memset(&query_params, 0, sizeof(struct query_params)); + + to_pos = pos + count; + query_params.filter = sqlite3_mprintf("pos => %d AND pos < %d", pos, to_pos); + + db_transaction_begin(); + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + return -1; + } + + while ((ret = queue_enum_fetch(&query_params, &queue_item, 0)) == 0 && (queue_item.id > 0)) + { + ret = queue_delete_item(&queue_item); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Failed to delete item with item-id: %d\n", queue_item.id); + break; + } + } + + db_query_end(&query_params); + sqlite3_free(query_params.filter); + + if (ret < 0) + { + db_transaction_rollback(); + } + else + { + db_transaction_end(); + queue_inc_version_and_notify(); + } + + return ret; +} + +int +db_queue_delete_byposrelativetoitem(uint32_t pos, uint32_t item_id, char shuffle) +{ + struct db_queue_item queue_item; + int ret; + + db_transaction_begin(); + + ret = queue_fetch_byposrelativetoitem(pos, item_id, shuffle, &queue_item, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + else if (queue_item.id == 0) + { + // No item found + db_transaction_end(); + return 0; + } + + ret = queue_delete_item(&queue_item); + + if (ret < 0) + { + db_transaction_rollback(); + } + else + { + db_transaction_end(); + queue_inc_version_and_notify(); + } + + return ret; +} + +/* + * Moves the queue item with the given id to the given position (zero-based). + * + * @param item_id Queue item id + * @param pos_to target position in the queue (zero-based) + * @return 0 on success, -1 on failure + */ +int +db_queue_move_byitemid(uint32_t item_id, int pos_to) +{ + char *query; + int pos_from; + int ret; + + db_transaction_begin(); + + // Find item with the given item_id + pos_from = db_queue_get_pos(item_id, 0); + if (pos_from < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update pos for all items after the item with given item_id + query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", pos_from); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update pos for all items from the given pos_to + query = sqlite3_mprintf("UPDATE queue SET pos = pos + 1 WHERE pos >= %d;", pos_to); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update item with the given item_id + query = sqlite3_mprintf("UPDATE queue SET pos = %d where id = %d;", pos_to, item_id); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + db_transaction_end(); + queue_inc_version_and_notify(); + + return 0; +} + +/* + * Moves the queue item at the given position to the given position (zero-based). + * + * @param pos_from Position of the queue item to move + * @param pos_to target position in the queue (zero-based) + * @return 0 on success, -1 on failure + */ +int +db_queue_move_bypos(int pos_from, int pos_to) +{ + struct db_queue_item queue_item; + char *query; + int ret; + + db_transaction_begin(); + + // Find item to move + ret = queue_fetch_bypos(pos_from, 0, &queue_item, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + if (queue_item.id == 0) + { + db_transaction_end(); + return 0; + } + + // Update pos for all items after the item with given position + query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", queue_item.pos); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update pos for all items from the given pos_to + query = sqlite3_mprintf("UPDATE queue SET pos = pos + 1 WHERE pos >= %d;", pos_to); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update item with the given item_id + query = sqlite3_mprintf("UPDATE queue SET pos = %d where id = %d;", pos_to, queue_item.id); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + db_transaction_end(); + queue_inc_version_and_notify(); + + return 0; +} + +/* + * Moves the queue item at the given position to the given target position. The positions + * are relavtive to the given base item (item id). + * + * @param from_pos Relative position of the queue item to the base item + * @param to_offset Target position relative to the base item + * @param item_id The base item id (normaly the now playing item) + * @return 0 on success, -1 on failure + */ +int +db_queue_move_byposrelativetoitem(uint32_t from_pos, uint32_t to_offset, uint32_t item_id, char shuffle) +{ + struct db_queue_item queue_item; + char *query; + int pos_move_from; + int pos_move_to; + int ret; + + db_transaction_begin(); + + DPRINTF(E_DBG, L_DB, "Move by pos: from %d offset %d relative to item (%d)\n", from_pos, to_offset, item_id); + + // Find item with the given item_id + ret = queue_fetch_byitemid(item_id, &queue_item, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Move by pos: base item (id=%d, pos=%d, file-id=%d)\n", queue_item.id, queue_item.pos, queue_item.file_id); + + if (queue_item.id == 0) + { + db_transaction_end(); + return 0; + } + + // Calculate the position of the item to move + if (shuffle) + pos_move_from = queue_item.shuffle_pos + from_pos; + else + pos_move_from = queue_item.pos + from_pos; + + // Calculate the position where to move the item to + if (shuffle) + pos_move_to = queue_item.shuffle_pos + to_offset; + else + pos_move_to = queue_item.pos + to_offset; + + if (pos_move_to < pos_move_from) + { + /* + * Moving an item to a previous position seems to send an offset incremented by one + */ + pos_move_to++; + } + + DPRINTF(E_DBG, L_DB, "Move by pos: absolute pos: move from %d to %d\n", pos_move_from, pos_move_to); + + // Find item to move + ret = queue_fetch_bypos(pos_move_from, shuffle, &queue_item, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Move by pos: move item (id=%d, pos=%d, file-id=%d)\n", queue_item.id, queue_item.pos, queue_item.file_id); + + if (queue_item.id == 0) + { + db_transaction_end(); + return 0; + } + + // Update pos for all items after the item with given position + if (shuffle) + query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos - 1 WHERE shuffle_pos > %d;", queue_item.shuffle_pos); + else + query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", queue_item.pos); + + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update pos for all items from the given pos_to + if (shuffle) + query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos + 1 WHERE shuffle_pos >= %d;", pos_move_to); + else + query = sqlite3_mprintf("UPDATE queue SET pos = pos + 1 WHERE pos >= %d;", pos_move_to); + + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + // Update item with the given item_id + if (shuffle) + query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = %d where id = %d;", pos_move_to, queue_item.id); + else + query = sqlite3_mprintf("UPDATE queue SET pos = %d where id = %d;", pos_move_to, queue_item.id); + + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + db_transaction_end(); + queue_inc_version_and_notify(); + + return 0; +} + +/* + * Reshuffles the shuffle queue + * + * If the given item_id is 0, the whole shuffle queue is reshuffled, otherwise the + * queue is reshuffled after the item with the given id (excluding this item). + * + * @param item_id The base item, after this item the queue is reshuffled + * @return 0 on success, -1 on failure + */ +int +db_queue_reshuffle(uint32_t item_id) +{ + char *query; + int pos; + int count; + struct db_queue_item queue_item; + int *shuffle_pos; + int len; + int i; + struct query_params query_params; + int ret; + + db_transaction_begin(); + + DPRINTF(E_DBG, L_DB, "Reshuffle queue after item with item-id: %d\n", item_id); + + // Reset the shuffled order + ret = db_query_run("UPDATE queue SET shuffle_pos = pos;", 0, 0); + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + pos = 0; + if (item_id > 0) + { + pos = db_queue_get_pos(item_id, 0); + if (pos < 0) + { + db_transaction_rollback(); + return -1; + } + + pos++; // Do not reshuffle the base item + } + + count = db_queue_get_count(); + + len = count - pos; + + DPRINTF(E_DBG, L_DB, "Reshuffle %d items off %d total items, starting from pos %d\n", len, count, pos); + + shuffle_pos = malloc(len * sizeof(int)); + for (i = 0; i < len; i++) + { + shuffle_pos[i] = i + pos; + } + + shuffle_int(&shuffle_rng, shuffle_pos, len); + + + + memset(&query_params, 0, sizeof(struct query_params)); + query_params.filter = sqlite3_mprintf("pos >= %d", pos); + + ret = queue_enum_start(&query_params); + if (ret < 0) + { + sqlite3_free(query_params.filter); + db_transaction_rollback(); + return -1; + } + + i = 0; + while ((ret = queue_enum_fetch(&query_params, &queue_item, 0)) == 0 && (queue_item.id > 0) && (i < len)) + { + query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = %d where id = %d;", shuffle_pos[i], queue_item.id); + ret = db_query_run(query, 1, 0); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Failed to delete item with item-id: %d\n", queue_item.id); + break; + } + + i++; + } + + db_query_end(&query_params); + sqlite3_free(query_params.filter); + + if (ret < 0) + { + db_transaction_rollback(); + return -1; + } + + db_transaction_end(); + queue_inc_version_and_notify(); + + return 0; +} + +int +db_queue_get_count() +{ + return db_get_count("SELECT COUNT(*) FROM queue;"); +} + /* Inotify */ int @@ -5012,6 +6462,8 @@ db_init(void) DPRINTF(E_LOG, L_DB, "Database OK with %d active files and %d active playlists\n", files, pls); + rng_init(&shuffle_rng); + return 0; } diff --git a/src/db.h b/src/db.h index f541ee10..47e212f6 100644 --- a/src/db.h +++ b/src/db.h @@ -28,6 +28,8 @@ enum sort_type { S_DISC, S_TRACK, S_VPATH, + S_POS, + S_SHUFFLE_POS, }; #define Q_F_BROWSE (1 << 15) @@ -374,6 +376,49 @@ struct directory_enum { sqlite3_stmt *stmt; }; +struct db_queue_item +{ + /* A unique id for this queue item. If the same item appears multiple + times in the queue each corresponding queue item has its own id. */ + uint32_t id; + + /* Id of the file/item in the files database */ + uint32_t file_id; + + /* Length of the item in ms */ + uint32_t song_length; + + /* Data type of the item */ + enum data_kind data_kind; + /* Media type of the item */ + enum media_kind media_kind; + + uint32_t seek; + + uint32_t pos; + uint32_t shuffle_pos; + + char *path; + char *virtual_path; + + char *title; + char *artist; + char *album_artist; + char *album; + char *genre; + + int64_t songalbumid; + uint32_t time_modified; + + char *artist_sort; + char *album_sort; + char *album_artist_sort; + + uint32_t year; + uint32_t track; + uint32_t disc; +}; + char * db_escape_string(const char *str); @@ -392,6 +437,9 @@ free_pli(struct playlist_info *pli, int content_only); void free_di(struct directory_info *di, int content_only); +void +free_queue_item(struct db_queue_item *queue_item, int content_only); + /* Maintenance and DB hygiene */ void db_hook_post_scan(void); @@ -409,6 +457,9 @@ db_transaction_begin(void); void db_transaction_end(void); +void +db_transaction_rollback(void); + /* Queries */ int db_query_start(struct query_params *qp); @@ -498,9 +549,6 @@ db_file_add(struct media_file_info *mfi); int db_file_update(struct media_file_info *mfi); -void -db_file_update_icy(int id, char *artist, char *album); - void db_file_save_seek(int id, uint32_t seek); @@ -646,6 +694,88 @@ db_speaker_get(uint64_t id, int *selected, int *volume); void db_speaker_clear_all(void); +/* Queue */ +int +db_queue_get_version(); + +void +db_queue_update_icymetadata(int id, char *artist, char *album); + +int +db_queue_add_by_queryafteritemid(struct query_params *qp, uint32_t item_id); + +int +db_queue_add_by_query(struct query_params *qp, char reshuffle, uint32_t item_id); + +int +db_queue_add_by_playlistid(int plid, char reshuffle, uint32_t item_id); + +int +db_queue_add_by_fileid(int id, char reshuffle, uint32_t item_id); + +int +db_queue_enum_start(struct query_params *query_params); + +void +db_queue_enum_end(struct query_params *query_params); + +int +db_queue_enum_fetch(struct query_params *query_params, struct db_queue_item *queue_item); + +struct db_queue_item * +db_queue_fetch_byitemid(uint32_t item_id); + +struct db_queue_item * +db_queue_fetch_byfileid(uint32_t file_id); + +struct db_queue_item * +db_queue_fetch_bypos(uint32_t pos, char shuffle); + +struct db_queue_item * +db_queue_fetch_byposrelativetoitem(int pos, uint32_t item_id, char shuffle); + +struct db_queue_item * +db_queue_fetch_next(uint32_t item_id, char shuffle); + +struct db_queue_item * +db_queue_fetch_prev(uint32_t item_id, char shuffle); + +int +db_queue_cleanup(); + +int +db_queue_clear(); + +int +db_queue_delete_byitemid(uint32_t item_id); + +int +db_queue_delete_bypos(uint32_t pos, int count); + +int +db_queue_delete_byposrelativetoitem(uint32_t pos, uint32_t item_id, char shuffle); + +int +db_queue_move_byitemid(uint32_t item_id, int pos_to); + +int +db_queue_move_bypos(int pos_from, int pos_to); + +int +db_queue_move_byposrelativetoitem(uint32_t from_pos, uint32_t to_offset, uint32_t item_id, char shuffle); + +int +db_queue_reshuffle(uint32_t item_id); + +int +db_queue_get_count(); + +int +db_queue_get_pos(uint32_t item_id, char shuffle); + +int +db_queue_get_pos_byfileid(uint32_t file_id, char shuffle); + /* Inotify */ int db_watch_clear(void); diff --git a/src/db_init.c b/src/db_init.c index 6eb57490..b92dd822 100644 --- a/src/db_init.c +++ b/src/db_init.c @@ -158,6 +158,32 @@ " parent_id INTEGER DEFAULT 0" \ ");" +#define T_QUEUE \ + "CREATE TABLE IF NOT EXISTS queue (" \ + " id INTEGER PRIMARY KEY NOT NULL," \ + " file_id INTEGER NOT NULL," \ + " pos INTEGER NOT NULL," \ + " shuffle_pos INTEGER NOT NULL," \ + " data_kind INTEGER NOT NULL," \ + " media_kind INTEGER NOT NULL," \ + " song_length INTEGER NOT NULL," \ + " path VARCHAR(4096) NOT NULL," \ + " virtual_path VARCHAR(4096) NOT NULL," \ + " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ + " songalbumid INTEGER NOT NULL," \ + " time_modified INTEGER DEFAULT 0," \ + " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " year INTEGER DEFAULT 0," \ + " track INTEGER DEFAULT 0," \ + " disc INTEGER DEFAULT 0" \ + ");" + #define TRG_GROUPS_INSERT_FILES \ "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ " BEGIN" \ @@ -216,6 +242,9 @@ "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ " VALUES (4, '/spotify:', 0, 4294967296, 1);" +#define Q_QUEUE_VERSION \ + "INSERT INTO admin (key, value) VALUES ('queue_version', '0');" + #define Q_SCVER_MAJOR \ "INSERT INTO admin (key, value) VALUES ('schema_version_major', '%d');" #define Q_SCVER_MINOR \ @@ -237,6 +266,7 @@ static const struct db_init_query db_init_table_queries[] = { T_SPEAKERS, "create table speakers" }, { T_INOTIFY, "create table inotify" }, { T_DIRECTORIES, "create table directories" }, + { T_QUEUE, "create table queue" }, { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" }, { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" }, @@ -251,6 +281,7 @@ static const struct db_init_query db_init_table_queries[] = { Q_DIR2, "create default base directory '/file:'" }, { Q_DIR3, "create default base directory '/http:'" }, { Q_DIR4, "create default base directory '/spotify:'" }, + { Q_QUEUE_VERSION, "initialize queue version" }, }; @@ -327,6 +358,12 @@ static const struct db_init_query db_init_table_queries[] = #define I_DIR_PARENT \ "CREATE INDEX IF NOT EXISTS idx_dir_parentid ON directories(parent_id);" +#define I_QUEUE_POS \ + "CREATE INDEX IF NOT EXISTS idx_queue_pos ON queue(pos);" + +#define I_QUEUE_SHUFFLEPOS \ + "CREATE INDEX IF NOT EXISTS idx_queue_shufflepos ON queue(shuffle_pos);" + static const struct db_init_query db_init_index_queries[] = { { I_RESCAN, "create rescan index" }, @@ -357,6 +394,9 @@ static const struct db_init_query db_init_index_queries[] = { I_DIR_VPATH, "create directories disabled_virtualpath index" }, { I_DIR_PARENT, "create directories parentid index" }, + + { I_QUEUE_POS, "create queue pos index" }, + { I_QUEUE_SHUFFLEPOS, "create queue shuffle pos index" }, }; int diff --git a/src/db_init.h b/src/db_init.h index befa6772..88287f18 100644 --- a/src/db_init.h +++ b/src/db_init.h @@ -26,7 +26,7 @@ * is a major upgrade. In other words minor version upgrades permit downgrading * forked-daapd after the database was upgraded. */ #define SCHEMA_VERSION_MAJOR 19 -#define SCHEMA_VERSION_MINOR 00 +#define SCHEMA_VERSION_MINOR 01 int db_init_indices(sqlite3 *hdl); diff --git a/src/db_upgrade.c b/src/db_upgrade.c index 32b1c56d..6745409a 100644 --- a/src/db_upgrade.c +++ b/src/db_upgrade.c @@ -1441,6 +1441,54 @@ db_upgrade_v19(sqlite3 *hdl) return 0; } +/* Upgrade from schema v19.00 to v20.00 */ +/* Create new table queue for persistent playqueue + */ + +#define U_V2000_CREATE_TABLE_QUEUE \ + "CREATE TABLE IF NOT EXISTS queue (" \ + " id INTEGER PRIMARY KEY NOT NULL," \ + " file_id INTEGER NOT NULL," \ + " pos INTEGER NOT NULL," \ + " shuffle_pos INTEGER NOT NULL," \ + " data_kind INTEGER NOT NULL," \ + " media_kind INTEGER NOT NULL," \ + " song_length INTEGER NOT NULL," \ + " path VARCHAR(4096) NOT NULL," \ + " virtual_path VARCHAR(4096) NOT NULL," \ + " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ + " songalbumid INTEGER NOT NULL," \ + " time_modified INTEGER DEFAULT 0," \ + " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " year INTEGER DEFAULT 0," \ + " track INTEGER DEFAULT 0," \ + " disc INTEGER DEFAULT 0" \ + ");" + +#define U_V2000_QUEUE_VERSION \ + "INSERT INTO admin (key, value) VALUES ('queue_version', '0');" + +#define U_V2000_SCVER_MAJOR \ + "UPDATE admin SET value = '20' WHERE key = 'schema_version_major';" +#define U_V2000_SCVER_MINOR \ + "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v1901_queries[] = + { + { U_V2000_CREATE_TABLE_QUEUE, "create table directories" }, + { U_V2000_QUEUE_VERSION, "insert queue version" }, + + { U_V2000_SCVER_MAJOR, "set schema_version_major to 19" }, + { U_V2000_SCVER_MINOR, "set schema_version_minor to 01" }, + }; + + int db_upgrade(sqlite3 *hdl, int db_ver) { @@ -1551,6 +1599,13 @@ db_upgrade(sqlite3 *hdl, int db_ver) if (ret < 0) return -1; + /* FALLTHROUGH */ + + case 1900: + ret = db_generic_upgrade(hdl, db_upgrade_v1901_queries, sizeof(db_upgrade_v1901_queries) / sizeof(db_upgrade_v1901_queries[0])); + if (ret < 0) + return -1; + break; default: diff --git a/src/dmap_common.c b/src/dmap_common.c index 495a7bfb..d79675ef 100644 --- a/src/dmap_common.c +++ b/src/dmap_common.c @@ -559,3 +559,78 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru return 0; } + +int +dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item) +{ + int32_t val; + int want_mikd; + int want_asdk; + int want_ased; + int ret; + + dmap_add_int(song, "miid", queue_item->file_id); + dmap_add_string(song, "minm", queue_item->title); + dmap_add_long(song, "mper", queue_item->file_id); + dmap_add_int(song, "mcti", queue_item->file_id); + dmap_add_string(song, "asal", queue_item->album); + dmap_add_long(song, "asai", queue_item->songalbumid); + dmap_add_string(song, "asaa", queue_item->album_artist); + dmap_add_string(song, "asar", queue_item->artist); + dmap_add_int(song, "asdm", queue_item->time_modified); + dmap_add_short(song, "asdn", queue_item->disc); + dmap_add_string(song, "asgn", queue_item->genre); + dmap_add_int(song, "astm", queue_item->song_length); + dmap_add_short(song, "astn", queue_item->track); + dmap_add_short(song, "asyr", queue_item->year); + dmap_add_int(song, "aeMK", queue_item->media_kind); + dmap_add_char(song, "aeMk", queue_item->media_kind); + + dmap_add_string(song, "asfm", "wav"); + dmap_add_short(song, "asbr", 1411); + dmap_add_string(song, "asdt", "wav audio file"); + + want_mikd = 1;/* Will be prepended to the list *//* item kind */ + want_asdk = 1;/* Will be prepended to the list *//* data kind */ + want_ased = 1;/* Extradata not in media_file_info but flag for reply */ + + /* Required for artwork in iTunes, set songartworkcount (asac) = 1 */ + if (want_ased) + { + dmap_add_short(song, "ased", 1); + dmap_add_short(song, "asac", 1); + } + + val = 0; + if (want_mikd) + val += 9; + if (want_asdk) + val += 9; + + dmap_add_container(songlist, "mlit", evbuffer_get_length(song) + val); + + /* Prepend mikd & asdk if needed */ + if (want_mikd) + { + /* dmap.itemkind must come first */ + val = 2; /* music by default */ + dmap_add_char(songlist, "mikd", val); + } + if (want_asdk) + { + ret = queue_item->data_kind; + if (ret < 0) + val = 0; + dmap_add_char(songlist, "asdk", val); + } + + ret = evbuffer_add_buffer(songlist, song); + if (ret < 0) + { + DPRINTF(E_LOG, L_DAAP, "Could not add song to song list\n"); + + return -1; + } + + return 0; +} diff --git a/src/dmap_common.h b/src/dmap_common.h index 620ab37a..a595558d 100644 --- a/src/dmap_common.h +++ b/src/dmap_common.h @@ -84,4 +84,7 @@ dmap_send_error(struct evhttp_request *req, const char *container, const char *e int dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav); +int +dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item); + #endif /* !__DMAP_HELPERS_H__ */ diff --git a/src/filescanner.c b/src/filescanner.c index e973ab91..d8e93e2d 100644 --- a/src/filescanner.c +++ b/src/filescanner.c @@ -1227,6 +1227,7 @@ bulk_scan(int flags) DPRINTF(E_DBG, L_SCAN, "Purging old database content\n"); db_purge_cruft(start); + db_queue_cleanup(); cache_artwork_purge_cruft(start); DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec\n", difftime(end, start)); @@ -1244,6 +1245,7 @@ bulk_scan(int flags) static void * filescanner(void *arg) { + int clear_queue_on_stop_disabled; int ret; #if defined(__linux__) struct sched_param param; @@ -1284,6 +1286,19 @@ filescanner(void *arg) pthread_exit(NULL); } + // Only clear the queue if enabled (default) in config + clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable"); + if (!clear_queue_on_stop_disabled) + { + ret = db_queue_clear(); + if (ret < 0) + { + DPRINTF(E_LOG, L_SCAN, "Error: could not clear queue from DB\n"); + + pthread_exit(NULL); + } + } + /* Recompute all songartistids and songalbumids, in case the SQLite DB got transferred * to a different host; the hash is not portable. * It will also rebuild the groups we just cleared. @@ -1937,7 +1952,7 @@ filescanner_fullrescan(void *arg, int *retval) DPRINTF(E_LOG, L_SCAN, "Full rescan triggered\n"); player_playback_stop(); - player_queue_clear(); + db_queue_clear(); inofd_event_unset(); // Clears all inotify watches db_purge_all(); // Clears files, playlists, playlistitems, inotify and groups diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 46b321eb..6897d98f 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -51,7 +51,6 @@ #include "db.h" #include "daap_query.h" #include "player.h" -#include "queue.h" #include "listener.h" /* httpd event base, from httpd.c */ @@ -76,7 +75,7 @@ struct dacp_update_request { struct dacp_update_request *next; }; -typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); typedef void (*dacp_propset)(const char *value, struct evkeyvalq *query); struct dacp_prop_map { @@ -88,40 +87,40 @@ struct dacp_prop_map { /* Forward - properties getters */ static void -dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void -dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); /* Forward - properties setters */ static void @@ -165,16 +164,25 @@ static struct media_file_info dummy_mfi = .album = "(unknown album)", .genre = "(unknown genre)", }; +static struct db_queue_item dummy_queue_item = +{ + .file_id = 9999999, + .title = "(unknown title)", + .artist = "(unknown artist)", + .album = "(unknown album)", + .genre = "(unknown genre)", +}; /* DACP helpers */ static void -dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { uint32_t id; int64_t songalbumid; + int pos_pl; - if ((status->status == PLAY_STOPPED) || !mfi) + if ((status->status == PLAY_STOPPED) || !queue_item) return; /* Send bogus id's if playing internet radio, because clients like @@ -183,44 +191,46 @@ dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct med * FIXME: Giving the client invalid ids on purpose is hardly ideal, but the * clients don't seem to use these ids for anything other than rating. */ - if (mfi->data_kind == DATA_KIND_HTTP) + if (queue_item->data_kind == DATA_KIND_HTTP) { - id = djb_hash(mfi->album, strlen(mfi->album)); + id = djb_hash(queue_item->album, strlen(queue_item->album)); songalbumid = (int64_t)id; } else { id = status->id; - songalbumid = mfi->songalbumid; + songalbumid = queue_item->songalbumid; } + pos_pl = db_queue_get_pos(status->item_id, 0); + dmap_add_container(evbuf, "canp", 16); dmap_add_raw_uint32(evbuf, 1); /* Database */ dmap_add_raw_uint32(evbuf, status->plid); - dmap_add_raw_uint32(evbuf, status->pos_pl); + dmap_add_raw_uint32(evbuf, pos_pl); dmap_add_raw_uint32(evbuf, id); - dmap_add_string(evbuf, "cann", mfi->title); - dmap_add_string(evbuf, "cana", mfi->artist); - dmap_add_string(evbuf, "canl", mfi->album); - dmap_add_string(evbuf, "cang", mfi->genre); + dmap_add_string(evbuf, "cann", queue_item->title); + dmap_add_string(evbuf, "cana", queue_item->artist); + dmap_add_string(evbuf, "canl", queue_item->album); + dmap_add_string(evbuf, "cang", queue_item->genre); dmap_add_long(evbuf, "asai", songalbumid); dmap_add_int(evbuf, "cmmk", 1); } static void -dacp_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { - if ((status->status == PLAY_STOPPED) || !mfi) + if ((status->status == PLAY_STOPPED) || !queue_item) return; - if (mfi->song_length) - dmap_add_int(evbuf, "cant", mfi->song_length - status->pos_ms); /* Remaining time in ms */ + if (queue_item->song_length) + dmap_add_int(evbuf, "cant", queue_item->song_length - status->pos_ms); /* Remaining time in ms */ else dmap_add_int(evbuf, "cant", 0); /* Unknown remaining time */ - dmap_add_int(evbuf, "cast", mfi->song_length); /* Song length in ms */ + dmap_add_int(evbuf, "cast", queue_item->song_length); /* Song length in ms */ } @@ -229,7 +239,7 @@ static int make_playstatusupdate(struct evbuffer *evbuf) { struct player_status status; - struct media_file_info *mfi; + struct db_queue_item *queue_item = NULL; struct evbuffer *psu; int ret; @@ -245,16 +255,14 @@ make_playstatusupdate(struct evbuffer *evbuf) if (status.status != PLAY_STOPPED) { - mfi = db_file_fetch_byid(status.id); - if (!mfi) + queue_item = db_queue_fetch_byitemid(status.item_id); + if (!queue_item) { - DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", status.id); + DPRINTF(E_LOG, L_DACP, "Could not fetch item id %d (file id %d)\n", status.item_id, status.id); - mfi = &dummy_mfi; + queue_item = &dummy_queue_item; } } - else - mfi = NULL; dmap_add_int(psu, "mstt", 200); /* 12 */ @@ -271,19 +279,19 @@ make_playstatusupdate(struct evbuffer *evbuf) dmap_add_char(psu, "cafe", 0); /* 9 */ /* dacp.fullscreenenabled */ dmap_add_char(psu, "cave", 0); /* 9 */ /* dacp.visualizerenabled */ - if (mfi) + if (queue_item) { - dacp_nowplaying(psu, &status, mfi); + dacp_nowplaying(psu, &status, queue_item); dmap_add_int(psu, "casa", 1); /* 12 */ /* unknown */ - dmap_add_int(psu, "astm", mfi->song_length); + dmap_add_int(psu, "astm", queue_item->song_length); dmap_add_char(psu, "casc", 1); /* Maybe an indication of extra data? */ dmap_add_char(psu, "caks", 6); /* Unknown */ - dacp_playingtime(psu, &status, mfi); + dacp_playingtime(psu, &status, queue_item); - if (mfi != &dummy_mfi) - free_mfi(mfi, 0); + if (queue_item != &dummy_queue_item) + free_queue_item(queue_item, 0); } dmap_add_char(psu, "casu", 1); /* 9 */ /* unknown */ @@ -448,103 +456,103 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg) /* Properties getters */ static void -dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_int(evbuf, "cmvo", status->volume); } static void -dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_char(evbuf, "cavc", 1); } static void -dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_char(evbuf, "caps", status->status); } static void -dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_char(evbuf, "cash", status->shuffle); } static void -dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_int(evbuf, "caas", 2); } static void -dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_char(evbuf, "carp", status->repeat); } static void -dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { dmap_add_int(evbuf, "caar", 6); } static void -dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { - dacp_nowplaying(evbuf, status, mfi); + dacp_nowplaying(evbuf, status, queue_item); } static void -dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { - dacp_playingtime(evbuf, status, mfi); + dacp_playingtime(evbuf, status, queue_item); } static void -dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } static void -dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { // TODO } @@ -614,7 +622,7 @@ seek_timer_cb(int fd, short what, void *arg) return; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) DPRINTF(E_LOG, L_DACP, "Player returned an error for start after seek\n"); } @@ -845,20 +853,19 @@ find_first_song_id(const char *query) static int -dacp_queueitem_make(struct queue_item **head, const char *query, const char *queuefilter, const char *sort, int quirk) +dacp_queueitem_add(const char *query, const char *queuefilter, const char *sort, int quirk, int mode) { struct media_file_info *mfi; struct query_params qp; - struct queue_item *items; int64_t albumid; int64_t artistid; int plid; int id; - int idx; int ret; int len; char *s; char buf[1024]; + struct player_status status; if (query) { @@ -965,36 +972,34 @@ dacp_queueitem_make(struct queue_item **head, const char *query, const char *que qp.sort = S_ARTIST; } - items = queueitem_make_byquery(&qp); + player_get_status(&status); + + if (mode == 3) + ret = db_queue_add_by_queryafteritemid(&qp, status.item_id); + else + ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id); if (qp.filter) free(qp.filter); - if (items) - *head = items; - else + if (ret < 0) return -1; - // Get the position (0-based) of the first item - idx = queueitem_pos(items, id); - - return idx; + return id; } static void dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct player_status status; - struct queue_item *items; const char *sort; const char *cuequery; const char *param; - uint32_t id; uint32_t item_id; uint32_t pos; int clear; + struct db_queue_item *queue_item = NULL; struct player_history *history; - int hist; int ret; /* /cue?command=play&query=...&sort=...&index=N */ @@ -1009,18 +1014,16 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { player_playback_stop(); - player_queue_clear(); + db_queue_clear(); } } - player_get_status(&status); - cuequery = evhttp_find_header(query, "query"); if (cuequery) { sort = evhttp_find_header(query, "sort"); - ret = dacp_queueitem_make(&items, cuequery, NULL, sort, 0); + ret = dacp_queueitem_add(cuequery, NULL, sort, 0, 0); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); @@ -1028,11 +1031,11 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u dmap_send_error(req, "cacr", "Could not build song queue"); return; } - - player_queue_add(items, NULL); } else { + player_get_status(&status); + if (status.status != PLAY_STOPPED) player_playback_stop(); } @@ -1041,7 +1044,6 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (param) dacp_propset_shufflestate(param, NULL); - id = 0; item_id = 0; pos = 0; param = evhttp_find_header(query, "index"); @@ -1053,7 +1055,6 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u } /* If selection was from Up Next queue or history queue (command will be playnow), then index is relative */ - hist = 0; if ((param = evhttp_find_header(query, "command")) && (strcmp(param, "playnow") == 0)) { /* If mode parameter is -1 or 1, the index is relative to the history queue, otherwise to the Up Next queue */ @@ -1061,12 +1062,20 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (param && ((strcmp(param, "-1") == 0) || (strcmp(param, "1") == 0))) { /* Play from history queue */ - hist = 1; history = player_history_get(); if (history->count > pos) { pos = (history->start_index + history->count - pos - 1) % MAX_HISTORY_COUNT; item_id = history->item_id[pos]; + + queue_item = db_queue_fetch_byitemid(item_id); + if (!queue_item) + { + DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n"); + + dmap_send_error(req, "cacr", "Playback failed to start"); + return; + } } else { @@ -1079,19 +1088,22 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u else { /* Play from Up Next queue */ - pos += status.pos_pl; - if (status.status == PLAY_STOPPED && pos > 0) pos--; + + queue_item = db_queue_fetch_byposrelativetoitem(pos, status.item_id, status.shuffle); + if (!queue_item) + { + DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d, now playing=%d\n", pos, status.item_id); + + dmap_send_error(req, "cacr", "Playback failed to start"); + return; } } + } - /* If playing from history queue, the pos holds the id of the item to play */ - if (hist) - ret = player_playback_start_byitemid(item_id, &id); - else - ret = player_playback_start_bypos(pos, &id); - + ret = player_playback_start_byitem(queue_item); + free_queue_item(queue_item, 0); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); @@ -1100,9 +1112,11 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u return; } + player_get_status(&status); + dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "miid", id); /* 12 */ + dmap_add_int(evbuf, "miid", status.id);/* 12 */ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); } @@ -1114,7 +1128,7 @@ dacp_reply_cue_clear(struct evhttp_request *req, struct evbuffer *evbuf, char ** player_playback_stop(); - player_queue_clear(); + db_queue_clear(); dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ dmap_add_int(evbuf, "mstt", 200); /* 12 */ @@ -1159,13 +1173,12 @@ static void dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct player_status status; - struct queue_item *items; struct daap_session *s; const char *param; const char *shuffle; uint32_t plid; uint32_t id; - int pos; + struct db_queue_item *queue_item = NULL; int ret; /* /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&container-item-spec='dmap.containeritemid:0x9' @@ -1241,40 +1254,41 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u DPRINTF(E_DBG, L_DACP, "Playspec request for playlist %d, start song id %d%s\n", plid, id, (shuffle) ? ", shuffle" : ""); - items = NULL; - if (plid > 0) - items = queueitem_make_byplid(plid); - else if (id > 0) - items = queueitem_make_byid(id); + player_get_status(&status); - if (!items) + if (status.status != PLAY_STOPPED) + player_playback_stop(); + + db_queue_clear(); + + if (plid > 0) + ret = db_queue_add_by_playlistid(plid, status.shuffle, status.item_id); + else if (id > 0) + ret = db_queue_add_by_fileid(id, status.shuffle, status.item_id); + + if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid); goto out_fail; } - pos = queueitem_pos(items, id); - if (pos < 0) - { - DPRINTF(E_DBG, L_DACP, "No item with %d found in queue\n", id); - pos = 0; - } - DPRINTF(E_DBG, L_DACP, "Playspec start song index is %d\n", pos); - - player_get_status(&status); - - if (status.status != PLAY_STOPPED) - player_playback_stop(); - - player_queue_clear(); - player_queue_add(items, NULL); player_queue_plid(plid); if (shuffle) dacp_propset_shufflestate(shuffle, NULL); - ret = player_playback_start_bypos(pos, NULL); + if (id > 0) + queue_item = db_queue_fetch_byfileid(id); + + if (queue_item) + { + ret = player_playback_start_byitem(queue_item); + free_queue_item(queue_item, 0); + } + else + ret = player_playback_start(); + if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); @@ -1324,7 +1338,7 @@ dacp_reply_playpause(struct evhttp_request *req, struct evbuffer *evbuf, char ** } else { - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Player returned an error for start after pause\n"); @@ -1357,7 +1371,7 @@ dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **u return; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Player returned an error for start after nextitem\n"); @@ -1389,7 +1403,7 @@ dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **u return; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Player returned an error for start after previtem\n"); @@ -1500,6 +1514,44 @@ playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int return 0; } +static int +playqueuecontents_add_queue_item(struct evbuffer *songlist, struct db_queue_item *queue_item, int pos_in_queue, uint32_t plid) +{ + struct evbuffer *song; + int ret; + + song = evbuffer_new(); + if (!song) + { + DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); + return -1; + } + + dmap_add_container(song, "ceQs", 16); + dmap_add_raw_uint32(song, 1); /* Database */ + dmap_add_raw_uint32(song, plid); + dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ + dmap_add_raw_uint32(song, queue_item->file_id); + dmap_add_string(song, "ceQn", queue_item->title); + dmap_add_string(song, "ceQr", queue_item->artist); + dmap_add_string(song, "ceQa", queue_item->album); + dmap_add_string(song, "ceQg", queue_item->genre); + dmap_add_long(song, "asai", queue_item->songalbumid); + dmap_add_int(song, "cmmk", queue_item->media_kind); + dmap_add_int(song, "casa", 1); /* Unknown */ + dmap_add_int(song, "astm", queue_item->song_length); + dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ + dmap_add_char(song, "caks", 6); /* Unknown */ + dmap_add_int(song, "ceQI", pos_in_queue); + + dmap_add_container(songlist, "mlit", evbuffer_get_length(song)); + + ret = evbuffer_add_buffer(songlist, song); + evbuffer_free(song); + + return ret; +} + static void dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) @@ -1509,17 +1561,15 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, struct evbuffer *playlists; struct player_status status; struct player_history *history; - struct queue *queue; - struct queue_item *item; const char *param; size_t songlist_length; size_t playlist_length; int span; int count; - int i; - int n; int ret; int start_index; + struct query_params query_params; + struct db_queue_item queue_item; /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */ @@ -1538,7 +1588,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, DPRINTF(E_LOG, L_DACP, "Invalid span value in playqueue-contents request\n"); } - n = 0; // count of songs in songlist + count = 0; // count of songs in songlist songlist = evbuffer_new(); if (!songlist) { @@ -1548,8 +1598,6 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, return; } - player_get_status(&status); - /* * If the span parameter is negativ make song list for Previously Played, * otherwise make song list for Up Next and begin with first song after playlist position. @@ -1565,9 +1613,9 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, { start_index = (history->start_index + history->count - abs(span)) % MAX_HISTORY_COUNT; } - for (n = 0; n < history->count && n < abs(span); n++) + for (count = 0; count < history->count && count < abs(span); count++) { - ret = playqueuecontents_add_source(songlist, history->id[(start_index + n) % MAX_HISTORY_COUNT], (n + 1), status.plid); + ret = playqueuecontents_add_source(songlist, history->id[(start_index + count) % MAX_HISTORY_COUNT], (count + 1), status.plid); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); @@ -1579,15 +1627,21 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, } else { - queue = player_queue_get_bypos(abs(span)); - if (queue) + player_get_status(&status); + + memset(&query_params, 0, sizeof(struct query_params)); + if (status.shuffle) + query_params.sort = S_SHUFFLE_POS; + ret = db_queue_enum_start(&query_params); + + count = 0; //FIXME [queue] Check count value + while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0) { - i = 0; - count = queue_count(queue); - for (n = 0; (n < count) && (n < abs(span)); n++) + if (status.item_id == 0 || status.item_id == queue_item.id) + count = 1; + else if (count > 0) { - item = queue_get_byindex(queue, n, 0); - ret = playqueuecontents_add_source(songlist, queueitem_id(item), (n + i + 1), status.plid); + ret = playqueuecontents_add_queue_item(songlist, &queue_item, count, status.plid); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); @@ -1595,9 +1649,13 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, dmap_send_error(req, "ceQR", "Out of memory"); return; } + + count++; } - queue_free(queue); } + + db_queue_enum_end(&query_params); + sqlite3_free(query_params.filter); } /* Playlists are hist, curr and main. */ @@ -1628,7 +1686,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, dmap_add_container(playlists, "mlit", 69); dmap_add_string(playlists, "ceQk", "main"); /* 12 */ dmap_add_int(playlists, "ceQi", 1); /* 12 */ - dmap_add_int(playlists, "ceQm", n); /* 12 */ + dmap_add_int(playlists, "ceQm", count); /* 12 */ dmap_add_string(playlists, "ceQl", "Up Next"); /* 15 = 8 + 7 */ dmap_add_string(playlists, "ceQh", "from Music"); /* 18 = 8 + 10 */ @@ -1640,10 +1698,10 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* Final construction of reply */ playlist_length = evbuffer_get_length(playlists); dmap_add_container(evbuf, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */ - dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ - dmap_add_int(evbuf, "mtco", abs(span)); /* 12 */ - dmap_add_int(evbuf, "mrco", n); /* 12 */ - dmap_add_char(evbuf, "ceQu", 0); /* 9 */ + dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ + dmap_add_int(evbuf, "mtco", abs(span)); /* 12 */ + dmap_add_int(evbuf, "mrco", count); /* 12 */ + dmap_add_char(evbuf, "ceQu", 0); /* 9 */ dmap_add_container(evbuf, "mlcl", 8 + playlist_length + songlist_length); /* 8 */ dmap_add_container(evbuf, "ceQS", playlist_length); /* 8 */ ret = evbuffer_add_buffer(evbuf, playlists); @@ -1691,7 +1749,7 @@ dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbu if (strcmp(param,"0x68697374") == 0) player_queue_clear_history(); else - player_queue_clear(); + db_queue_clear(); dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ dmap_add_int(evbuf, "mstt", 200); /* 12 */ @@ -1712,7 +1770,6 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, //?command=add&query='dmap.itemid:2'&query-modifier=containers&sort=name&mode=2&session-id=100 // -> mode 2: stop playblack, clear playqueue, add shuffled songs from playlist=itemid to playqueue - struct queue_item *items; const char *editquery; const char *queuefilter; const char *querymodifier; @@ -1724,6 +1781,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, int plid; int ret; int quirkyquery; + struct db_queue_item *queue_item; mode = 1; @@ -1743,67 +1801,54 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, if ((mode == 1) || (mode == 2)) { player_playback_stop(); - player_queue_clear(); + db_queue_clear(); } editquery = evhttp_find_header(query, "query"); - if (editquery) + if (!editquery) { - sort = evhttp_find_header(query, "sort"); + DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n"); - // if sort param is missing and an album or artist is added to the queue, set sort to "album" - if (!sort && (strstr(editquery, "daap.songalbumid:") || strstr(editquery, "daap.songartistid:"))) - { - sort = "album"; - } + dmap_send_error(req, "cacr", "Invalid request"); + return; + } - // only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next) - queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(query, "queuefilter"); + sort = evhttp_find_header(query, "sort"); - querymodifier = evhttp_find_header(query, "query-modifier"); - if (!querymodifier || (strcmp(querymodifier, "containers") != 0)) - { - quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)")); - ret = dacp_queueitem_make(&items, editquery, queuefilter, sort, quirkyquery); - } - else - { - // Modify the query: Take the id from the editquery and use it as a queuefilter playlist id - ret = safe_atoi32(strchr(editquery, ':') + 1, &plid); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery); + // if sort param is missing and an album or artist is added to the queue, set sort to "album" + if (!sort && (strstr(editquery, "daap.songalbumid:") || strstr(editquery, "daap.songartistid:"))) + { + sort = "album"; + } - dmap_send_error(req, "cacr", "Invalid request"); - return; - } - - snprintf(modifiedquery, sizeof(modifiedquery), "playlist:%d", plid); - ret = dacp_queueitem_make(&items, NULL, modifiedquery, sort, 0); - } + // only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next) + queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(query, "queuefilter"); + querymodifier = evhttp_find_header(query, "query-modifier"); + if (!querymodifier || (strcmp(querymodifier, "containers") != 0)) + { + quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)")); + ret = dacp_queueitem_add(editquery, queuefilter, sort, quirkyquery, mode); + } + else + { + // Modify the query: Take the id from the editquery and use it as a queuefilter playlist id + ret = safe_atoi32(strchr(editquery, ':') + 1, &plid); if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); + { + DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery); dmap_send_error(req, "cacr", "Invalid request"); return; } - idx = ret; - - if (mode == 3) - { - player_queue_add_next(items); - } - else - { - player_queue_add(items, NULL); - } + snprintf(modifiedquery, sizeof(modifiedquery), "playlist:%d", plid); + ret = dacp_queueitem_add(NULL, modifiedquery, sort, 0, mode); } - else + + if (ret < 0) { - DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n"); + DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); dmap_send_error(req, "cacr", "Invalid request"); return; @@ -1812,11 +1857,27 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, if (mode == 2) { player_shuffle_set(1); - idx = 0; + ret = 0; + } + + idx = 0; + queue_item = NULL; + if (ret > 0) + { + queue_item = db_queue_fetch_byfileid(ret); } DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx); - ret = player_playback_start_bypos(idx, NULL); + if (queue_item) + { + ret = player_playback_start_byitem(queue_item); + free_queue_item(queue_item, 0); + } + else + { + ret = player_playback_start(); + } + if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); @@ -1840,6 +1901,7 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf * The 'edit-param.move-pair' param contains the index of the song in the playqueue to be moved (index 3 in the example) * and the index of the song after which it should be inserted (index 0 in the exampe, the now playing song). */ + struct player_status status; int ret; const char *param; @@ -1867,7 +1929,8 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf return; } - player_queue_move_bypos(src, dst); + player_get_status(&status); + db_queue_move_byposrelativetoitem(src, dst, status.item_id, status.shuffle); } /* 204 No Content is the canonical reply */ @@ -1882,6 +1945,7 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb * Exampe request (removes song at position 1 in the playqueue): * ?command=remove&items=1&session-id=100 */ + struct player_status status; int ret; const char *param; @@ -1899,7 +1963,9 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb return; } - player_queue_remove_bypos(item_index); + player_get_status(&status); + + db_queue_delete_byposrelativetoitem(item_index, status.item_id, status.shuffle); } /* 204 No Content is the canonical reply */ @@ -2150,7 +2216,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char struct player_status status; struct daap_session *s; const struct dacp_prop_map *dpm; - struct media_file_info *mfi; + struct db_queue_item *queue_item = NULL; struct evbuffer *proplist; const char *param; char *ptr; @@ -2194,17 +2260,15 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char if (status.status != PLAY_STOPPED) { - mfi = db_file_fetch_byid(status.id); - if (!mfi) + queue_item = db_queue_fetch_byitemid(status.item_id); + if (!queue_item) { - DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", status.id); + DPRINTF(E_LOG, L_DACP, "Could not fetch queue_item for item-id %d\n", status.item_id); dmap_send_error(req, "cmgt", "Server error"); goto out_free_proplist; } } - else - mfi = NULL; prop = strtok_r(propstr, ",", &ptr); while (prop) @@ -2213,7 +2277,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char if (dpm) { if (dpm->propget) - dpm->propget(proplist, &status, mfi); + dpm->propget(proplist, &status, queue_item); else DPRINTF(E_WARN, L_DACP, "No getter method for DACP property %s\n", prop); } @@ -2225,8 +2289,8 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char free(propstr); - if (mfi) - free_mfi(mfi, 0); + if (queue_item) + free_queue_item(queue_item, 0); len = evbuffer_get_length(proplist); dmap_add_container(evbuf, "cmgt", 12 + len); diff --git a/src/mpd.c b/src/mpd.c index 874d8ff0..333fd2bb 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 Julien BLACHE + * Copyright (C) 2016 Christian Meffert * * 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 @@ -61,7 +61,6 @@ #include "artwork.h" #include "player.h" -#include "queue.h" #include "filescanner.h" #include "commands.h" @@ -396,18 +395,16 @@ mpd_parse_args(char *args, int *argc, char **argv) * Id: 1 * * @param evbuf the response event buffer - * @param mfi media information - * @param item_id queue-item id - * @param pos_pl position in the playqueue, if -1 the position is ignored + * @param queue_item queue item information * @return the number of bytes added if successful, or -1 if an error occurred. */ static int -mpd_add_mediainfo(struct evbuffer *evbuf, struct media_file_info *mfi, unsigned int item_id, int pos_pl) +mpd_add_db_queue_item(struct evbuffer *evbuf, struct db_queue_item *queue_item) { char modified[32]; int ret; - mpd_time(modified, sizeof(modified), mfi->time_modified); + mpd_time(modified, sizeof(modified), queue_item->time_modified); ret = evbuffer_add_printf(evbuf, "file: %s\n" @@ -422,65 +419,28 @@ mpd_add_mediainfo(struct evbuffer *evbuf, struct media_file_info *mfi, unsigned "Track: %d\n" "Date: %d\n" "Genre: %s\n" - "Disc: %d\n", - (mfi->virtual_path + 1), + "Disc: %d\n" + "Pos: %d\n" + "Id: %d\n", + (queue_item->virtual_path + 1), modified, - (mfi->song_length / 1000), - mfi->artist, - mfi->album_artist, - mfi->artist_sort, - mfi->album_artist_sort, - mfi->album, - mfi->title, - mfi->track, - mfi->year, - mfi->genre, - mfi->disc); - - if (ret >= 0 && pos_pl >= 0) - { - ret = evbuffer_add_printf(evbuf, - "Pos: %d\n", - pos_pl); - - if (ret >= 0) - { - ret = evbuffer_add_printf(evbuf, - "Id: %d\n", - item_id); - } - } + (queue_item->song_length / 1000), + queue_item->artist, + queue_item->album_artist, + queue_item->artist_sort, + queue_item->album_artist_sort, + queue_item->album, + queue_item->title, + queue_item->track, + queue_item->year, + queue_item->genre, + queue_item->disc, + queue_item->pos, + queue_item->id); return ret; } -static int -mpd_add_mediainfo_byid(struct evbuffer *evbuf, int id, unsigned int item_id, int pos_pl) -{ - struct media_file_info *mfi; - int ret; - - mfi = db_file_fetch_byid(id); - if (!mfi) - { - DPRINTF(E_LOG, L_MPD, "Error fetching file by id: %d\n", id); - return -1; - } - - ret = mpd_add_mediainfo(evbuf, mfi, item_id, pos_pl); - if (ret < 0) - { - DPRINTF(E_LOG, L_MPD, "Error adding media info for file with id: %d\n", id); - - free_mfi(mfi, 0); - - return -1; - } - - free_mfi(mfi, 0); - return 0; -} - /* * Adds the informations (path, id, tags, etc.) for the given song to the given buffer. * @@ -568,6 +528,7 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er { struct player_status status; + struct db_queue_item *queue_item; int ret; player_get_status(&status); @@ -578,7 +539,20 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er return 0; } - ret = mpd_add_mediainfo_byid(evbuf, status.id, status.item_id, status.pos_pl); + queue_item = db_queue_fetch_byitemid(status.item_id); + if (!queue_item) + { + ret = asprintf(errmsg, "Error adding queue item info for file with id: %d", status.item_id); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + + return ACK_ERROR_UNKNOWN; + } + + ret = mpd_add_db_queue_item(evbuf, queue_item); + + free_queue_item(queue_item, 0); + if (ret < 0) { ret = asprintf(errmsg, "Error adding media info for file with id: %d", status.id); @@ -717,7 +691,11 @@ static int mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { struct player_status status; + int queue_length; + int queue_version; char *state; + int pos_pl; + struct db_queue_item *next_item; player_get_status(&status); @@ -736,6 +714,9 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) break; } + queue_version = db_queue_get_version(); + queue_length = db_queue_get_count(); + evbuffer_add_printf(evbuf, "volume: %d\n" "repeat: %d\n" @@ -751,12 +732,14 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) status.shuffle, (status.repeat == REPEAT_SONG ? 1 : 0), 0 /* consume: not supported by forked-daapd, always return 'off' */, - status.plversion, - status.playlistlength, + queue_version, + queue_length, state); if (status.status != PLAY_STOPPED) { + pos_pl = db_queue_get_pos(status.item_id, 0); + evbuffer_add_printf(evbuf, "song: %d\n" "songid: %d\n" @@ -764,7 +747,7 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) "elapsed: %#.3f\n" "bitrate: 128\n" "audio: 44100:16:2\n", - status.pos_pl, + pos_pl, status.item_id, (status.pos_ms / 1000), (status.len_ms / 1000), (status.pos_ms / 1000.0)); @@ -777,11 +760,17 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (status.status != PLAY_STOPPED) { + next_item = db_queue_fetch_next(status.item_id, status.shuffle); + if (next_item) + { evbuffer_add_printf(evbuf, "nextsong: %d\n" "nextsongid: %d\n", - status.next_pos_pl, - status.next_item_id); + next_item->id, + next_item->pos); + + free_queue_item(next_item, 0); + } } return 0; @@ -1074,7 +1063,7 @@ mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { ret = asprintf(errmsg, "Player returned an error for start after nextitem"); @@ -1122,7 +1111,7 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (pause == 1) ret = player_playback_pause(); else - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { @@ -1145,10 +1134,9 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { int songpos; struct player_status status; + struct db_queue_item *queue_item; int ret; - player_get_status(&status); - songpos = 0; if (argc > 1) { @@ -1162,6 +1150,8 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } } + player_get_status(&status); + if (status.status == PLAY_PLAYING && songpos < 0) { DPRINTF(E_DBG, L_MPD, "Ignoring play command with parameter '%s', player is already playing.\n", argv[1]); @@ -1175,9 +1165,21 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } if (songpos > 0) - ret = player_playback_start_byindex(songpos, NULL); + { + queue_item = db_queue_fetch_bypos(songpos, 0); + if (!queue_item) + { + ret = asprintf(errmsg, "Failed to start playback"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + + ret = player_playback_start_byitem(queue_item); + free_queue_item(queue_item, 0); + } else - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { @@ -1200,6 +1202,7 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { uint32_t id; struct player_status status; + struct db_queue_item *queue_item; int ret; player_get_status(&status); @@ -1225,9 +1228,21 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } if (id > 0) - ret = player_playback_start_byitemid(id, NULL); + { + queue_item = db_queue_fetch_byitemid(id); + if (!queue_item) + { + ret = asprintf(errmsg, "Failed to start playback"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + + ret = player_playback_start_byitem(queue_item); + free_queue_item(queue_item, 0); + } else - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { @@ -1259,7 +1274,7 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms return ACK_ERROR_UNKNOWN; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { ret = asprintf(errmsg, "Player returned an error for start after previtem"); @@ -1279,7 +1294,6 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms static int mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct player_status status; uint32_t songpos; float seek_target_sec; int seek_target_msec; @@ -1303,14 +1317,6 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } //TODO Allow seeking in songs not currently playing - player_get_status(&status); - if (status.pos_pl != songpos) - { - ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported"); - if (ret < 0) - DPRINTF(E_LOG, L_MPD, "Out of memory\n"); - return ACK_ERROR_UNKNOWN; - } seek_target_sec = strtof(argv[2], NULL); seek_target_msec = seek_target_sec * 1000; @@ -1325,7 +1331,7 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { ret = asprintf(errmsg, "Player returned an error for start after seekcur"); @@ -1391,7 +1397,7 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { ret = asprintf(errmsg, "Player returned an error for start after seekcur"); @@ -1436,7 +1442,7 @@ mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg return ACK_ERROR_UNKNOWN; } - ret = player_playback_start(NULL); + ret = player_playback_start(); if (ret < 0) { ret = asprintf(errmsg, "Player returned an error for start after seekcur"); @@ -1470,11 +1476,12 @@ mpd_command_stop(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return 0; } -static struct queue_item * -mpd_queueitem_make(char *path, int recursive) +static int +mpd_queue_add(char *path, int recursive) { struct query_params qp; - struct queue_item *items; + struct player_status status; + int ret; memset(&qp, 0, sizeof(struct query_params)); @@ -1495,10 +1502,12 @@ mpd_queueitem_make(char *path, int recursive) DPRINTF(E_DBG, L_PLAYER, "Out of memory\n"); } - items = queueitem_make_byquery(&qp); + player_get_status(&status); + + ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id); sqlite3_free(qp.filter); - return items; + return ret; } /* @@ -1509,7 +1518,6 @@ mpd_queueitem_make(char *path, int recursive) static int mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct queue_item *items; int ret; if (argc < 2) @@ -1520,9 +1528,9 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_ARG; } - items = mpd_queueitem_make(argv[1], 1); + ret = mpd_queue_add(argv[1], 1); - if (!items) + if (ret < 0) { ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) @@ -1530,8 +1538,6 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - player_queue_add(items, NULL); - return 0; } @@ -1544,8 +1550,6 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) static int mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct queue_item *items; - uint32_t item_id; int ret; if (argc < 2) @@ -1562,9 +1566,9 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) DPRINTF(E_LOG, L_MPD, "Adding at a specified position not supported for 'addid', adding songs at end of queue.\n"); } - items = mpd_queueitem_make(argv[1], 0); + ret = mpd_queue_add(argv[1], 0); - if (!items) + if (ret < 0) { ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) @@ -1572,12 +1576,9 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - - player_queue_add(items, &item_id); - evbuffer_add_printf(evbuf, "Id: %d\n", - item_id); + ret); // mpd_queue_add returns the item_id of the last inserted queue item return 0; } @@ -1597,7 +1598,7 @@ mpd_command_clear(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) DPRINTF(E_DBG, L_MPD, "Failed to stop playback\n"); } - player_queue_clear(); + db_queue_clear(); return 0; } @@ -1616,10 +1617,10 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) int count; int ret; - // If argv[1] is ommited clear the whole queue except the current playing one + // If argv[1] is ommited clear the whole queue if (argc < 2) { - player_queue_clear(); + db_queue_clear(); return 0; } @@ -1635,7 +1636,7 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) count = end_pos - start_pos; - ret = player_queue_remove_byindex(start_pos, count); + ret = db_queue_delete_bypos(start_pos, count); if (ret < 0) { ret = asprintf(errmsg, "Failed to remove %d songs starting at position %d", count, start_pos); @@ -1674,7 +1675,7 @@ mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errms return ACK_ERROR_ARG; } - ret = player_queue_remove_byitemid(songid); + ret = db_queue_delete_byitemid(songid); if (ret < 0) { ret = asprintf(errmsg, "Failed to remove song with id '%s'", argv[1]); @@ -1726,7 +1727,7 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_ARG; } - ret = player_queue_move_byindex(start_pos, to_pos); + ret = db_queue_move_bypos(start_pos, to_pos); if (ret < 0) { ret = asprintf(errmsg, "Failed to move song at position %d to %d", start_pos, to_pos); @@ -1771,7 +1772,7 @@ mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_ARG; } - ret = player_queue_move_byitemid(songid, to_pos); + ret = db_queue_move_byitemid(songid, to_pos); if (ret < 0) { ret = asprintf(errmsg, "Failed to move song with id '%s' to index '%s'", argv[1], argv[2]); @@ -1793,12 +1794,9 @@ mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) static int mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct queue *queue; - struct queue_item *item; + struct query_params query_params; + struct db_queue_item queue_item; uint32_t songid; - int pos_pl; - int count; - int i; int ret; songid = 0; @@ -1815,39 +1813,38 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err } } - // Get the whole queue (start_pos = 0, end_pos = -1) - queue = player_queue_get_byindex(0, 0); + memset(&query_params, 0, sizeof(struct query_params)); - if (!queue) + if (songid > 0) + query_params.filter = sqlite3_mprintf("id = %d", songid); + + ret = db_queue_enum_start(&query_params); + if (ret < 0) { - // Queue is emtpy - return 0; + sqlite3_free(query_params.filter); + ret = asprintf(errmsg, "Failed to start queue enum for command playlistid: '%s'", argv[1]); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; } - pos_pl = 0; - count = queue_count(queue); - for (i = 0; i < count; i++) + while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0) { - item = queue_get_byindex(queue, i, 0); - if (songid == 0 || songid == queueitem_item_id(item)) - { - ret = mpd_add_mediainfo_byid(evbuf, queueitem_id(item), queueitem_item_id(item), pos_pl); + ret = mpd_add_db_queue_item(evbuf, &queue_item); if (ret < 0) { - ret = asprintf(errmsg, "Error adding media info for file with id: %d", queueitem_id(item)); - - queue_free(queue); - + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue_item.file_id); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + + db_queue_enum_end(&query_params); + sqlite3_free(query_params.filter); return ACK_ERROR_UNKNOWN; } } - pos_pl++; - } - - queue_free(queue); + db_queue_enum_end(&query_params); + sqlite3_free(query_params.filter); return 0; } @@ -1863,17 +1860,15 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err static int mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct queue *queue; - struct queue_item *item; + struct query_params query_params; + struct db_queue_item queue_item; int start_pos; int end_pos; - int count; - int pos_pl; - int i; int ret; start_pos = 0; end_pos = 0; + memset(&query_params, 0, sizeof(struct query_params)); if (argc > 1) { @@ -1885,46 +1880,41 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_ARG; } - } - - count = end_pos - start_pos; if (start_pos < 0) - { DPRINTF(E_DBG, L_MPD, "Command 'playlistinfo' called with pos < 0 (arg = '%s'), ignore arguments and return whole queue\n", argv[1]); - start_pos = 0; - count = 0; + else + query_params.filter = sqlite3_mprintf("pos >= %d AND pos < %d", start_pos, end_pos); } - queue = player_queue_get_byindex(start_pos, count); - - if (!queue) + ret = db_queue_enum_start(&query_params); + if (ret < 0) { - // Queue is emtpy - return 0; + sqlite3_free(query_params.filter); + ret = asprintf(errmsg, "Failed to start queue enum for command playlistinfo: '%s'", argv[1]); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; } - pos_pl = start_pos; - count = queue_count(queue); - for (i = 0; i < count; i++) + while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0) { - item = queue_get_byindex(queue, i, 0); - ret = mpd_add_mediainfo_byid(evbuf, queueitem_id(item), queueitem_item_id(item), pos_pl); + ret = mpd_add_db_queue_item(evbuf, &queue_item); if (ret < 0) { - ret = asprintf(errmsg, "Error adding media info for file with id: %d", queueitem_id(item)); - - queue_free(queue); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue_item.file_id); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + + db_queue_enum_end(&query_params); + sqlite3_free(query_params.filter); return ACK_ERROR_UNKNOWN; } - - pos_pl++; } - queue_free(queue); + db_queue_enum_end(&query_params); + sqlite3_free(query_params.filter); return 0; } @@ -1936,46 +1926,42 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e static int mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct queue *queue; - struct queue_item *item; - int pos_pl; - int count; - int i; + struct query_params query_params; + struct db_queue_item queue_item; int ret; /* * forked-daapd does not keep track of changes in the queue based on the playlist version, * therefor plchanges returns all songs in the queue as changed ignoring the given version. */ - queue = player_queue_get_byindex(0, 0); + memset(&query_params, 0, sizeof(struct query_params)); - if (!queue) + ret = db_queue_enum_start(&query_params); + if (ret < 0) { - // Queue is emtpy - return 0; + ret = asprintf(errmsg, "Failed to start queue enum for command plchanges"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; } - pos_pl = 0; - count = queue_count(queue); - for (i = 0; i < count; i++) + while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0) { - item = queue_get_byindex(queue, i, 0); - ret = mpd_add_mediainfo_byid(evbuf, queueitem_id(item), queueitem_item_id(item), pos_pl); + ret = mpd_add_db_queue_item(evbuf, &queue_item); if (ret < 0) { - ret = asprintf(errmsg, "Error adding media info for file with id: %d", queueitem_id(item)); - - queue_free(queue); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue_item.file_id); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + + db_queue_enum_end(&query_params); return ACK_ERROR_UNKNOWN; } - - pos_pl++; } - queue_free(queue); + db_queue_enum_end(&query_params); + return 0; } @@ -1986,36 +1972,36 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm static int mpd_command_plchangesposid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct queue *queue; - struct queue_item *item; - int count; - int i; + struct query_params query_params; + struct db_queue_item queue_item; + int ret; /* * forked-daapd does not keep track of changes in the queue based on the playlist version, * therefor plchangesposid returns all songs in the queue as changed ignoring the given version. */ - queue = player_queue_get_byindex(0, 0); + memset(&query_params, 0, sizeof(struct query_params)); - if (!queue) + ret = db_queue_enum_start(&query_params); + if (ret < 0) { - // Queue is emtpy - return 0; + ret = asprintf(errmsg, "Failed to start queue enum for command plchangesposid"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; } - count = queue_count(queue); - for (i = 0; i < count; i++) + while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0) { - item = queue_get_byindex(queue, i, 0); - evbuffer_add_printf(evbuf, "cpos: %d\n" "Id: %d\n", - i, - queueitem_item_id(item)); + queue_item.pos, + queue_item.id); } - queue_free(queue); + db_queue_enum_end(&query_params); + return 0; } @@ -2239,7 +2225,7 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { char path[PATH_MAX]; struct playlist_info *pli; - struct queue_item *items; + struct player_status status; int ret; if (argc < 2) @@ -2274,20 +2260,18 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) //TODO If a second parameter is given only add the specified range of songs to the playqueue - items = queueitem_make_byplid(pli->id); + player_get_status(&status); - if (!items) + ret = db_queue_add_by_playlistid(pli->id, status.shuffle, status.item_id); + free_pli(pli, 0); + if (ret < 0) { - free_pli(pli, 0); - ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; } - player_queue_add(items, NULL); - return 0; } @@ -2436,6 +2420,7 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { db_query_end(&qp); + sqlite3_free(qp.filter); ret = asprintf(errmsg, "Could not start query"); if (ret < 0) @@ -2447,6 +2432,7 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { db_query_end(&qp); + sqlite3_free(qp.filter); ret = asprintf(errmsg, "Could not fetch query count"); if (ret < 0) @@ -2461,6 +2447,7 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) (fci.length / 1000)); db_query_end(&qp); + sqlite3_free(qp.filter); return 0; } @@ -2492,6 +2479,7 @@ mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { db_query_end(&qp); + sqlite3_free(qp.filter); ret = asprintf(errmsg, "Could not start query"); if (ret < 0) @@ -2509,6 +2497,7 @@ mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } db_query_end(&qp); + sqlite3_free(qp.filter); return 0; } @@ -2517,7 +2506,7 @@ static int mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { struct query_params qp; - struct queue_item *items; + struct player_status status; int ret; if (argc < 3 || ((argc - 1) % 2) != 0) @@ -2536,9 +2525,11 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg mpd_get_query_params_find(argc - 1, argv + 1, &qp); - items = queueitem_make_byquery(&qp); + player_get_status(&status); - if (!items) + ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id); + sqlite3_free(qp.filter); + if (ret < 0) { ret = asprintf(errmsg, "Failed to add songs to playlist"); if (ret < 0) @@ -2546,8 +2537,6 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg return ACK_ERROR_UNKNOWN; } - player_queue_add(items, NULL); - return 0; } @@ -2639,6 +2628,7 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { db_query_end(&qp); + sqlite3_free(qp.filter); ret = asprintf(errmsg, "Could not start query"); if (ret < 0) @@ -2682,6 +2672,7 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } db_query_end(&qp); + sqlite3_free(qp.filter); return 0; } @@ -3121,6 +3112,7 @@ mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { db_query_end(&qp); + sqlite3_free(qp.filter); ret = asprintf(errmsg, "Could not start query"); if (ret < 0) @@ -3138,6 +3130,7 @@ mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } db_query_end(&qp); + sqlite3_free(qp.filter); return 0; } @@ -3146,7 +3139,7 @@ static int mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { struct query_params qp; - struct queue_item *items; + struct player_status status; int ret; if (argc < 3 || ((argc - 1) % 2) != 0) @@ -3165,9 +3158,11 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm mpd_get_query_params_search(argc - 1, argv + 1, &qp); - items = queueitem_make_byquery(&qp); + player_get_status(&status); - if (!items) + ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id); + sqlite3_free(qp.filter); + if (ret < 0) { ret = asprintf(errmsg, "Failed to add songs to playlist"); if (ret < 0) @@ -3175,8 +3170,6 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm return ACK_ERROR_UNKNOWN; } - player_queue_add(items, NULL); - return 0; } diff --git a/src/outputs/raop.c b/src/outputs/raop.c index d7c7496b..c0249b41 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -821,12 +821,9 @@ raop_metadata_prune(uint64_t rtptime) static void * raop_metadata_prepare(int id) { - struct query_params qp; - struct db_media_file_info dbmfi; - char filter[32]; + struct db_queue_item *queue_item; struct raop_metadata *rmd; struct evbuffer *tmp; - uint64_t duration; int ret; rmd = (struct raop_metadata *)malloc(sizeof(struct raop_metadata)); @@ -839,6 +836,14 @@ raop_metadata_prepare(int id) memset(rmd, 0, sizeof(struct raop_metadata)); + queue_item = db_queue_fetch_byitemid(id); + if (!queue_item) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for queue item\n"); + + goto out_rmd; + } + /* Get artwork */ rmd->artwork = evbuffer_new(); if (!rmd->artwork) @@ -848,7 +853,7 @@ raop_metadata_prepare(int id) goto skip_artwork; } - ret = artwork_get_item(rmd->artwork, id, 600, 600); + ret = artwork_get_item(rmd->artwork, queue_item->file_id, 600, 600); if (ret < 0) { DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file id %d; no artwork will be sent\n", id); @@ -861,44 +866,13 @@ raop_metadata_prepare(int id) skip_artwork: - /* Get dbmfi */ - memset(&qp, 0, sizeof(struct query_params)); - qp.type = Q_ITEMS; - qp.idx_type = I_NONE; - qp.sort = S_NONE; - qp.filter = filter; - - ret = snprintf(filter, sizeof(filter), "id = %d", id); - if ((ret < 0) || (ret >= sizeof(filter))) - { - DPRINTF(E_LOG, L_RAOP, "Could not build filter for file id %d; metadata will not be sent\n", id); - - goto out_rmd; - } - - ret = db_query_start(&qp); - if (ret < 0) - { - DPRINTF(E_LOG, L_RAOP, "Couldn't start query; no metadata will be sent\n"); - - goto out_rmd; - } - - ret = db_query_fetch_file(&qp, &dbmfi); - if (ret < 0) - { - DPRINTF(E_LOG, L_RAOP, "Couldn't fetch file id %d; metadata will not be sent\n", id); - - goto out_query; - } - /* Turn it into DAAP metadata */ tmp = evbuffer_new(); if (!tmp) { DPRINTF(E_LOG, L_RAOP, "Out of memory for temporary metadata evbuffer; metadata will not be sent\n"); - goto out_query; + goto out_qi; } rmd->metadata = evbuffer_new(); @@ -907,10 +881,10 @@ raop_metadata_prepare(int id) DPRINTF(E_LOG, L_RAOP, "Out of memory for metadata evbuffer; metadata will not be sent\n"); evbuffer_free(tmp); - goto out_query; + goto out_qi; } - ret = dmap_encode_file_metadata(rmd->metadata, tmp, &dbmfi, NULL, 0, 0, 1); + ret = dmap_encode_queue_metadata(rmd->metadata, tmp, queue_item); evbuffer_free(tmp); if (ret < 0) { @@ -919,27 +893,18 @@ raop_metadata_prepare(int id) goto out_metadata; } - /* Progress */ - ret = safe_atou64(dbmfi.song_length, &duration); - if (ret < 0) - { - DPRINTF(E_LOG, L_RAOP, "Failed to convert song_length to integer; no metadata will be sent\n"); - - goto out_metadata; - } - - db_query_end(&qp); - - /* raop_metadata_send() will add rtptime to these */ + /* Progress - raop_metadata_send() will add rtptime to these */ rmd->start = 0; - rmd->end = (duration * 44100UL) / 1000UL; + rmd->end = (queue_item->song_length * 44100UL) / 1000UL; + + free_queue_item(queue_item, 0); return rmd; out_metadata: evbuffer_free(rmd->metadata); - out_query: - db_query_end(&qp); + out_qi: + free_queue_item(queue_item, 0); out_rmd: free(rmd); diff --git a/src/player.c b/src/player.c index 1ec16a58..17ea5172 100644 --- a/src/player.c +++ b/src/player.c @@ -97,6 +97,7 @@ struct player_source enum data_kind data_kind; enum media_kind media_kind; + char *path; /* Start time of the media item as rtp-time The stream-start is the rtp-time the media item did or would have @@ -136,44 +137,6 @@ struct spk_enum void *arg; }; -struct playback_start_param -{ - uint32_t id; - int pos; - - uint32_t *id_ptr; -}; - -struct playerqueue_get_param -{ - int pos; - int count; - - struct queue *queue; -}; - -struct playerqueue_add_param -{ - struct queue_item *items; - int pos; - - uint32_t *item_id_ptr; -}; - -struct playerqueue_move_param -{ - uint32_t item_id; - int from_pos; - int to_pos; - int count; -}; - -struct playerqueue_remove_param -{ - int from_pos; - int count; -}; - struct icy_artwork { uint32_t id; @@ -211,11 +174,6 @@ union player_arg uint32_t id; int intval; struct icy_artwork icy; - struct playback_start_param playback_start_param; - struct playerqueue_get_param queue_get_param; - struct playerqueue_add_param queue_add_param; - struct playerqueue_move_param queue_move_param; - struct playerqueue_remove_param queue_remove_param; }; struct event_base *evbase_player; @@ -278,9 +236,6 @@ static struct evbuffer *audio_buf; static uint8_t rawbuf[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; -/* Play queue */ -static struct queue *queue; - /* Play history */ static struct player_history *history; @@ -475,9 +430,6 @@ pb_timer_stop(void) static void playback_abort(void); -static enum command_state -playerqueue_clear(void *arg, int *retval); - static void player_metadata_send(struct player_metadata *pmd); @@ -525,7 +477,7 @@ update_icy_cb(void *arg) { struct http_icy_metadata *metadata = arg; - db_file_update_icy(metadata->id, metadata->artist, metadata->title); + db_queue_update_icymetadata(metadata->id, metadata->artist, metadata->title); http_icy_metadata_free(metadata, 1); } @@ -550,7 +502,7 @@ metadata_trigger(int startup) memset(&pmd, 0, sizeof(struct player_metadata)); - pmd.id = cur_streaming->id; + pmd.id = cur_streaming->item_id; pmd.startup = startup; if (cur_streaming->stream_start && cur_streaming->output_start) @@ -584,7 +536,7 @@ metadata_check_icy(void) if (metadata->title[0] == '\0') goto no_update; - metadata->id = cur_streaming->id; + metadata->id = cur_streaming->item_id; /* Defer the database update to the worker thread */ worker_execute(update_icy_cb, metadata, sizeof(struct http_icy_metadata), 0); @@ -645,14 +597,14 @@ history_add(uint32_t id, uint32_t item_id) * Initializes the given player source for playback */ static int -stream_setup(struct player_source *ps, struct media_file_info *mfi) +stream_setup(struct player_source *ps) { char *url; int ret; - if (!ps || !mfi) + if (!ps) { - DPRINTF(E_LOG, L_PLAYER, "No player source and/or media info given to stream_setup\n"); + DPRINTF(E_LOG, L_PLAYER, "No player source given to stream_setup\n"); return -1; } @@ -666,39 +618,39 @@ stream_setup(struct player_source *ps, struct media_file_info *mfi) switch (ps->data_kind) { case DATA_KIND_FILE: - ps->xcode = transcode_setup(mfi->data_kind, mfi->path, mfi->song_length, XCODE_PCM16_NOHEADER, NULL); + ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL); ret = ps->xcode ? 0 : -1; break; case DATA_KIND_HTTP: - ret = http_stream_setup(&url, mfi->path); + ret = http_stream_setup(&url, ps->path); if (ret < 0) break; - free(mfi->path); - mfi->path = url; + free(ps->path); + ps->path = url; - ps->xcode = transcode_setup(mfi->data_kind, mfi->path, mfi->song_length, XCODE_PCM16_NOHEADER, NULL); + ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL); ret = ps->xcode ? 0 : -1; break; case DATA_KIND_SPOTIFY: #ifdef HAVE_SPOTIFY_H - ret = spotify_playback_setup(mfi->path); + ret = spotify_playback_setup(ps->path); #else - DPRINTF(E_LOG, L_PLAYER, "Player source has data kind 'spotify' (%d), but forked-daapd is compiled without spotify support - cannot setup source '%s' (%s)\n", - ps->data_kind, mfi->title, mfi->path); + DPRINTF(E_LOG, L_PLAYER, "Player source has data kind 'spotify' (%d), but forked-daapd is compiled without spotify support - cannot setup source '%s'\n", + ps->data_kind, ps->path); ret = -1; #endif break; case DATA_KIND_PIPE: - ret = pipe_setup(mfi->path); + ret = pipe_setup(ps->path); break; default: - DPRINTF(E_LOG, L_PLAYER, "Unknown data kind (%d) for player source - cannot setup source '%s' (%s)\n", - ps->data_kind, mfi->title, mfi->path); + DPRINTF(E_LOG, L_PLAYER, "Unknown data kind (%d) for player source - cannot setup source '%s'\n", + ps->data_kind, ps->path); ret = -1; } @@ -963,7 +915,7 @@ source_now_playing() * Creates a new player source for the given queue item */ static struct player_source * -source_new(struct queue_item *item) +source_new(struct db_queue_item *queue_item) { struct player_source *ps; @@ -974,16 +926,26 @@ source_new(struct queue_item *item) return NULL; } - ps->id = queueitem_id(item); - ps->item_id = queueitem_item_id(item); - ps->data_kind = queueitem_data_kind(item); - ps->media_kind = queueitem_media_kind(item); - ps->len_ms = queueitem_len(item); + ps->id = queue_item->file_id; + ps->item_id = queue_item->id; + ps->data_kind = queue_item->data_kind; + ps->media_kind = queue_item->media_kind; + ps->len_ms = queue_item->song_length; ps->play_next = NULL; + ps->path = strdup(queue_item->path); return ps; } +static void +source_free(struct player_source *ps) +{ + if (ps->path) + free(ps->path); + + free(ps); +} + /* * Stops playback for the current streaming source and frees all * player sources (starting from the playing source). Sets current streaming @@ -1006,7 +968,7 @@ source_stop() ps_playing = ps_playing->play_next; ps_temp->play_next = NULL; - free(ps_temp); + source_free(ps_temp); } cur_playing = NULL; @@ -1026,7 +988,6 @@ source_pause(uint64_t pos) struct player_source *ps_playing; struct player_source *ps_playnext; struct player_source *ps_temp; - struct media_file_info *mfi; uint64_t seek_frames; int seek_ms; int ret; @@ -1061,7 +1022,7 @@ source_pause(uint64_t pos) ps_playnext = ps_playnext->play_next; ps_temp->play_next = NULL; - free(ps_temp); + source_free(ps_temp); } ps_playing->play_next = NULL; @@ -1070,33 +1031,14 @@ source_pause(uint64_t pos) if (!cur_streaming->setup_done) { - mfi = db_file_fetch_byid(cur_streaming->id); - if (!mfi) - { - DPRINTF(E_LOG, L_PLAYER, "Couldn't fetch file id %d\n", cur_streaming->id); + DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path); - return -1; - } - - if (mfi->disabled) - { - DPRINTF(E_DBG, L_PLAYER, "File id %d is disabled, skipping\n", cur_streaming->id); - - free_mfi(mfi, 0); - return -1; - } - - DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path); - - ret = stream_setup(cur_streaming, mfi); + ret = stream_setup(cur_streaming); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (%s)\n", mfi->title, mfi->path); - free_mfi(mfi, 0); + DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path); return -1; } - - free_mfi(mfi, 0); } /* Seek back to the pause position */ @@ -1152,66 +1094,38 @@ source_play() } /* - * Initializes playback of the given queue item (but does not start playback) + * Opens the given player source for playback (but does not start playback) * - * A new source is created for the given queue item and is set as the current - * streaming source. If a streaming source already existed (and reached eof) - * the new source is appended as the play-next item to it. + * The given source is appended to the current streaming source (if one exists) and + * becomes the new current streaming source. * * Stream-start and output-start values are set to the given start position. */ static int -source_open(struct queue_item *qii, uint64_t start_pos, int seek) +source_open(struct player_source *ps, uint64_t start_pos, int seek_ms) { - struct player_source *ps; - struct media_file_info *mfi; - uint32_t id; int ret; + DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); + if (cur_streaming && cur_streaming->end == 0) { - DPRINTF(E_LOG, L_PLAYER, "Current streaming source not at eof %d\n", cur_streaming->id); + DPRINTF(E_LOG, L_PLAYER, "Current streaming source not at eof '%s' (id=%d, item-id=%d)\n", + cur_streaming->path, cur_streaming->id, cur_streaming->item_id); return -1; } - id = queueitem_id(qii); - mfi = db_file_fetch_byid(id); - if (!mfi) - { - DPRINTF(E_LOG, L_PLAYER, "Couldn't fetch file id %d\n", id); - - return -1; - } - - if (mfi->disabled) - { - DPRINTF(E_DBG, L_PLAYER, "File id %d is disabled, skipping\n", id); - - free_mfi(mfi, 0); - return -1; - } - - DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path); - - ps = source_new(qii); - if (!ps) - return -1; - - ret = stream_setup(ps, mfi); + ret = stream_setup(ps); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (%s)\n", mfi->title, mfi->path); - free(ps); - free_mfi(mfi, 0); + DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); return -1; } /* If a streaming source exists, append the new source as play-next and set it as the new streaming source */ if (cur_streaming) - { cur_streaming->play_next = ps; - } cur_streaming = ps; @@ -1219,11 +1133,13 @@ source_open(struct queue_item *qii, uint64_t start_pos, int seek) cur_streaming->output_start = cur_streaming->stream_start; cur_streaming->end = 0; - /* Seek to the saved seek position */ - if (seek && mfi->seek) - source_seek(mfi->seek); + // Seek to the given seek position + if (seek_ms) + { + DPRINTF(E_INFO, L_PLAYER, "Seek to %d ms for '%s' (id=%d, item-id=%d)\n", seek_ms, ps->path, ps->id, ps->item_id); + source_seek(seek_ms); + } - free_mfi(mfi, 0); return ret; } @@ -1316,7 +1232,7 @@ source_check(void) ps = cur_playing; cur_playing = cur_playing->play_next; - free(ps); + source_free(ps); } if (i > 0) @@ -1331,13 +1247,96 @@ source_check(void) return pos; } +/* + * Returns the next player source based on the current streaming source and repeat mode + * + * If repeat mode is repeat all, shuffle is active and the current streaming source is the + * last item in the queue, the queue is reshuffled prior to returning the first item of the + * queue. + */ +static struct player_source * +source_next() +{ + struct player_source *ps = NULL; + struct db_queue_item *queue_item; + + if (!cur_streaming) + { + DPRINTF(E_LOG, L_PLAYER, "source_next() called with no current streaming source available\n"); + return NULL; + } + + if (repeat == REPEAT_SONG) + { + queue_item = db_queue_fetch_byitemid(cur_streaming->item_id); + if (!queue_item) + { + DPRINTF(E_LOG, L_PLAYER, "Error fetching item from queue '%s' (id=%d, item-id=%d)\n", cur_streaming->path, cur_streaming->id, cur_streaming->item_id); + return NULL; + } + } + else + { + queue_item = db_queue_fetch_next(cur_streaming->item_id, shuffle); + if (!queue_item && repeat == REPEAT_ALL) + { + if (shuffle) + { + db_queue_reshuffle(0); + } + + queue_item = db_queue_fetch_bypos(0, shuffle); + if (!queue_item) + { + DPRINTF(E_LOG, L_PLAYER, "Error fetching item from queue '%s' (id=%d, item-id=%d)\n", cur_streaming->path, cur_streaming->id, cur_streaming->item_id); + return NULL; + } + } + } + + if (!queue_item) + { + DPRINTF(E_DBG, L_PLAYER, "Reached end of queue\n"); + return NULL; + } + + ps = source_new(queue_item); + free_queue_item(queue_item, 0); + return ps; +} + +/* + * Returns the previous player source based on the current streaming source + */ +static struct player_source * +source_prev() +{ + struct player_source *ps = NULL; + struct db_queue_item *queue_item; + + if (!cur_streaming) + { + DPRINTF(E_LOG, L_PLAYER, "source_prev() called with no current streaming source available\n"); + return NULL; + } + + queue_item = db_queue_fetch_prev(cur_streaming->item_id, shuffle); + if (!queue_item) + return NULL; + + ps = source_new(queue_item); + free_queue_item(queue_item, 0); + + return ps; +} + static int source_read(uint8_t *buf, int len, uint64_t rtptime) { int ret; int nbytes; char *silence_buf; - struct queue_item *item; + struct player_source *ps; if (!cur_streaming) return 0; @@ -1373,17 +1372,17 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) DPRINTF(E_DBG, L_PLAYER, "New file\n"); - item = queue_next(queue, cur_streaming->item_id, shuffle, repeat, 1); + ps = source_next(); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Error reading source %d\n", cur_streaming->id); - queue_remove_byitemid(queue, cur_streaming->item_id); + db_queue_delete_byitemid(cur_streaming->item_id); } - if (item) + if (ps) { - ret = source_open(item, cur_streaming->end + 1, 0); + ret = source_open(ps, cur_streaming->end + 1, 0); if (ret < 0) return -1; @@ -2033,8 +2032,6 @@ device_restart_cb(struct output_device *device, struct output_session *session, static void playback_abort(void) { - int ret; - outputs_playback_stop(); pb_timer_stop(); @@ -2044,7 +2041,7 @@ playback_abort(void) evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf)); if (!clear_queue_on_stop_disabled) - playerqueue_clear(NULL, &ret); + db_queue_clear(); status_update(PLAY_STOPPED); @@ -2059,7 +2056,6 @@ get_status(void *arg, int *retval) struct timespec ts; struct player_source *ps; struct player_status *status; - struct queue_item *item_next; uint64_t pos; int ret; @@ -2073,8 +2069,6 @@ get_status(void *arg, int *retval) status->volume = master_volume; status->plid = cur_plid; - status->plversion = cur_plversion; - status->playlistlength = queue_count(queue); switch (player_state) { @@ -2095,8 +2089,6 @@ get_status(void *arg, int *retval) status->pos_ms = (pos * 1000) / 44100; status->len_ms = cur_streaming->len_ms; - status->pos_pl = queue_index_byitemid(queue, cur_streaming->item_id, 0); - break; case PLAY_PLAYING: @@ -2136,21 +2128,6 @@ get_status(void *arg, int *retval) status->id = ps->id; status->item_id = ps->item_id; - status->pos_pl = queue_index_byitemid(queue, ps->item_id, 0); - - item_next = queue_next(queue, ps->item_id, shuffle, repeat, 0); - if (item_next) - { - status->next_id = queueitem_id(item_next); - status->next_item_id = queueitem_item_id(item_next); - status->next_pos_pl = queue_index_byitemid(queue, status->next_item_id, 0); - } - else - { - //TODO [queue/mpd] Check how mpd sets the next-id/-pos if the last song is playing - status->next_id = 0; - status->next_pos_pl = 0; - } break; } @@ -2300,30 +2277,17 @@ playback_start_bh(void *arg, int *retval) } static enum command_state -playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qii) +playback_start_item(void *arg, int *retval) { - uint32_t *dbmfi_id; + struct db_queue_item *queue_item = arg; + struct media_file_info *mfi; struct output_device *device; - struct player_source *ps_playing; - struct queue_item *item; + struct player_source *ps; + int seek_ms; int ret; - dbmfi_id = cmdarg->playback_start_param.id_ptr; - - ps_playing = source_now_playing(); - if (player_state == PLAY_PLAYING) { - /* - * If player is already playing a song, only return current playing song id - * and do not change player state (ignores given arguments for playing a - * specified song by pos or id). - */ - if (dbmfi_id && ps_playing) - { - *dbmfi_id = ps_playing->id; - } - status_update(player_state); *retval = 1; // Value greater 0 will prevent execution of the bottom half function @@ -2333,22 +2297,44 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi // Update global playback position pb_pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - 88200; - item = NULL; - if (qii) + if (player_state == PLAY_STOPPED && !queue_item) { - item = qii; - } - else if (!cur_streaming) - { - if (shuffle) - queue_shuffle(queue, 0); - item = queue_next(queue, 0, shuffle, repeat, 0); + *retval = -1; + return COMMAND_END; } - if (item) + if (!queue_item) { + // Resume playback of current source + ps = source_now_playing(); + DPRINTF(E_DBG, L_PLAYER, "Resume playback of '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); + } + else + { + // Start playback for given queue item + DPRINTF(E_DBG, L_PLAYER, "Start playback of '%s' (id=%d, item-id=%d)\n", queue_item->path, queue_item->file_id, queue_item->id); source_stop(); - ret = source_open(item, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 1); + + ps = source_new(queue_item); + if (!ps) + { + playback_abort(); + *retval = -1; + return COMMAND_END; + } + + seek_ms = 0; + if (queue_item->file_id > 0) + { + mfi = db_file_fetch_byid(queue_item->file_id); + if (mfi) + { + seek_ms = mfi->seek; + free_mfi(mfi, 0); + } + } + + ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, seek_ms); if (ret < 0) { playback_abort(); @@ -2366,9 +2352,6 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi } - if (dbmfi_id) - *dbmfi_id = cur_streaming->id; - metadata_trigger(1); /* Start sessions on selected devices */ @@ -2433,59 +2416,22 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi static enum command_state playback_start(void *arg, int *retval) { - return playback_start_item(arg, retval, NULL); -} + struct db_queue_item *queue_item = NULL; + enum command_state cmd_state; -static enum command_state -playback_start_byitemid(void *arg, int *retval) + if (player_state == PLAY_STOPPED) { - union player_arg *cmdarg = arg; - int item_id; - struct queue_item *qii; - - item_id = cmdarg->playback_start_param.id; - - qii = queue_get_byitemid(queue, item_id); - - return playback_start_item(cmdarg, retval, qii); -} - -static enum command_state -playback_start_byindex(void *arg, int *retval) + // Start playback of first item in queue + queue_item = db_queue_fetch_bypos(0, shuffle); + if (!queue_item) { - union player_arg *cmdarg = arg; - int pos; - struct queue_item *qii; - - pos = cmdarg->playback_start_param.pos; - - qii = queue_get_byindex(queue, pos, 0); - - return playback_start_item(cmdarg, retval, qii); + *retval = -1; + return COMMAND_END; } - -static enum command_state -playback_start_bypos(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - int offset; - struct player_source *ps_playing; - struct queue_item *qii; - - offset = cmdarg->playback_start_param.pos; - - ps_playing = source_now_playing(); - - if (ps_playing) - { - qii = queue_get_bypos(queue, ps_playing->item_id, offset, shuffle); - } - else - { - qii = queue_get_byindex(queue, offset, shuffle); } - return playback_start_item(cmdarg, retval, qii); + cmd_state = playback_start_item(queue_item, retval); + return cmd_state; } static enum command_state @@ -2493,7 +2439,7 @@ playback_prev_bh(void *arg, int *retval) { int ret; int pos_sec; - struct queue_item *item; + struct player_source *ps; /* * The upper half is playback_pause, therefor the current playing item is @@ -2521,8 +2467,8 @@ playback_prev_bh(void *arg, int *retval) DPRINTF(E_DBG, L_PLAYER, "Skipping song played %d sec\n", pos_sec); if (pos_sec < 3) { - item = queue_prev(queue, cur_streaming->item_id, shuffle, repeat); - if (!item) + ps = source_prev(); + if (!ps) { playback_abort(); *retval = -1; @@ -2531,7 +2477,7 @@ playback_prev_bh(void *arg, int *retval) source_stop(); - ret = source_open(item, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0); + ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0); if (ret < 0) { playback_abort(); @@ -2571,8 +2517,8 @@ playback_prev_bh(void *arg, int *retval) static enum command_state playback_next_bh(void *arg, int *retval) { + struct player_source *ps; int ret; - struct queue_item *item; /* * The upper half is playback_pause, therefor the current playing item is @@ -2589,8 +2535,8 @@ playback_next_bh(void *arg, int *retval) if (cur_streaming->output_start > cur_streaming->stream_start) history_add(cur_streaming->id, cur_streaming->item_id); - item = queue_next(queue, cur_streaming->item_id, shuffle, repeat, 0); - if (!item) + ps = source_next(); + if (!ps) { playback_abort(); *retval = -1; @@ -2599,7 +2545,7 @@ playback_next_bh(void *arg, int *retval) source_stop(); - ret = source_open(item, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0); + ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0); if (ret < 0) { playback_abort(); @@ -3064,7 +3010,7 @@ shuffle_set(void *arg, int *retval) if (!shuffle) { cur_id = cur_streaming ? cur_streaming->item_id : 0; - queue_shuffle(queue, cur_id); + db_queue_reshuffle(cur_id); } /* FALLTHROUGH*/ case 0: @@ -3083,276 +3029,6 @@ shuffle_set(void *arg, int *retval) return COMMAND_END; } -static enum command_state -playerqueue_get_bypos(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - int count; - struct queue *qi; - struct player_source *ps; - int item_id; - - count = cmdarg->queue_get_param.count; - - ps = source_now_playing(); - - item_id = 0; - if (ps) - { - item_id = ps->item_id; - } - - qi = queue_new_bypos(queue, item_id, count, shuffle); - - cmdarg->queue_get_param.queue = qi; - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_get_byindex(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - int pos; - int count; - struct queue *qi; - - pos = cmdarg->queue_get_param.pos; - count = cmdarg->queue_get_param.count; - - qi = queue_new_byindex(queue, pos, count, 0); - cmdarg->queue_get_param.queue = qi; - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_add(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - struct queue_item *items; - uint32_t cur_id; - uint32_t *item_id; - - items = cmdarg->queue_add_param.items; - item_id = cmdarg->queue_add_param.item_id_ptr; - - queue_add(queue, items); - - if (shuffle) - { - cur_id = cur_streaming ? cur_streaming->item_id : 0; - queue_shuffle(queue, cur_id); - } - - if (item_id) - *item_id = queueitem_item_id(items); - - cur_plid = 0; - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_add_next(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - struct queue_item *items; - uint32_t cur_id; - - items = cmdarg->queue_add_param.items; - - cur_id = cur_streaming ? cur_streaming->item_id : 0; - - queue_add_after(queue, items, cur_id); - - if (shuffle) - queue_shuffle(queue, cur_id); - - cur_plid = 0; - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_move_bypos(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - struct player_source *ps_playing; - uint32_t item_id; - - DPRINTF(E_DBG, L_PLAYER, "Moving song from position %d to be the next song after %d\n", - cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos); - - ps_playing = source_now_playing(); - - if (!ps_playing) - { - DPRINTF(E_DBG, L_PLAYER, "No playing item found for move by pos\n"); - item_id = 0; - } - else - item_id = ps_playing->item_id; - - queue_move_bypos(queue, item_id, cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos, shuffle); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_move_byindex(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - - DPRINTF(E_DBG, L_PLAYER, "Moving song from index %d to be the next song after %d\n", - cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos); - - queue_move_byindex(queue, cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos, 0); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_move_byitemid(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - - DPRINTF(E_DBG, L_PLAYER, "Moving song with item-id %d to be the next song after index %d\n", - cmdarg->queue_move_param.item_id, cmdarg->queue_move_param.to_pos); - - queue_move_byitemid(queue, cmdarg->queue_move_param.item_id, cmdarg->queue_move_param.to_pos, 0); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_remove_bypos(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - int pos; - struct player_source *ps_playing; - uint32_t item_id; - - pos = cmdarg->intval; - if (pos < 1) - { - DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid position %d\n", pos); - *retval = -1; - return COMMAND_END; - } - - ps_playing = source_now_playing(); - - if (!ps_playing) - { - DPRINTF(E_DBG, L_PLAYER, "No playing item for remove by pos\n"); - item_id = 0; - } - else - item_id = ps_playing->item_id; - - DPRINTF(E_DBG, L_PLAYER, "Removing item from position %d\n", pos); - queue_remove_bypos(queue, item_id, pos, shuffle); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_remove_byindex(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - int pos; - int count; - int i; - - pos = cmdarg->queue_remove_param.from_pos; - count = cmdarg->queue_remove_param.count; - - DPRINTF(E_DBG, L_PLAYER, "Removing %d items starting from position %d\n", count, pos); - - for (i = 0; i < count; i++) - queue_remove_byindex(queue, pos, 0); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -static enum command_state -playerqueue_remove_byitemid(void *arg, int *retval) -{ - union player_arg *cmdarg = arg; - uint32_t id; - - id = cmdarg->id; - if (id < 1) - { - DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid id %d\n", id); - *retval = -1; - return COMMAND_END; - } - - DPRINTF(E_DBG, L_PLAYER, "Removing item with id %d\n", id); - queue_remove_byitemid(queue, id); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - -/* - * Removes all media items from the queue - */ -static enum command_state -playerqueue_clear(void *arg, int *retval) -{ - queue_clear(queue); - - cur_plid = 0; - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - *retval = 0; - return COMMAND_END; -} - /* * Removes all items from the history */ @@ -3361,7 +3037,7 @@ playerqueue_clear_history(void *arg, int *retval) { memset(history, 0, sizeof(struct player_history)); - cur_plversion++; + cur_plversion++; // TODO [db_queue] need to update db queue version listener_notify(LISTENER_PLAYLIST); @@ -3444,14 +3120,11 @@ player_get_icy_artwork_url(uint32_t id) * @return 0 if successful, -1 if an error occurred */ int -player_playback_start(uint32_t *id) +player_playback_start() { - union player_arg cmdarg; int ret; - cmdarg.playback_start_param.id_ptr = id; - - ret = commands_exec_sync(cmdbase, playback_start, playback_start_bh, &cmdarg); + ret = commands_exec_sync(cmdbase, playback_start, playback_start_bh, NULL); return ret; } @@ -3467,66 +3140,11 @@ player_playback_start(uint32_t *id) * @return 0 if successful, -1 if an error occurred */ int -player_playback_start_byindex(int index, uint32_t *id) +player_playback_start_byitem(struct db_queue_item *queue_item) { - union player_arg cmdarg; int ret; - cmdarg.playback_start_param.pos = index; - cmdarg.playback_start_param.id_ptr = id; - - ret = commands_exec_sync(cmdbase, playback_start_byindex, playback_start_bh, &cmdarg); - return ret; -} - -/* - * Starts playback with the media item at the given position in the UpNext-queue. - * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue - * (shuffle on) after the current playing item (starting with position 0). - * - * If shuffle is set, the queue is reshuffled prior to starting playback. - * - * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id. - * - * @param pos the position in the UpNext-queue (zero-based) - * @param *id if not NULL, will be set to the playing item dbmfi-id - * @return 0 if successful, -1 if an error occurred - */ -int -player_playback_start_bypos(int pos, uint32_t *id) -{ - union player_arg cmdarg; - int ret; - - cmdarg.playback_start_param.pos = pos; - cmdarg.playback_start_param.id_ptr = id; - - ret = commands_exec_sync(cmdbase, playback_start_bypos, playback_start_bh, &cmdarg); - return ret; -} - -/* - * Starts playback with the media item with the given (queueitem) item-id in queue - * - * If shuffle is set, the queue is reshuffled prior to starting playback. - * - * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id. - * - * @param item_id The queue-item-id - * @param *id if not NULL, will be set to the playing item dbmfi-id - * @return 0 if successful, -1 if an error occurred - */ -int -player_playback_start_byitemid(uint32_t item_id, uint32_t *id) -{ - union player_arg cmdarg; - int ret; - - cmdarg.playback_start_param.id = item_id; - cmdarg.playback_start_param.id_ptr = id; - ret = commands_exec_sync(cmdbase, playback_start_byitemid, playback_start_bh, &cmdarg); - return ret; - + ret = commands_exec_sync(cmdbase, playback_start_item, playback_start_bh, queue_item); return ret; } @@ -3668,202 +3286,6 @@ player_shuffle_set(int enable) return ret; } -/* - * Returns the queue info for max "count" media items in the UpNext-queue - * - * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue - * (shuffle on) after the current playing item (starting with position 0). - * - * @param count max number of media items to return - * @return queue info - */ -struct queue * -player_queue_get_bypos(int count) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_get_param.pos = -1; - cmdarg.queue_get_param.count = count; - cmdarg.queue_get_param.queue = NULL; - - ret = commands_exec_sync(cmdbase, playerqueue_get_bypos, NULL, &cmdarg); - - if (ret != 0) - return NULL; - - return cmdarg.queue_get_param.queue; -} - -/* - * Returns the queue info for max "count" media items starting with the item at the given - * index in the play-queue - * - * @param index Index of the play-queue for the first item - * @param count max number of media items to return - * @return queue info - */ -struct queue * -player_queue_get_byindex(int index, int count) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_get_param.pos = index; - cmdarg.queue_get_param.count = count; - cmdarg.queue_get_param.queue = NULL; - - ret = commands_exec_sync(cmdbase, playerqueue_get_byindex, NULL, &cmdarg); - - if (ret != 0) - return NULL; - - return cmdarg.queue_get_param.queue; -} - -/* - * Appends the given media items to the queue - */ -int -player_queue_add(struct queue_item *items, uint32_t *item_id) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_add_param.items = items; - cmdarg.queue_add_param.item_id_ptr = item_id; - - ret = commands_exec_sync(cmdbase, playerqueue_add, NULL, &cmdarg); - return ret; -} - -/* - * Adds the given media items directly after the current playing/streaming media item - */ -int -player_queue_add_next(struct queue_item *items) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_add_param.items = items; - - ret = commands_exec_sync(cmdbase, playerqueue_add_next, NULL, &cmdarg); - return ret; -} - -/* - * Moves the media item at 'pos_from' to 'pos_to' in the UpNext-queue. - * - * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue - * (shuffle on) after the current playing item (starting with position 0). - */ -int -player_queue_move_bypos(int pos_from, int pos_to) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_move_param.from_pos = pos_from; - cmdarg.queue_move_param.to_pos = pos_to; - - ret = commands_exec_sync(cmdbase, playerqueue_move_bypos, NULL, &cmdarg); - return ret; -} - -int -player_queue_move_byindex(int pos_from, int pos_to) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_move_param.from_pos = pos_from; - cmdarg.queue_move_param.to_pos = pos_to; - - ret = commands_exec_sync(cmdbase, playerqueue_move_byindex, NULL, &cmdarg); - return ret; -} - -int -player_queue_move_byitemid(uint32_t item_id, int pos_to) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_move_param.item_id = item_id; - cmdarg.queue_move_param.to_pos = pos_to; - - ret = commands_exec_sync(cmdbase, playerqueue_move_byitemid, NULL, &cmdarg); - return ret; -} - -/* - * Removes the media item at the given position from the UpNext-queue - * - * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue - * (shuffle on) after the current playing item (starting with position 0). - * - * @param pos Position in the UpNext-queue (0-based) - * @return 0 on success, -1 on failure - */ -int -player_queue_remove_bypos(int pos) -{ - union player_arg cmdarg; - int ret; - - cmdarg.intval = pos; - - ret = commands_exec_sync(cmdbase, playerqueue_remove_bypos, NULL, &cmdarg); - return ret; -} - -/* - * Removes the media item at the given position from the UpNext-queue - * - * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue - * (shuffle on) after the current playing item (starting with position 0). - * - * @param pos Position in the UpNext-queue (0-based) - * @return 0 on success, -1 on failure - */ -int -player_queue_remove_byindex(int pos, int count) -{ - union player_arg cmdarg; - int ret; - - cmdarg.queue_remove_param.from_pos = pos; - cmdarg.queue_remove_param.count = count; - - ret = commands_exec_sync(cmdbase, playerqueue_remove_byindex, NULL, &cmdarg); - return ret; -} - -/* - * Removes the item with the given (queueitem) item id from the queue - * - * @param id Id of the queue item to remove - * @return 0 on success, -1 on failure - */ -int -player_queue_remove_byitemid(uint32_t id) -{ - union player_arg cmdarg; - int ret; - - cmdarg.id = id; - - ret = commands_exec_sync(cmdbase, playerqueue_remove_byitemid, NULL, &cmdarg); - return ret; -} - -void -player_queue_clear(void) -{ - commands_exec_sync(cmdbase, playerqueue_clear, NULL, NULL); -} - void player_queue_clear_history() { @@ -3994,7 +3416,6 @@ player_init(void) repeat = REPEAT_OFF; shuffle = 0; - queue = queue_new(); history = (struct player_history *)calloc(1, sizeof(struct player_history)); /* @@ -4126,7 +3547,6 @@ player_deinit(void) return; } - queue_free(queue); free(history); pb_timer_stop(); diff --git a/src/player.h b/src/player.h index d0b4040b..faec7da3 100644 --- a/src/player.h +++ b/src/player.h @@ -5,7 +5,6 @@ #include #include "db.h" -#include "queue.h" /* AirTunes v2 packet interval in ns */ /* (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet */ @@ -28,6 +27,12 @@ enum play_status { PLAY_PLAYING = 4, }; +enum repeat_mode { + REPEAT_OFF = 0, + REPEAT_SONG = 1, + REPEAT_ALL = 2, +}; + struct spk_flags { unsigned selected:1; unsigned has_password:1; @@ -44,13 +49,6 @@ struct player_status { /* Playlist id */ uint32_t plid; - /* Playlist version - After startup plversion is 0 and gets incremented after each change of the playlist - (e. g. after adding/moving/removing items). It is used by mpd clients to recognize if - they need to update the current playlist. */ - uint32_t plversion; - /* Playlist length */ - uint32_t playlistlength; /* Id of the playing file/item in the files database */ uint32_t id; /* Item-Id of the playing file/item in the queue */ @@ -59,14 +57,6 @@ struct player_status { uint32_t pos_ms; /* Length in ms of playing item */ uint32_t len_ms; - /* Playlist position of playing item*/ - int pos_pl; - /* Item id of next item in playlist */ - uint32_t next_id; - /* Item-Id of the next file/item in the queue */ - uint32_t next_item_id; - /* Playlist position of next item */ - int next_pos_pl; }; typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg); @@ -104,16 +94,10 @@ int player_speaker_set(uint64_t *ids); int -player_playback_start(uint32_t *id); +player_playback_start(); int -player_playback_start_byindex(int pos, uint32_t *id); - -int -player_playback_start_bypos(int pos, uint32_t *id); - -int -player_playback_start_byitemid(uint32_t item_id, uint32_t *id); +player_playback_start_byitem(struct db_queue_item *queue_item); int player_playback_stop(void); @@ -147,39 +131,6 @@ int player_shuffle_set(int enable); -struct queue * -player_queue_get_bypos(int count); - -struct queue * -player_queue_get_byindex(int pos, int count); - -int -player_queue_add(struct queue_item *items, uint32_t *item_id); - -int -player_queue_add_next(struct queue_item *items); - -int -player_queue_move_bypos(int ps_pos_from, int ps_pos_to); - -int -player_queue_move_byindex(int pos_from, int pos_to); - -int -player_queue_move_byitemid(uint32_t item_id, int pos_to); - -int -player_queue_remove_bypos(int pos); - -int -player_queue_remove_byindex(int pos, int count); - -int -player_queue_remove_byitemid(uint32_t id); - -void -player_queue_clear(void); - void player_queue_clear_history(void); diff --git a/src/queue.c b/src/queue.c deleted file mode 100644 index 718eaa91..00000000 --- a/src/queue.c +++ /dev/null @@ -1,1272 +0,0 @@ -/* - * Copyright (C) 2015 Christian Meffert - * - * 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 - * (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. - * - * 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 - */ - - -#include "queue.h" - -#include -#include -#include -#include - -#include "logger.h" -#include "misc.h" -#include "rng.h" - - -/* - * Internal representation of an item in a queue. It links to the previous and the next item - * in the queue for shuffle on/off. To access the properties use the queueitem_* functions. - */ -struct queue_item -{ - /* Item-Id is a unique id for this queue item. If the same item appears multiple - times in the queue each corresponding queue item has its own id. */ - unsigned int item_id; - - /* Id of the file/item in the files database */ - uint32_t id; - - /* Length of the item in ms */ - unsigned int len_ms; - - /* Data type of the item */ - enum data_kind data_kind; - /* Media type of the item */ - enum media_kind media_kind; - - /* Link to the previous/next item in the queue */ - struct queue_item *next; - struct queue_item *prev; - - /* Link to the previous/next item in the shuffle queue */ - struct queue_item *shuffle_next; - struct queue_item *shuffle_prev; -}; - -/* - * The queue struct references two (double) linked lists of queue_item. One for the play-queue - * and one for the shuffle-queue. - * - * Both linked lists start with the "head" item. The head item is not a media item, instead it is - * an internal item created during initialization of a new queue struct (see queue_new() function). - * The head item is always the first item in the queue and will only be removed when queue is - * destructed. - * - * The linked lists are circular, therefor the last item in a list has the first item (the head item) - * as "next" and the first item in the queue (the head item) has the last item as "prev" linked. - * - * An empty queue (with no media items) will only consist of the head item pointing to itself. - */ -struct queue -{ - /* The queue item id of the last inserted item */ - unsigned int last_inserted_item_id; - - /* The version number of the queue */ - unsigned int version; - - /* Shuffle RNG state */ - struct rng_ctx shuffle_rng; - - /* - * The head item in the queue is not an actual media item, instead it is the - * starting point for the play-queue and the shuffle-queue. It always has the - * item-id 0. The queue is circular, the last item of the queue has the head - * item as "next" and the head item has the last item as "prev". - */ - struct queue_item *head; -}; - - -/* - * Creates and initializes a new queue - */ -struct queue * -queue_new() -{ - struct queue *queue; - - queue = (struct queue *)calloc(1, sizeof(struct queue)); - queue->head = (struct queue_item *)calloc(1, sizeof(struct queue_item)); - - // Create the head item and make the queue circular (head points to itself) - queue->head->next = queue->head; - queue->head->prev = queue->head; - queue->head->shuffle_next = queue->head; - queue->head->shuffle_prev = queue->head; - - rng_init(&queue->shuffle_rng); - - return queue; -} - -/* - * Frees the given item and all linked (next) items - */ -static void -queue_items_free(struct queue_item *item) -{ - struct queue_item *temp; - struct queue_item *next; - - if (!item) - return; - - // Make the queue non-circular - if (item->prev) - item->prev->next = NULL; - - next = item; - while (next) - { - temp = next->next; - free(next); - next = temp; - } -} - -/* - * Frees the given queue and all the items in it - */ -void -queue_free(struct queue *queue) -{ - queue_items_free(queue->head); - free(queue); -} - -/* - * Returns the number of media items in the queue - * - * @param queue The queue - * @return The number of items in the queue - */ -unsigned int -queue_count(struct queue *queue) -{ - struct queue_item *item; - int count; - - count = 0; - - for (item = queue->head->next; item != queue->head; item = item->next) - { - count++; - } - - return count; -} - -/* - * Returns the next item in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - */ -static struct queue_item * -item_next(struct queue_item *item, char shuffle) -{ - if (shuffle) - return item->shuffle_next; - return item->next; -} - -/* - * Returns the previous item in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - */ -static struct queue_item * -item_prev(struct queue_item *item, char shuffle) -{ - if (shuffle) - return item->shuffle_prev; - return item->prev; -} - -/* - * Returns the (0-based) position of the first item with the given dbmfi-id. - * If no item is found for the given id, it returns -1. - */ -int -queueitem_pos(struct queue_item *item, uint32_t id) -{ - struct queue_item *temp; - int pos; - - if (id == 0 || item->id == id) - return 0; - - pos = 1; - for (temp = item->next; (temp != item) && temp->id != id; temp = temp->next) - { - pos++; - } - - if (temp == item) - { - // Item with given (database) id does not exists - return -1; - } - - return pos; -} - -/* - * Returns the id of the item/file in the files database table - */ -uint32_t -queueitem_id(struct queue_item *item) -{ - return item->id; -} - -/* - * Returns the queue-item-id - */ -unsigned int -queueitem_item_id(struct queue_item *item) -{ - return item->item_id; -} - -/* - * Returns the length of the item in milliseconds - */ -unsigned int -queueitem_len(struct queue_item *item) -{ - return item->len_ms; -} - -/* - * Returns the data-kind - */ -enum data_kind -queueitem_data_kind(struct queue_item *item) -{ - return item->data_kind; -} - -/* - * Returns the media-kind - */ -enum media_kind -queueitem_media_kind(struct queue_item *item) -{ - return item->media_kind; -} - -/* - * Returns the item with the given item_id in the queue - * - * @param queue The queue - * @param item_id The unique id of the item in the queue - * @return Item with the given item_id or NULL if not found - */ -static struct queue_item * -queueitem_get_byitemid(struct queue *queue, int item_id) -{ - struct queue_item *item; - - for (item = queue->head->next; item != queue->head && item->item_id != item_id; item = item->next) - { - // Iterate through the queue until the item with item_id is found - } - - if (item == queue->head && item_id != 0) - return NULL; - - return item; -} - -/* - * Returns the item at the given index (0-based) in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * - * @param queue The queue - * @param index Index of item in the queue (0-based) - * @param shuffle Play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * @return Item at position in the queue or NULL if not found - */ -static struct queue_item * -queueitem_get_byindex(struct queue *queue, unsigned int index, char shuffle) -{ - struct queue_item *item; - int i; - - i = 0; - for (item = item_next(queue->head, shuffle); item != queue->head && i < index; item = item_next(item, shuffle)) - { - i++; - } - - if (item == queue->head) - return NULL; - - return item; -} - -/* - * Returns the item at the given position relative to the item with the given item_id in the - * play queue (shuffle = 0) or shuffle queue (shuffle = 1). - * - * The item with item_id is at pos == 0. - * - * @param queue The queue - * @param pos The position relative to the item with given queue-item-id - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - * @return Item at position in the queue or NULL if not found - */ -static struct queue_item * -queueitem_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle) -{ - struct queue_item *item_base; - struct queue_item *item; - int i; - - item_base = queueitem_get_byitemid(queue, item_id); - - if (!item_base) - return NULL; - - i = 0; - for (item = item_base; i < pos; item = item_next(item, shuffle)) - { - i++; - } - - if (item == queue->head) - return NULL; - - return item; -} - -/* - * Returns the item with the given item_id in the queue - * - * @param queue The queue - * @param item_id The unique id of the item in the queue - * @return Item with the given item_id or NULL if not found - */ -struct queue_item * -queue_get_byitemid(struct queue *queue, unsigned int item_id) -{ - struct queue_item *item; - - item = queueitem_get_byitemid(queue, item_id); - - if (!item) - return NULL; - - return item; -} - -/* - * Returns the item at the given index (0-based) in the play queue (shuffle = 0) or shuffle queue (shuffle = 1) - * - * @param queue The queue - * @param index Position of item in the queue (zero-based) - * @param shuffle Play queue (shuffle = 0) or shuffle queue (shuffle = 1) - * @return Item at index in the queue or NULL if not found - */ -struct queue_item * -queue_get_byindex(struct queue *queue, unsigned int index, char shuffle) -{ - struct queue_item *item; - - item = queueitem_get_byindex(queue, index, shuffle); - - if (!item) - return NULL; - - return item; -} - -/* - * Returns the item at the given position relative to the item with the given item_id in the - * play queue (shuffle = 0) or shuffle queue (shuffle = 1). - * - * The item with item_id is at pos == 0. - * - * @param queue The queue - * @param item_id The unique id of the item in the queue - * @param pos The position relative to the item with given queue-item-id - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - * @return Item at position in the queue or NULL if not found - */ -struct queue_item * -queue_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle) -{ - struct queue_item *item; - - item = queueitem_get_bypos(queue, item_id, pos, shuffle); - - if (!item) - return NULL; - - return item; -} - -/* - * Returns the index of the item with the given item-id (unique id in the queue) - * or -1 if the item does not exist. Depending on the given shuffle value, the position - * is either the on in the play-queue (shuffle = 0) or the shuffle-queue (shuffle = 1). - * - * @param queue The queue to search the item - * @param item_id The id of the item in the queue - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - * @return Index (0-based) of the item in the given queue or -1 if it does not exist - */ -int -queue_index_byitemid(struct queue *queue, unsigned int item_id, char shuffle) -{ - struct queue_item *item; - int pos; - - pos = 0; - for (item = item_next(queue->head, shuffle); item != queue->head && item->item_id != item_id; item = item_next(item, shuffle)) - { - pos++; - } - - if (item == queue->head) - // Item not found - return -1; - - return pos; -} - -/* - * Return the next item in the queue for the item with the given item-id. - * - * @param queue The queue - * @param item_id The id of the item in the queue - * @param shuffle If 0 return the next item in the play-queue, if 1 the next item in the shuffle-queue - * @param r_mode Repeat mode - * @param reshuffle If 1 and repeat mode is "repeat all" reshuffles the queue on wrap around - * @return The next item - */ -struct queue_item * -queue_next(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode, int reshuffle) -{ - struct queue_item *item; - - item = queueitem_get_byitemid(queue, item_id); - - if (!item) - // Item not found, start playing from the start of the queue - item = queue->head; - - if (r_mode == REPEAT_SONG && item != queue->head) - return item; - - item = item_next(item, shuffle); - - if (item == queue->head && r_mode == REPEAT_ALL) - { - // Repeat all and end of queue reached, return first item in the queue - if (reshuffle) - queue_shuffle(queue, 0); - item = item_next(queue->head, shuffle); - } - - if (item == queue->head) - return NULL; - - return item; -} - -/* - * Return the previous item in the queue for the item with the given item-id. - * - * @param queue The queue - * @param item_id The id of the item in the queue - * @param shuffle If 0 return the next item in the play-queue, if 1 the next item in the shuffle-queue - * @param r_mode Repeat mode - * @return The previous item - */ -struct queue_item * -queue_prev(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode) -{ - struct queue_item *item; - - item = queueitem_get_byitemid(queue, item_id); - - if (!item) - // Item not found - return NULL; - - if (r_mode == REPEAT_SONG && item != queue->head) - return item; - - item = item_prev(item, shuffle); - - if (item == queue->head && r_mode == REPEAT_ALL) - { - // Repeat all and start of queue reached, return last item in the queue - item = item_prev(queue->head, shuffle); - } - - if (item == queue->head) - return NULL; - - return item; -} - -/* - * Creates a new queue with a copy of the items of the given queue. - * - * The given number of items (count) are copied from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * starting with the item at the given index (0-based). - * - * If count == 0, all items from the given index up to the end of the queue will be returned. - * - * @param queue The queue - * @param index Index of the first item in the queue - * @param count Maximum number of items to copy (if 0 all remaining items after index) - * @param shuffle If 0 the play-queue, if 1 the shuffle queue - * @return A new queue with the specified items - */ -struct queue * -queue_new_byindex(struct queue *queue, unsigned int index, unsigned int count, char shuffle) -{ - struct queue *qi; - struct queue_item *qii; - struct queue_item *item; - int i; - unsigned int qlength; - int qii_size; - - qi = queue_new(); - - qlength = queue_count(queue); - - qii_size = qlength - index; - if (count > 0 && count < qii_size) - qii_size = count; - - if (qii_size <= 0) - { - return qi; - } - - item = queueitem_get_byindex(queue, index, shuffle); - - if (!item) - return NULL; - - i = 0; - for (; item != queue->head && i < qii_size; item = item_next(item, shuffle)) - { - qii = malloc(sizeof(struct queue_item)); - qii->id = item->id; - qii->item_id = item->item_id; - qii->len_ms = item->len_ms; - qii->data_kind = item->data_kind; - qii->media_kind = item->media_kind; - qii->next = qii; - qii->prev = qii; - qii->shuffle_next = qii; - qii->shuffle_prev = qii; - - queue_add(qi, qii); - - // queue_add(...) changes the queue item-id, reset the item-id to the original value - qii->item_id = item->item_id; - - i++; - } - - return qi; -} - -/* - * Creates a new queue with a copy of the items of the given queue. - * - * The given number of items (count) are copied from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * starting after the item with the given item_id. The item with item_id is excluded, therefor the first item - * is the one after the item with item_id. - * - * If count == 0, all items from the given index up to the end of the queue will be returned. - * - * @param queue The queue - * @param item_id The unique id of the item in the queue - * @param count Maximum number of items to copy (if 0 all remaining items after index) - * @param shuffle If 0 the play-queue, if 1 the shuffle queue - * @return A new queue with the specified items - */ -struct queue * -queue_new_bypos(struct queue *queue, unsigned int item_id, unsigned int count, char shuffle) -{ - int pos; - struct queue *qi; - - pos = queue_index_byitemid(queue, item_id, shuffle); - - if (pos < 0) - pos = 0; - else - pos = pos + 1; // exclude the item with the given item-id - - qi = queue_new_byindex(queue, pos, count, shuffle); - - return qi; -} - -/* - * Adds items to the queue after the given item - * - * @param queue The queue to add the new items - * @param item_new The item(s) to add - * @param item_prev The item to append the new items - */ -static void -queue_add_afteritem(struct queue *queue, struct queue_item *item_new, struct queue_item *item_prev) -{ - struct queue_item *item; - struct queue_item *item_tail; - - if (!item_new) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid new item given to add items\n"); - return; - } - - // Check the item after which the new items will be added - if (!item_prev) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid previous item given to add items\n"); - queue_items_free(item_new); - return; - } - - // Set item-id for all new items - queue->last_inserted_item_id++; - item_new->item_id = queue->last_inserted_item_id; - for (item = item_new->next; item != item_new; item = item->next) - { - queue->last_inserted_item_id++; - item->item_id = queue->last_inserted_item_id; - } - - // Add items into the queue - item_tail = item_new->prev; - - item_tail->next = item_prev->next; - item_tail->shuffle_next = item_prev->shuffle_next; - item_prev->next->prev = item_tail; - item_prev->shuffle_next->shuffle_prev = item_tail; - - item_prev->next = item_new; - item_prev->shuffle_next = item_new; - item_new->prev = item_prev; - item_new->shuffle_prev = item_prev; -} - -/* - * Adds items to the end of the queue - * - * @param queue The queue to add the new items - * @param item The item(s) to add - */ -void -queue_add(struct queue *queue, struct queue_item *item) -{ - queue_add_afteritem(queue, item, queue->head->prev); -} - -/* - * Adds items to the queue after the item with the given item id (id of the item in the queue) - * - * @param queue The queue to add the new items - * @param item The item(s) to add - * @param item_id The item id after which the new items will be inserted - */ -void -queue_add_after(struct queue *queue, struct queue_item *item, unsigned int item_id) -{ - struct queue_item *item_prev; - - // Get the item after which the new items will be added - item_prev = queueitem_get_byitemid(queue, item_id); - queue_add_afteritem(queue, item, item_prev); -} - -static void -queue_move_item_before_item(struct queue *queue, struct queue_item *item, struct queue_item *item_next, char shuffle) -{ - if (!item_next) - { - // If item_next is NULL the item should be inserted at the end of the queue (directly before the head item) - item_next = queue->head; - } - - // Remove item from the queue - if (shuffle) - { - item->shuffle_prev->shuffle_next = item->shuffle_next; - item->shuffle_next->shuffle_prev = item->shuffle_prev; - } - else - { - item->prev->next = item->next; - item->next->prev = item->prev; - } - - // Insert item into the queue before the item at the target postion - if (shuffle) - { - item_next->shuffle_prev->shuffle_next = item; - item->shuffle_prev = item_next->shuffle_prev; - - item_next->shuffle_prev = item; - item->shuffle_next = item_next; - } - else - { - item_next->prev->next = item; - item->prev = item_next->prev; - - item_next->prev = item; - item->next = item_next; - } -} - -/* - * Moves the item at from_pos to to_pos in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * - * The position arguments are relativ to the item with the given id. At position = 1 is the first item - * after the item with the given id (either in the play-queue or shuffle-queue, depending on the shuffle - * argument). - * - * @param queue The queue to move items - * @param from_pos The position of the first item to be moved - * @param to_pos The position to move the items - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - */ -void -queue_move_bypos(struct queue *queue, unsigned int item_id, unsigned int from_pos, unsigned int to_offset, char shuffle) -{ - struct queue_item *item; - struct queue_item *item_next; - - // Get the item to be moved - item = queueitem_get_bypos(queue, item_id, from_pos, shuffle); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid position given to move items\n"); - return; - } - - // Get the item at the target position - item_next = queueitem_get_bypos(queue, item_id, (to_offset + 1), shuffle); - - queue_move_item_before_item(queue, item, item_next, shuffle); -} - -void -queue_move_byindex(struct queue *queue, unsigned int from_pos, unsigned int to_pos, char shuffle) -{ - struct queue_item *item; - struct queue_item *item_next; - - if (from_pos == to_pos) - return; - - // Get the item to be moved - item = queueitem_get_byindex(queue, from_pos, shuffle); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid position given to move items\n"); - return; - } - - // Check if the index of the item to move is lower than the target index - // If that is the case, increment the target position, because the given to_pos - // is based on the queue without the moved item. - if (from_pos < to_pos) - to_pos++; - - // Get the item at the target position - item_next = queueitem_get_byindex(queue, to_pos, shuffle); - - queue_move_item_before_item(queue, item, item_next, shuffle); -} - -/* - * Moves the item with the given item-id to the index to_pos in the play-queue (shuffle = 0) - * or shuffle-queue (shuffle = 1) - * - * @param queue The queue to move item - * @param item_id The item-id of the to be moved - * @param to_pos The index to move the item - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - */ -void -queue_move_byitemid(struct queue *queue, unsigned int item_id, unsigned int to_pos, char shuffle) -{ - struct queue_item *item; - struct queue_item *item_next; - int from_pos; - - // Get the item to be moved - item = queueitem_get_byitemid(queue, item_id); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Item with item-id %d does not exist in the queue\n", item_id); - return; - } - - from_pos = queue_index_byitemid(queue, item_id, shuffle); - - if (from_pos == to_pos) - { - DPRINTF(E_DBG, L_PLAYER, "Ignore moving item %d from index %d to %d\n", item_id, from_pos, to_pos); - return; - } - - // Check if the index of the item to move is lower than the target index - // If that is the case, increment the target position, because the given to_pos - // is based on the queue without the moved item. - if (from_pos < to_pos) - to_pos++; - - // Get the item at the target position - item_next = queueitem_get_byindex(queue, to_pos, shuffle); - - queue_move_item_before_item(queue, item, item_next, shuffle); -} - -/* - * Removes the item from the queue and frees it - */ -static void -queue_remove_item(struct queue_item *item) -{ - struct queue_item *item_next; - struct queue_item *item_prev; - - item_next = item->next; - item_prev = item->prev; - - item_prev->next = item_next; - item_next->prev = item_prev; - - item_next = item->shuffle_next; - item_prev = item->shuffle_prev; - - item_prev->shuffle_next = item_next; - item_next->shuffle_prev = item_prev; - - item->next = NULL; - item->prev = NULL; - item->shuffle_next = NULL; - item->shuffle_prev = NULL; - - free(item); -} - -/* - * Removes the item with the given item-id from the queue - */ -void -queue_remove_byitemid(struct queue *queue, unsigned int item_id) -{ - struct queue_item *item; - - // Do not remove the head item - if (item_id <= 0) - return; - - // Get the item after which the items will be removed from the queue - item = queueitem_get_byitemid(queue, item_id); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid item-id given to remove items\n"); - return; - } - - queue_remove_item(item); -} - -/* - * Remove item at index from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * - * @param queue The queue - * @param index The index of the item to be removed (0-based) - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - */ -void -queue_remove_byindex(struct queue *queue, unsigned int index, char shuffle) -{ - struct queue_item *item; - - // Get the item after which the items will be removed from the queue - item = queueitem_get_byindex(queue, index, shuffle); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid position given to remove items\n"); - return; - } - - queue_remove_item(item); -} - -/* - * Removes the item at pos from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) - * - * The position argument is relativ to the item with the given id. At position = 1 is the first item - * after the item with the given id (either in the play-queue or shuffle-queue, depending on the shuffle - * argument). - * - * @param queue The queue to add the new items - * @param item_id The unique id of the item in the queue - * @param pos The position of the first item to be removed - * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue - */ -void -queue_remove_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle) -{ - struct queue_item *item; - - // Get the item after which the items will be removed from the queue - item = queueitem_get_bypos(queue, item_id, pos, shuffle); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid position given to remove items\n"); - return; - } - - queue_remove_item(item); -} - -/* - * Removes all items from the queue - * - * @param queue The queue to clear - */ -void -queue_clear(struct queue *queue) -{ - struct queue_item *item; - - // Check if the queue is already empty - if (queue->head->next == queue->head) - return; - - // Remove the head item from the shuffle-queue - item = queue->head->shuffle_next; - item->shuffle_prev = queue->head->shuffle_prev; - queue->head->shuffle_prev->shuffle_next = item; - - // Remove the head item from the play-queue - item = queue->head->next; - item->prev = queue->head->prev; - queue->head->prev->next = item; - - // item now points to the first item in the play-queue (excluding the head item) - queue_items_free(item); - - // Make the queue circular again - queue->head->next = queue->head; - queue->head->prev = queue->head; - queue->head->shuffle_next = queue->head; - queue->head->shuffle_prev = queue->head; -} - -/* - * Resets the shuffle-queue to be identical to the play-queue and returns the item - * with the given item_id. - * - * If no item was found with the given item_id, it returns the head item. - */ -static struct queue_item * -queue_reset_and_find(struct queue *queue, unsigned int item_id) -{ - struct queue_item *item; - struct queue_item *temp; - - item = queue->head; - - item->shuffle_next = item->next; - item->shuffle_prev = item->prev; - - for (temp = item->next; temp != queue->head; temp = temp->next) - { - temp->shuffle_next = temp->next; - temp->shuffle_prev = temp->prev; - - if (temp->item_id == item_id) - item = temp; - } - - return item; -} - -/* - * Shuffles the queue - * - * If the item_id > 0, only the items in the queue after the item (excluding it) - * with the given id are shuffled. - * - * @param queue The queue to shuffle - * @param item_id 0 to shuffle the whole queue or the item-id after which the queue gets shuffled - */ -void -queue_shuffle(struct queue *queue, unsigned int item_id) -{ - struct queue_item *temp; - struct queue_item *item; - struct queue_item **item_array; - int nitems; - int i; - - item = queue_reset_and_find(queue, item_id); - - // Count items to reshuffle - nitems = 0; - for (temp = item->next; temp != queue->head; temp = temp->next) - { - nitems++; - } - - // Do not reshuffle queue with one item - if (nitems < 2) - return; - - // Construct array for number of items in queue - item_array = (struct queue_item **)malloc(nitems * sizeof(struct queue_item *)); - if (!item_array) - { - DPRINTF(E_LOG, L_PLAYER, "Could not allocate memory for shuffle array\n"); - return; - } - - // Fill array with items in queue - i = 0; - for (temp = item->next; temp != queue->head; temp = temp->next) - { - item_array[i] = temp; - i++; - } - - // Shuffle item array - shuffle_ptr(&queue->shuffle_rng, (void **)item_array, nitems); - - // Update shuffle-next/-prev for shuffled items - for (i = 0; i < nitems; i++) - { - temp = item_array[i]; - - if (i > 0) - temp->shuffle_prev = item_array[i - 1]; - else - temp->shuffle_prev = NULL; - - if (i < (nitems - 1)) - temp->shuffle_next = item_array[i + 1]; - else - temp->shuffle_next = NULL; - } - - // Insert shuffled items after item with given item_id - item->shuffle_next = item_array[0]; - item_array[0]->shuffle_prev = item; - - queue->head->shuffle_prev = item_array[nitems - 1]; - item_array[nitems - 1]->shuffle_next = queue->head; - - free(item_array); -} - -/* - * Creates a new queue item for the given media file - * - * @param dbmfi media file info - * @return The new queue item or NULL if an error occured - */ -static struct queue_item * -queue_item_new(struct db_media_file_info *dbmfi) -{ - struct queue_item *item; - uint32_t id; - uint32_t len_ms; - uint32_t data_kind; - uint32_t media_kind; - int ret; - - ret = safe_atou32(dbmfi->id, &id); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n"); - return NULL; - } - - ret = safe_atou32(dbmfi->song_length, &len_ms); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid song length in query result!\n"); - return NULL; - } - - ret = safe_atou32(dbmfi->data_kind, &data_kind); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid data kind in query result!\n"); - return NULL; - } - - ret = safe_atou32(dbmfi->media_kind, &media_kind); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid media kind in query result!\n"); - return NULL; - } - - item = (struct queue_item *) calloc(1, sizeof(struct queue_item)); - if (!item) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory for struct queue_item\n"); - return NULL; - } - - item->id = id; - item->len_ms = len_ms; - item->data_kind = data_kind; - item->media_kind = media_kind; - - return item; -} - -struct queue_item * -queueitem_make_byquery(struct query_params *qp) -{ - struct db_media_file_info dbmfi; - struct queue_item *item_head; - struct queue_item *item_tail; - struct queue_item *item_temp; - int ret; - - ret = db_query_start(qp); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not start query\n"); - return NULL; - } - - DPRINTF(E_DBG, L_PLAYER, "Player queue query returned %d items\n", qp->results); - - item_head = NULL; - item_tail = NULL; - while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id)) - { - item_temp = queue_item_new(&dbmfi); - if (!item_temp) - { - DPRINTF(E_LOG, L_PLAYER, "Error creating new queue_item for id '%s'\n", dbmfi.id); - continue; - } - - if (!item_head) - item_head = item_temp; - - if (item_tail) - { - item_tail->next = item_temp; - item_temp->prev = item_tail; - item_tail->shuffle_next = item_temp; - item_temp->shuffle_prev = item_tail; - } - - item_tail = item_temp; - - DPRINTF(E_DBG, L_PLAYER, "Added song id %s (%s)\n", dbmfi.id, dbmfi.title); - } - - db_query_end(qp); - - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Error fetching results\n"); - queue_items_free(item_tail); - return NULL; - } - - if (!item_head || !item_tail) - { - DPRINTF(E_INFO, L_PLAYER, "No item found to add to queue\n"); - return NULL; - } - - item_head->prev = item_tail; - item_tail->next = item_head; - item_head->shuffle_prev = item_tail; - item_tail->shuffle_next = item_head; - - return item_head; -} - -/* - * Makes a list of queue-items for the given playlist id (plid) - * - * @param plid Id of the playlist - * @return List of items for all playlist items - */ -struct queue_item * -queueitem_make_byplid(int plid) -{ - struct query_params qp; - struct queue_item *item; - - memset(&qp, 0, sizeof(struct query_params)); - - qp.id = plid; - qp.type = Q_PLITEMS; - qp.offset = 0; - qp.limit = 0; - qp.sort = S_NONE; - qp.idx_type = I_NONE; - - item = queueitem_make_byquery(&qp); - - return item; -} - -/* - * Makes a queue-item for the item/file with the given id - * - * @param id Id of the item/file in the db - * @return List of items containing only the item with the given id - */ -struct queue_item * -queueitem_make_byid(uint32_t id) -{ - struct query_params qp; - struct queue_item *item; - char buf[124]; - - memset(&qp, 0, sizeof(struct query_params)); - - qp.id = 0; - qp.type = Q_ITEMS; - qp.offset = 0; - qp.limit = 0; - qp.sort = S_NONE; - snprintf(buf, sizeof(buf), "f.id = %" PRIu32, id); - qp.filter = buf; - - item = queueitem_make_byquery(&qp); - - return item; -} diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index 556aec91..00000000 --- a/src/queue.h +++ /dev/null @@ -1,116 +0,0 @@ - -#ifndef SRC_QUEUE_H_ -#define SRC_QUEUE_H_ - - -#include "db.h" - -enum repeat_mode { - REPEAT_OFF = 0, - REPEAT_SONG = 1, - REPEAT_ALL = 2, -}; - - -/* - * Internal representation of a queue - */ -struct queue; - -/* - * Internal representation of a list of queue items - */ -struct queue_item; - - -struct queue * -queue_new(); - -void -queue_free(struct queue *queue); - -unsigned int -queue_count(struct queue *queue); - -int -queueitem_pos(struct queue_item *item, uint32_t id); - -uint32_t -queueitem_id(struct queue_item *item); - -unsigned int -queueitem_item_id(struct queue_item *item); - -unsigned int -queueitem_len(struct queue_item *item); - -enum data_kind -queueitem_data_kind(struct queue_item *item); - -enum media_kind -queueitem_media_kind(struct queue_item *item); - -struct queue_item * -queue_get_byitemid(struct queue *queue, unsigned int item_id); - -struct queue_item * -queue_get_byindex(struct queue *queue, unsigned int index, char shuffle); - -struct queue_item * -queue_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle); - -int -queue_index_byitemid(struct queue *queue, unsigned int item_id, char shuffle); - -struct queue_item * -queue_next(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode, int reshuffle); - -struct queue_item * -queue_prev(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode); - -struct queue * -queue_new_byindex(struct queue *queue, unsigned int index, unsigned int count, char shuffle); - -struct queue * -queue_new_bypos(struct queue *queue, unsigned int item_id, unsigned int count, char shuffle); - -void -queue_add(struct queue *queue, struct queue_item *item); - -void -queue_add_after(struct queue *queue, struct queue_item *item, unsigned int item_id); - -void -queue_move_bypos(struct queue *queue, unsigned int item_id, unsigned int from_pos, unsigned int to_offset, char shuffle); - -void -queue_move_byindex(struct queue *queue, unsigned int from_pos, unsigned int to_pos, char shuffle); - -void -queue_move_byitemid(struct queue *queue, unsigned int item_id, unsigned int to_pos, char shuffle); - -void -queue_remove_byitemid(struct queue *queue, unsigned int item_id); - -void -queue_remove_byindex(struct queue *queue, unsigned int index, char shuffle); - -void -queue_remove_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle); - -void -queue_clear(struct queue *queue); - -void -queue_shuffle(struct queue *queue, unsigned int item_id); - -struct queue_item * -queueitem_make_byquery(struct query_params *qp); - -struct queue_item * -queueitem_make_byplid(int plid); - -struct queue_item * -queueitem_make_byid(uint32_t id); - -#endif /* SRC_QUEUE_H_ */ diff --git a/src/rng.c b/src/rng.c index 5b57e06a..00d0e5ce 100644 --- a/src/rng.c +++ b/src/rng.c @@ -128,11 +128,11 @@ rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max) * Durstenfeld in-place shuffling variant */ void -shuffle_ptr(struct rng_ctx *ctx, void **values, int len) +shuffle_int(struct rng_ctx *ctx, int *values, int len) { int i; int32_t j; - void *tmp; + int tmp; for (i = len - 1; i > 0; i--) { @@ -143,4 +143,3 @@ shuffle_ptr(struct rng_ctx *ctx, void **values, int len) values[j] = tmp; } } - diff --git a/src/rng.h b/src/rng.h index b6a2c34f..200f4a52 100644 --- a/src/rng.h +++ b/src/rng.h @@ -19,7 +19,7 @@ int32_t rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max); void -shuffle_ptr(struct rng_ctx *ctx, void **values, int len); +shuffle_int(struct rng_ctx *ctx, int *values, int len); #endif /* !__RNG_H__ */