#define _POSIX_C_SOURCE 200809L #include <lektor/common.h> #include <lektor/database.h> #include <lektor/internal/dbmacro.h> #define sqlite_just_exec(func, query) \ bool func(lkt_db *db) \ { \ SQLITE_EXEC(db, "BEGIN TRANSACTION;" query "COMMIT;", error); \ return true; \ error: \ SQLITE_DO_ROLLBACK(db); \ return false; \ } // clang-format off sqlite_just_exec(database_queue_toggle_pause, "UPDATE queue_state SET paused = 1 - paused;"); sqlite_just_exec(database_queue_crop, "DELETE FROM queue WHERE queue.kara_id <> (SELECT current FROM queue_state LIMIT 1);"); sqlite_just_exec(database_queue_stop, "UPDATE queue_state SET current = NULL;"); sqlite_just_exec(database_queue_clear, "DELETE FROM queue;" "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';" "UPDATE queue_state SET current = NULL;"); sqlite_just_exec(database_config_queue_default, "UPDATE queue_state SET volume = 100, paused = 1," " random = 0, repeat = 0, single = 0, consume = 0," " current = NULL, duration = 0;"); sqlite_just_exec(database_queue_flat, "UPDATE queue SET priority = 1;"); // clang-format on #undef sqlite_just_exec PRIVATE_FUNCTION void __queue_resequence(lkt_db *db) { static const char *SQL = "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence SET seq = (SELECT COUNT(*) FROM queue) WHERE name = 'queue';"; SQLITE_EXEC(db, SQL, error); LOG_INFO("DB", "Re-sequenced the queue"); return; error: LOG_ERROR("DB", "Failed to update the " LKT_PROTECTED_DATABASE ".sqlite_sequence table..."); return; } bool database_queue_set_paused(lkt_db *db, bool paused) { const char *SQL = paused ? "UPDATE queue_state SET paused = 1;" : "UPDATE queue_state SET paused = 0;"; SQLITE_EXEC(db, SQL, error); return true; error: return false; } bool database_queue_state(lkt_db *db, struct lkt_queue_state *res) { static const char *SQL_STMT = "SELECT" " volume, paused, random, repeat, single, current, duration, consume," " (SELECT COUNT(*) FROM queue) AS length " "FROM queue_state;\n"; sqlite3_stmt *stmt = 0; bool ret = false; SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_STEP_ROW(db, stmt, error); res->volume = sqlite3_column_int(stmt, 0); res->paused = sqlite3_column_int(stmt, 1); res->random = sqlite3_column_int(stmt, 2); res->repeat = sqlite3_column_int(stmt, 3); res->single = sqlite3_column_int(stmt, 4); if (sqlite3_column_type(stmt, 5) == SQLITE_NULL) res->current = -1; else res->current = sqlite3_column_int(stmt, 5); if (sqlite3_column_type(stmt, 6) == SQLITE_NULL) res->duration = 0; else res->duration = sqlite3_column_int(stmt, 6); res->consume = sqlite3_column_int(stmt, 7); res->length = sqlite3_column_int(stmt, 8); ret = true; error: sqlite3_finalize(stmt); return ret; } void database_queue_playtime(lkt_db *db, uint64_t *seconds) { static const char *SQL = "SELECT SUM(cached_duration) " "FROM kara " "JOIN queue ON queue.kara_id = kara.id;"; sqlite3_stmt *stmt = NULL; *seconds = 0; SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_STEP_ROW(db, stmt, error); *seconds = sqlite3_column_int(stmt, 0); error: sqlite3_finalize(stmt); return; } void database_total_playtime(lkt_db *db, uint64_t *seconds) { static const char *SQL = "SELECT SUM(cached_duration) FROM kara;"; sqlite3_stmt *stmt = NULL; *seconds = 0; SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_STEP_ROW(db, stmt, error); *seconds = sqlite3_column_int(stmt, 0); error: sqlite3_finalize(stmt); return; } bool database_queue_current_kara(lkt_db *db, struct kara_metadata *res, int *id) { static const char *SQL_STMT = "SELECT song_name, source_name, category, language, author_name, " "song_type, song_number, kara_id" " FROM kara" " JOIN queue ON kara_id = kara.id" " JOIN queue_state ON current = position"; sqlite3_stmt *stmt = NULL; int ret = false; SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_STEP_ROW(db, stmt, error); /* Here use gotos because of optimisations done by compilators. Most of the time it won't be NULL. */ if (!res) goto no_metadata; // clang-format off strncpy(res->song_name, sqlite3_column_chars(stmt, 0), LEKTOR_TAG_MAX - 1); strncpy(res->source_name, sqlite3_column_chars(stmt, 1), LEKTOR_TAG_MAX - 1); strncpy(res->category, sqlite3_column_chars(stmt, 2), LEKTOR_TAG_MAX - 1); strncpy(res->language, sqlite3_column_chars(stmt, 3), LEKTOR_TAG_MAX - 1); strncpy(res->author_name, sqlite3_column_chars(stmt, 4), LEKTOR_TAG_MAX - 1); strncpy(res->song_type, sqlite3_column_chars(stmt, 5), LEKTOR_TAG_MAX - 1); // clang-format on res->song_number = sqlite3_column_int(stmt, 6); no_metadata: /* Most of the time this will be NULL. */ if (id && sqlite3_column_type(stmt, 7) != SQLITE_NULL) *id = sqlite3_column_int(stmt, 7); ret = true; error: sqlite3_finalize(stmt); return ret; } #define reorder(db, prio, error) \ if (prio > 1) { \ if (__queue_reorder(db)) { \ LOG_INFO("DB", "Queue has been reordered"); \ } else { \ LOG_INFO("DB", "Failed to reorder"); \ goto error; \ } \ } static bool __queue_reorder(lkt_db *db) { #define CURRENT_POS_OR_0 /* To move karas after the current */ \ "(SELECT CASE WHEN (SELECT current FROM queue_state) IS NULL THEN 0" \ " ELSE (SELECT current FROM queue_state) END AS val LIMIT 1)" static const char *SQL_REORDER = /* Clear the TMP */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" /* Separate karas that are after the current one */ "INSERT INTO queue_tmp (kara_id, priority)" " SELECT kara_id, priority FROM queue WHERE position > " CURRENT_POS_OR_0 " ORDER BY priority DESC, position ASC;" "DELETE FROM queue WHERE position > " CURRENT_POS_OR_0 ";" /* Update the sqlite_sequence table */ "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence SET seq = " CURRENT_POS_OR_0 " WHERE name = 'queue';" /* Insert back */ "INSERT INTO queue (position, kara_id, priority)" " SELECT position + " CURRENT_POS_OR_0 ", kara_id, priority" " FROM queue_tmp;" /* Clear the TMP */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"; #undef CURRENT_POS_OR_0 SQLITE_EXEC(db, SQL_REORDER, error); return true; error: return false; } static bool queue_add_with_col_like_str(lkt_db *db, const char *col, const char *val, int priority) { static const char *SQL_STMT = /* Add correspondig karas into the queue */ "INSERT INTO queue (kara_id, priority)" " SELECT id, ?" " FROM kara" " WHERE %s LIKE ? AND available = 1" " ORDER BY RANDOM();"; RETURN_IF(___is_sql_str_invalid(col), "Column name is invalid", false); char SQL[LKT_MAX_SQLITE_STATEMENT]; sqlite3_stmt *stmt = NULL; /* Begin, transactions are atomic so we won't be anoyed by multi-threading */ SQLITE_EXEC(db, "BEGIN TRANSACTION;", error); /* Insert at the end of the queue */ safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_STMT, col); SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_BIND_INT(db, stmt, 1, priority, error); SQLITE_BIND_TEXT(db, stmt, 2, val, error); SQLITE_STEP_DONE(db, stmt, error); sqlite3_finalize(stmt); /* Reorder kara that are after the current one, do this shit only if priority > 1... */ reorder(db, priority, error); /* End */ SQLITE_EXEC(db, "COMMIT;", error); LOG_INFO("DB-DEBUG", "Added with col %s like %s with priority %d", col, val, priority); return true; error: sqlite3_finalize(stmt); SQLITE_DO_ROLLBACK(db); __queue_resequence(db); return false; } bool database_queue_add_plt(lkt_db *db, const char *plt_name, int priority) { static const char *SQL_STMT = /* Insert the playlist */ "INSERT INTO queue (kara_id, priority) " "SELECT kara.id, ?" " FROM kara" " JOIN kara_playlist ON kara_id = kara.id AND kara.available = 1" " JOIN playlist ON playlist_id = playlist.id AND playlist.name = ?" " ORDER BY RANDOM();"; sqlite3_stmt *stmt = NULL; /* Begin, transactions are atomic so we won't be anoyed by multi-threading */ SQLITE_EXEC(db, "BEGIN TRANSACTION;", error); SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_BIND_INT(db, stmt, 1, priority, error); SQLITE_BIND_TEXT(db, stmt, 2, plt_name, error); SQLITE_STEP_DONE(db, stmt, error); sqlite3_finalize(stmt); /* Do the move shit only if the priority > 1 */ reorder(db, priority, error); SQLITE_EXEC(db, "COMMIT;", error); LOG_INFO("DB-DEBUG", "Added play list %s with priority %d", plt_name, priority); return true; error: sqlite3_finalize(stmt); SQLITE_DO_ROLLBACK(db); __queue_resequence(db); return false; } bool database_queue_add_id(lkt_db *db, int id, int priority) { static const char *SQL_STMT = "INSERT INTO queue (kara_id, priority) VALUES (?,?);"; sqlite3_stmt *stmt = NULL; SQLITE_EXEC(db, "BEGIN TRANSACTION;", error); SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_BIND_INT(db, stmt, 1, id, error); SQLITE_BIND_INT(db, stmt, 2, priority, error); SQLITE_STEP_DONE(db, stmt, error); sqlite3_finalize(stmt); /* Do the move shit only if the priority > 1 */ reorder(db, priority, error_no_stmt); SQLITE_EXEC(db, "COMMIT;", error); return true; error: LOG_ERROR("DB", "Error while adding the kara id %d into the queue with the " "priority %d", id, priority); sqlite3_finalize(stmt); error_no_stmt: LOG_ERROR("DB", "Need to rollback the queue because of failed insertion of id " "%d with priority %d in the queue", id, priority); SQLITE_DO_ROLLBACK(db); __queue_resequence(db); return false; } bool database_queue_add_uri(lkt_db *db, struct lkt_uri *uri, int prio) { switch (uri->type) { case URI_QUERY: return queue_add_with_col_like_str(db, LKT_DB_ALL, uri->value, prio); case URI_ID: return database_queue_add_id(db, uri->id, prio); case URI_TYPE: return queue_add_with_col_like_str(db, LKT_DB_TYPE, uri->value, prio); case URI_CATEGORY: return queue_add_with_col_like_str(db, LKT_DB_CAT, uri->value, prio); case URI_LANGUAGE: return queue_add_with_col_like_str(db, LKT_DB_LANG, uri->value, prio); case URI_AUTHOR: return queue_add_with_col_like_str(db, LKT_DB_AUTHOR, uri->value, prio); case URI_PLAYLIST: return database_queue_add_plt(db, uri->value, prio); default: LOG_WARN("DB", "Add to queue for uri of type %d is not implemented", uri->type); return false; } } bool database_queue_del_id(lkt_db *db, int id) { static const char *SQL_TEMPLATE = "BEGIN TRANSACTION;" /* Clear the TMP */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" /* Move the current 'pointer' */ "UPDATE queue_state SET current = (SELECT NULLIF(COUNT(position), 0) FROM queue JOIN queue_state ON position <= current AND kara_id != %d);" "DELETE FROM queue WHERE kara_id = %d;" /* Delete any kara with the specified id */ "INSERT INTO queue_tmp(priority, position, kara_id) SELECT priority, position, kara_id FROM queue ORDER BY position ASC;" "DELETE FROM queue;" "INSERT INTO queue(priority, position, kara_id) SELECT priority, ROW_NUMBER() OVER(ORDER BY position ASC), kara_id FROM queue_tmp;" "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence SET seq = (SELECT COUNT(*) FROM queue) WHERE name = 'queue';" /* Update the sqlite_sequence */ /* Clear the TMP */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" "COMMIT;"; char SQL[LKT_MAX_SQLITE_STATEMENT]; safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE, id, id); SQLITE_EXEC(db, SQL, error); __queue_resequence(db); return true; error: SQLITE_DO_ROLLBACK(db); return false; } bool database_queue_del_pos(lkt_db *db, int pos) { bool sta = false; sqlite3_stmt *stmt = NULL; struct lkt_queue_state queue = { .current = -1 }; RETURN_UNLESS(database_queue_state(db, &queue), "Failed to get the queue state", false); /* Delete the current kara, can't do that because we need to skip it, and * send signals to the window module to do so, so we just fail here... */ if (queue.current == pos) { LOG_ERROR("DB", "Can't delete the currently playing kara at position %d in queue", pos); return false; } static const char *SQL_TEMPLATE = /* Create temporary queue */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" /* -1 to all positions after the deleted kara */ "DELETE FROM queue WHERE position = %d;" "INSERT INTO queue_tmp (kara_id, priority)" " SELECT kara_id, priority" " FROM queue;" /* Re-init positions in the queue */ "DELETE FROM queue;" "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';" /* Insert back all karas */ "INSERT INTO queue (kara_id, priority, position)" " SELECT kara_id, priority, position" " FROM queue_tmp;" /* Slap correct values here */ "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence" " SET seq = (SELECT COUNT(position) FROM queue)" " WHERE name = 'queue';" "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"; char SQL[LKT_MAX_SQLITE_STATEMENT]; safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE, pos); SQLITE_EXEC(db, "BEGIN TRANSACTION;", error_no_rollback); SQLITE_EXEC(db, SQL, error_rollback); if (pos < queue.current) { LOG_DEBUG("DB", "Needs to decrement current index: pos %d < current %d", pos, queue.current); SQLITE_EXEC(db, "UPDATE queue_state SET current = current - 1;", error_in_delete_or_update); } sta = true; error_in_delete_or_update: if (sta) { /* Commit transaction, if failed use a magnificient goto... */ __queue_resequence(db); SQLITE_EXEC(db, "END TRANSACTION;", error_rollback); } else { error_rollback: sta = false; LOG_DEBUG("DB", "An error occured, needs to rollback transaction"); SQLITE_DO_ROLLBACK(db); } error_no_rollback: if (stmt != NULL) sqlite3_finalize(stmt); return sta; } bool database_queue_next(lkt_db *db, char filepath[PATH_MAX]) { static const char *SQL_STMT = "SELECT file_path, position, RANDOM()" " FROM kara" " JOIN queue ON kara.id = queue.kara_id" " JOIN queue_state" " ON current IS NULL" " OR (CASE WHEN (SELECT single FROM queue_state LIMIT 1) = 1 THEN position = current" " WHEN (SELECT random FROM queue_state LIMIT 1) = 1 THEN position <> current" " ELSE position > current END)" " ORDER BY CASE WHEN (SELECT random FROM queue_state LIMIT 1) = 1 THEN RANDOM() ELSE 2 END" " LIMIT 1"; char SQL_UPDATE[LKT_MAX_SQLITE_STATEMENT]; bool status = false; int code = SQLITE_OK, id; sqlite3_stmt *stmt = NULL; SQLITE_PREPARE(db, stmt, SQL_STMT, error); code = sqlite3_step(stmt); if (code == SQLITE_ROW) { id = MAX(1, sqlite3_column_int(stmt, 1)); safe_snprintf(SQL_UPDATE, LKT_MAX_SQLITE_STATEMENT, "UPDATE queue_state SET current = %d;", id); if (filepath != NULL) strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX - 1); else { LOG_ERROR("DB", "Failed to get file, id was %d", id); goto error; } } else if (code == SQLITE_DONE) { LOG_ERROR("DB", "Failed to get next"); goto error; } else { LOG_ERROR("DB", "Failed to fetch next kara: %s", sqlite3_errmsg((sqlite3 *)db)); goto error; } SQLITE_EXEC(db, SQL_UPDATE, error); status = true; error: sqlite3_finalize(stmt); return status; } bool database_queue_skip_current(lkt_db *db, char filepath[PATH_MAX]) { if (database_queue_next(db, filepath)) return true; else if (database_queue_prev(db, filepath)) return true; else { database_queue_stop(db); return false; } } bool database_queue_prev(lkt_db *db, char filepath[PATH_MAX]) { static const char *SQL_STMT = "SELECT file_path, position " " FROM kara" " JOIN queue ON kara.id = queue.kara_id AND available = 1" " JOIN queue_state ON CASE " " WHEN (SELECT single FROM queue_state LIMIT 1) = 1 THEN position = current" " ELSE queue.position < queue_state.current" " END" " ORDER BY position DESC LIMIT 1;"; char SQL_UPDATE[LKT_MAX_SQLITE_STATEMENT]; bool status = false; int code = SQLITE_OK, id; sqlite3_stmt *stmt = NULL; SQLITE_PREPARE(db, stmt, SQL_STMT, error); code = sqlite3_step(stmt); if (code == SQLITE_ROW) { id = MAX(1, sqlite3_column_int(stmt, 1)); safe_snprintf(SQL_UPDATE, LKT_MAX_SQLITE_STATEMENT, "UPDATE queue_state SET current = %d;", id); if (filepath != NULL) strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX - 1); else { LOG_ERROR("DB", "Failed to get file, position was %d", id); goto error; } } else if (code == SQLITE_DONE) { LOG_ERROR("DB", "Failed to get previous"); goto error; } else { LOG_ERROR("DB", "Failed to fetch prev kara: %s", sqlite3_errmsg((sqlite3 *)db)); goto error; } SQLITE_EXEC(db, SQL_UPDATE, error); status = true; error: sqlite3_finalize(stmt); return status; } bool database_queue_swap(lkt_db *db, int from, int to) { static const char *SQL_POP_ROW_1 = "SELECT position, priority, kara_id FROM queue WHERE position = ?;"; static const char *SQL_POP_ROW_2 = "DELETE FROM queue WHERE position = ?;"; static const char *SQL_SLAP_MAX = "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence" " SET seq = (SELECT COUNT(position) FROM queue)" " WHERE name = 'queue';"; static const char *SQL_PUSH_ROW = "INSERT INTO queue (position, priority, kara_id) VALUES (?, ?, ?);"; struct { int position; int priority; int kara_id; } kara_from, kara_to; sqlite3_stmt *stmt = NULL; bool sta = false; int tmp_kara_id; /* Get one kara from the 'queue' and delete it */ #define POP_QUEUE(name) \ SQLITE_PREPARE(db, stmt, SQL_POP_ROW_1, error); \ SQLITE_BIND_INT(db, stmt, 1, name, error); \ SQLITE_STEP_ROW(db, stmt, error); \ kara_##name.position = sqlite3_column_int(stmt, 0); \ kara_##name.priority = sqlite3_column_int(stmt, 1); \ kara_##name.kara_id = sqlite3_column_int(stmt, 2); \ sqlite3_finalize(stmt); \ SQLITE_PREPARE(db, stmt, SQL_POP_ROW_2, error); \ SQLITE_BIND_INT(db, stmt, 1, name, error); \ SQLITE_STEP_DONE(db, stmt, error); \ sqlite3_finalize(stmt); /* Push one kara on the 'queue' */ #define PUSH_QUEUE(name) \ SQLITE_PREPARE(db, stmt, SQL_PUSH_ROW, error); \ SQLITE_BIND_INT(db, stmt, 1, kara_##name.position, error); \ SQLITE_BIND_INT(db, stmt, 2, kara_##name.priority, error); \ SQLITE_BIND_INT(db, stmt, 3, kara_##name.kara_id, error); \ SQLITE_STEP_DONE(db, stmt, error); \ sqlite3_finalize(stmt); SQLITE_EXEC(db, "BEGIN TRANSACTION;", error_no_rollback); POP_QUEUE(from); POP_QUEUE(to); tmp_kara_id = kara_from.kara_id; kara_from.kara_id = kara_to.kara_id; kara_to.kara_id = tmp_kara_id; PUSH_QUEUE(from); PUSH_QUEUE(to); SQLITE_EXEC(db, SQL_SLAP_MAX, error_no_stmt); SQLITE_EXEC(db, "END TRANSACTION;", error_no_stmt); #undef PUSH_QUEUE #undef POP_QUEUE sta = true; goto error_no_stmt; error: sqlite3_finalize(stmt); error_no_stmt: if (!sta) SQLITE_DO_ROLLBACK(db); error_no_rollback: return sta; } bool database_queue_move(lkt_db *db, int from, int to) { static const char *SQL_TEMPLATE_PART_1 = /* Clean queue_tmp */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" /* Construct queue */ "INSERT INTO queue_tmp(kara_id,priority)" "WITH TEMP_TABLE AS (SELECT" " position, kara_id, priority" " FROM queue" " ORDER BY position ASC, priority DESC)" "SELECT kara_id, priority" " FROM TEMP_TABLE WHERE POSITION < %d AND POSITION != %d ORDER BY POSITION;" /* Construct queue */ "INSERT INTO queue_tmp(kara_id,priority)" " VALUES((" " WITH TEMP_TABLE AS (" " SELECT ROW_NUMBER() OVER(ORDER BY position ASC, priority DESC) AS POSITION," " kara_id, priority FROM queue)" " SELECT kara_id FROM TEMP_TABLE WHERE POSITION = %d" " ),(" " WITH TEMP_TABLE AS (" " SELECT ROW_NUMBER() OVER(ORDER BY position ASC, priority DESC) AS POSITION," " kara_id, priority FROM queue)" " SELECT priority FROM TEMP_TABLE WHERE POSITION = " " CASE WHEN (SELECT COUNT(*) FROM TEMP_TABLE)>=%d THEN %d ELSE %d-1 END" " ));"; static const char *SQL_TEMPLATE_PART_2 = /* Construct queue */ "INSERT INTO queue_tmp(kara_id,priority)" "WITH TEMP_TABLE AS (" " SELECT ROW_NUMBER() OVER(ORDER BY position ASC, priority DESC) AS POSITION," " kara_id, priority FROM queue)" "SELECT kara_id, priority" " FROM TEMP_TABLE WHERE POSITION >= %d AND POSITION != %d ORDER BY POSITION;" /* Clean queue and insert back */ "UPDATE queue_state" " SET current = CASE" " WHEN current NOT NULL " " THEN (SELECT position FROM queue_tmp " " WHERE kara_id=(SELECT kara_id FROM queue WHERE position = current))" " ELSE NULL END;" "DELETE FROM queue;" "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';" "INSERT INTO queue(position, kara_id, priority)" " SELECT position, kara_id, priority FROM queue_tmp ORDER BY priority, position;" /* Clean queue_tmp */ "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" "DELETE FROM queue_tmp;"; char SQL[LKT_MAX_SQLITE_STATEMENT]; SQLITE_EXEC(db, "BEGIN TRANSACTION;", error_no_rollback); safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE_PART_1, to, from, from, to, to, to); SQLITE_EXEC(db, SQL, error); safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE_PART_2, to, from); SQLITE_EXEC(db, SQL, error); SQLITE_EXEC(db, "COMMIT;", error); return true; error: SQLITE_DO_ROLLBACK(db); error_no_rollback: return false; } bool database_queue_play(lkt_db *db, int pos) { static const char *SQL_STMT = "UPDATE queue_state SET current = ?, paused = 0" " WHERE current <= 0 OR current IS NULL"; int code = SQLITE_OK; bool ret = false; sqlite3_stmt *stmt = NULL; SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_BIND_INT(db, stmt, 1, pos, error); code = sqlite3_step(stmt); if (code != SQLITE_OK && code != SQLITE_DONE) { LOG_ERROR("DB", "Failed to update queue_state: %s", sqlite3_errmsg((sqlite3 *)db)); goto error; } ret = true; error: sqlite3_finalize(stmt); return ret; } bool database_queue_set_current_index(lkt_db *db, int idx) { static const char *SQL_GET_TEMPLATE = "UPDATE queue_state SET current = %d;"; char SQL_GET[LKT_MAX_SQLITE_STATEMENT]; if (idx <= 0) { LOG_ERROR("DB", "An idx of %d is invalid, must be >= 0", idx); return false; } if (safe_snprintf(SQL_GET, LKT_MAX_SQLITE_STATEMENT, SQL_GET_TEMPLATE, idx) < 0) return false; SQLITE_EXEC(db, SQL_GET, error); return true; error: return false; } bool database_queue_get_current_file(lkt_db *db, char filepath[PATH_MAX]) { static const char *SQL_STMT = "SELECT file_path" " FROM kara" " JOIN queue ON kara.id = queue.kara_id" " JOIN queue_state ON queue.position = queue_state.current"; bool status = false; int code = SQLITE_OK; sqlite3_stmt *stmt = NULL; if (filepath == NULL) goto error; SQLITE_PREPARE(db, stmt, SQL_STMT, error); code = sqlite3_step(stmt); if (code == SQLITE_ROW) { strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX); filepath[PATH_MAX - 1] = '\0'; } else { LOG_ERROR("DB", "Failed to fetch prev kara: %s", sqlite3_errmsg((sqlite3 *)db)); goto error; } status = true; error: sqlite3_finalize(stmt); return status; } bool database_get_kara_path(lkt_db *db, int id, char filepath[PATH_MAX]) { bool ret_code = false; sqlite3_stmt *stmt = NULL; static const char *SQL = "SELECT file_path FROM kara_cache WHERE kara_id = ?;"; RETURN_UNLESS(id && db, "Invalid argument", false); SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_BIND_INT(db, stmt, 1, id, error); SQLITE_STEP_ROW(db, stmt, error); if (filepath) safe_strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX); ret_code = true; error: sqlite3_finalize(stmt); return ret_code; } bool database_get_kara_id(lkt_db *db, char filepath[PATH_MAX], int *id) { bool ret_code = false; sqlite3_stmt *stmt = NULL; static const char *SQL = "SELECT id FROM kara WHERE file_path = ? AND available = 1;"; RETURN_UNLESS(id && db, "Invalid argument", false); SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_BIND_TEXT(db, stmt, 1, filepath, error); SQLITE_STEP_ROW(db, stmt, error); if (id) *id = sqlite3_column_int(stmt, 0); ret_code = true; error: sqlite3_finalize(stmt); return ret_code; } bool database_get_kara_position(lkt_db *db, int id, int *pos) { bool ret_code = false; sqlite3_stmt *stmt = NULL; static const char *SQL = "WITH content AS (" " SELECT kara.id AS id, position" " FROM queue" " JOIN kara ON kara_id = kara.id" " GROUP BY position ORDER BY position ASC, priority DESC)" "SELECT id, position FROM content WHERE id = ?;"; RETURN_UNLESS(db && pos, "Invalid argument", false); SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_BIND_INT(db, stmt, 1, id, error); SQLITE_STEP_ROW(db, stmt, error); *pos = sqlite3_column_int(stmt, 0); ret_code = true; error: sqlite3_finalize(stmt); return ret_code; } int database_queue_probe_id(lkt_db *db, int id) { int position = 0; /* The queue begins with the position 1 */ sqlite3_stmt *stmt = NULL; static const char *SQL = "SELECT position FROM queue WHERE kara_id = ?;"; SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_BIND_INT(db, stmt, 1, id, error); SQLITE_STEP_ROW(db, stmt, error); position = sqlite3_column_int(stmt, 0); error: sqlite3_finalize(stmt); return position; } bool database_queue_shuffle(lkt_db *db) { const char *SQL = "BEGIN TRANSACTION;" /* Create temporary queue */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" /* When current is NULL, that thing is also NULL, so no insertion is done */ "INSERT INTO queue_tmp (kara_id, priority)" " SELECT kara_id, 5" " FROM queue" " JOIN queue_state ON queue.position = queue_state.current;" /* Insert the others karas */ "INSERT INTO queue_tmp (kara_id, priority)" " SELECT kara_id, priority" " FROM queue" " JOIN queue_state ON" " queue.position <> CASE" " WHEN queue_state.current IS NOT NULL THEN queue_state.current" " ELSE 0 END" " ORDER BY RANDOM();" /* Do the insertion in the other way */ "DELETE FROM queue;" "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';" "INSERT INTO queue (position, kara_id, priority)" " SELECT position, kara_id, priority" " FROM queue_tmp" " ORDER BY priority, position;" /* Set the current to the right kara when needed */ "UPDATE queue_state" " SET current = CASE" " WHEN current NOT NULL THEN 1" " ELSE NULL END;" /* Clear the TMP */ "DELETE FROM queue_tmp;" "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" "COMMIT;"; database_queue_flat(db); SQLITE_EXEC(db, SQL, error); return true; error: SQLITE_DO_ROLLBACK(db); return false; } bool database_queue_list(lkt_db *db, size_t from, size_t to, struct lkt_callback *callback) { const char *SQL_STMT = "WITH content AS (" " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len, position, LENGTH(CAST(position AS TEXT)) AS pos_len" " FROM queue" " JOIN kara ON kara_id = kara.id" " WHERE position >= ? AND position <= ?" " GROUP BY position ORDER BY position ASC, priority DESC)" "SELECT id, string, (SELECT MAX(len) FROM content), position, (SELECT MAX(pos_len) FROM content) FROM content;"; int code, id, id_len, pos, pos_len; const char *row; bool ret = false; sqlite3_stmt *stmt; SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_BIND_INT(db, stmt, 1, (int)from, error); SQLITE_BIND_INT(db, stmt, 2, (int)to, error); for (;;) { code = sqlite3_step(stmt); if (code == SQLITE_ROW) { id = sqlite3_column_int(stmt, 0); row = sqlite3_column_chars(stmt, 1); id_len = sqlite3_column_int(stmt, 2); pos = sqlite3_column_int(stmt, 3); pos_len = sqlite3_column_int(stmt, 4); ++(callback->iterations); if (callback->call(callback, pos_len, pos, id, id_len, row)) continue; else break; } else if (code == SQLITE_OK || code == SQLITE_DONE) goto done; else { LOG_ERROR("DB", "Failed: %s", sqlite3_errmsg((sqlite3 *)db)); break; } } done: ret = true; error: sqlite3_finalize(stmt); return ret; } bool database_queue_dump(lkt_db *db, const char *plt_name) { static const char *SQL = "WITH plt_id AS (SELECT playlist.id AS id FROM playlist" " WHERE name COLLATE nocase = ?) " "INSERT OR IGNORE INTO kara_playlist (kara_id, playlist_id)" " SELECT DISTINCT kara_id, plt_id.id" " FROM queue, plt_id" " ORDER BY RANDOM();"; bool ret_code = false; sqlite3_stmt *stmt = NULL; SQLITE_PREPARE(db, stmt, SQL, error); SQLITE_BIND_TEXT(db, stmt, 1, plt_name, error); SQLITE_STEP_DONE(db, stmt, error); ret_code = true; error: sqlite3_finalize(stmt); return ret_code; } bool database_queue_seekid(lkt_db *db, int id, int *out_pos) { static const char *SQL_STMT = "SELECT position FROM queue WHERE kara_id = ? LIMIT 1"; int ret = 0; sqlite3_stmt *stmt; SQLITE_PREPARE(db, stmt, SQL_STMT, error); SQLITE_BIND_INT(db, stmt, 1, id, error); SQLITE_STEP_ROW(db, stmt, error); *out_pos = sqlite3_column_int(stmt, 0); ret = true; error: sqlite3_finalize(stmt); return ret; }