diff --git a/inc/lektor/database.h b/inc/lektor/database.h index e26b78f14089117b8bc4c83e49ecf6e248bbf5a9..475206c5e695c6419e26f7077487e4d6236edb19 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -30,7 +30,7 @@ bool database_detach(sqlite3 *db, const char *name); /* Get information on the queue and currently playing kara. */ bool database_queue_state(sqlite3 *db, struct lkt_queue_state *res); -bool database_queue_current_kara(sqlite3 *db, struct kara_metadata *res); +bool database_queue_current_kara(sqlite3 *db, struct kara_metadata *res, int *id); bool database_queue_set_paused(sqlite3 *db, bool paused); bool database_queue_set_current_index(sqlite3 *db, int idx); diff --git a/inc/lektor/macro.h b/inc/lektor/macro.h index 3ac78e56235110244f9fe2ee620bb38c82e95e04..5403ef8ce96f0ebf4efc9bc18e87c0aa8b056eec 100644 --- a/inc/lektor/macro.h +++ b/inc/lektor/macro.h @@ -28,6 +28,9 @@ goto error; \ } +#define SQLITE_DO_ROLLBACK(db) \ + sqlite3_exec(db, "ROLLBACK TRANSACTION;\n", NULL, NULL, NULL); + #ifndef MAX #define MAX(a, b) ((a) < (b) ? (b) : (a)) #endif /* MAX */ diff --git a/src/commands.c b/src/commands.c index 2282c9bb041d216aabab8ceaf51ea43681a0bb48..1c5254629204d094134de368286e49adac14bd12 100644 --- a/src/commands.c +++ b/src/commands.c @@ -98,7 +98,7 @@ command_currentsong(struct lkt_state *srv, size_t c) memset(&kara, 0, sizeof(struct kara_metadata)); - if (!database_queue_current_kara(srv->db, &kara)) + if (!database_queue_current_kara(srv->db, &kara, NULL)) fprintf(stderr, " ! command_currentsong: Failed to get information about the current kara\n"); out = lkt_message_new(); @@ -365,59 +365,13 @@ command_crop(sqlite3 *db, enum mpd_idle_flag *watch_mask_ptr) return database_queue_crop(db); } -bool -command_del(sqlite3 *db, struct lkt_win *win, char *pos_range, enum mpd_idle_flag *watch_mask_ptr) -{ - (void) win; - long pos_lowwer, pos_upper = -1, i; - char *endptr; - bool ret = true; - - if (pos_range == NULL) { - fprintf(stderr, " ! command_del: NULL argument\n"); - return false; - } - - errno = 0; - *watch_mask_ptr |= MPD_IDLE_PLAYLIST; - pos_lowwer = strtol(pos_range, &endptr, 10); - - if ((errno == ERANGE && (pos_lowwer == LONG_MAX || pos_lowwer == LONG_MIN)) - || (errno != 0 && pos_lowwer == 0)) { - fprintf(stderr, " ! command_del: strtol failed: %s\n", strerror(errno)); - return false; - } - - // Second digit -> a range - if (endptr == pos_range) { - fprintf(stderr, " . command_del: to digit found in string '%s'\n", pos_range); - pos_range = endptr + strcspn(endptr, "0123456789"); - - pos_upper = strtol(pos_range, &endptr, 10); - - if ((errno == ERANGE && (pos_upper == LONG_MAX || pos_upper == LONG_MIN)) - || (errno != 0 && pos_upper == 0)) { - fprintf(stderr, " ! command_del: strtol failed: %s\n", strerror(errno)); - return false; - } - } - - if (pos_lowwer >= pos_upper) - return database_queue_del_pos(db, pos_lowwer); - else { - for (i = pos_lowwer; i <= pos_upper; ++i) - ret &= database_queue_del_pos(db, i); - - return ret; - } -} - bool command_delid(sqlite3 *db, struct lkt_win *win, char *id_str, enum mpd_idle_flag *watch_mask_ptr) { (void) win; long id; char *endptr; + int uri = 0; errno = 0; *watch_mask_ptr |= MPD_IDLE_PLAYLIST; @@ -433,6 +387,15 @@ command_delid(sqlite3 *db, struct lkt_win *win, char *id_str, enum mpd_idle_flag return false; } + /* If one day we allow suppression of the current kara, will need the `win` + pointer to reload the kara in the same position (but the kara won't be + the same). */ + database_queue_current_kara(db, NULL, &uri); + if (id == (long) uri) { + fprintf(stderr, " . command_delid: Can't delete the currently playing kara\n"); + return false; + } + return database_queue_del_id(db, id); } diff --git a/src/database/queue.c b/src/database/queue.c index 6a486ae466cdb51658d2e1ad9c190460962507f8..1ec0e4292ae8c8821db2cfc6cddfdbd6335fbf54 100644 --- a/src/database/queue.c +++ b/src/database/queue.c @@ -53,20 +53,23 @@ error: } bool -database_queue_current_kara(sqlite3 *db, struct kara_metadata *res) +database_queue_current_kara(sqlite3 *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" + "SELECT song_name, source_name, category, language, author_name, song_type, song_number, kara_id" " FROM kara" - " JOIN queue ON queue.kara_id = kara.id" - " JOIN queue_state ON queue_state.current = queue.position"; + " JOIN queue_ ON kara_id = kara.id" + " JOIN queue_state ON current = position"; sqlite3_stmt *stmt = 0; int ret = false; SQLITE_PREPARE(db, stmt, SQL_STMT, error); - memset(res, 0, sizeof(struct kara_metadata)); if (sqlite3_step(stmt) == SQLITE_ROW) { + /* Here use gotos because of optimisations done by compilators. + Most of the time it won't be NULL. */ + if (!res) + goto no_metadata; strncpy(res->song_name, (const char *) sqlite3_column_text(stmt, 0), LEKTOR_TAG_MAX - 1); strncpy(res->source_name, (const char *) sqlite3_column_text(stmt, 1), LEKTOR_TAG_MAX - 1); strncpy(res->category, (const char *) sqlite3_column_text(stmt, 2), LEKTOR_TAG_MAX - 1); @@ -74,6 +77,10 @@ database_queue_current_kara(sqlite3 *db, struct kara_metadata *res) strncpy(res->author_name, (const char *) sqlite3_column_text(stmt, 4), LEKTOR_TAG_MAX - 1); strncpy(res->song_type, (const char *) sqlite3_column_text(stmt, 5), LEKTOR_TAG_MAX - 1); res->song_number = sqlite3_column_int(stmt, 6); +no_metadata: + /* Most of the time this will be NULL. */ + if (id) + *id = sqlite3_column_int(stmt, 7); } else { fprintf(stderr, " ! database_queue_current_kara: failed: %s\n", sqlite3_errmsg(db)); @@ -303,40 +310,25 @@ error: return status; } -static bool -queue_rm_with_sql(sqlite3 *db, const char *sql, int arg) -{ - sqlite3_stmt *stmt = 0; - bool res = sqlite3_prepare_v2(db, sql, -1, &stmt, 0) == SQLITE_OK && - sqlite3_bind_int(stmt, 1, arg) == SQLITE_OK && - sqlite3_step(stmt) == SQLITE_DONE; - - if (!res) - fprintf(stderr, " ! del_kara_with_sql: Failed to delete kara: %s\n", - sqlite3_errmsg(db)); - - sqlite3_finalize(stmt); - return res; -} - bool database_queue_del_id(sqlite3 *db, int id) { - static const char *SQL_DEL_KARA = "DELETE FROM queue WHERE kara_id = ?"; - return queue_rm_with_sql(db, SQL_DEL_KARA, id); -} - -bool -database_queue_del_pos(sqlite3 *db, int pos) -{ - static const char *SQL_DEL_POS = - "DELETE FROM queue" - " WHERE position = " - " ( SELECT old_position" - " FROM queue_" - " WHERE position = ?" - " );"; - return queue_rm_with_sql(db, SQL_DEL_POS, pos); + static const char *SQL_TEMPLATE = + "BEGIN TRANSACTION;" + "WITH before(pos) AS (SELECT position FROM queue_ JOIN queue_state WHERE position < current ORDER BY position DESC LIMIT 1) " + "UPDATE queue_state SET current = CASE" + " WHEN current IS NULL THEN NULL" + " ELSE (SELECT pos FROM before) " + "END WHERE current > (SELECT position FROM queue_ WHERE kara_id = %d);" + "DELETE FROM queue WHERE kara_id = %d;" + "COMMIT TRANSACTION;"; + char SQL[LKT_MAX_SQLITE_STATEMENT]; + snprintf(SQL, LKT_MAX_SQLITE_STATEMENT - 1, SQL_TEMPLATE, id, id, id); + SQL[LKT_MAX_SQLITE_STATEMENT - 1] = '\0'; + SQLITE_EXEC(db, SQL, error); + return true; +error: + return false; } bool @@ -578,8 +570,8 @@ database_queue_get_current_file(sqlite3 *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"; + " 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; diff --git a/src/database/stickers.c b/src/database/stickers.c index 4b8661b800f249bdf5fc9fd3cb7c80552c1a7b23..afeb6209b62dd438d7a9cc57e8e1cae23e7fc5b4 100644 --- a/src/database/stickers.c +++ b/src/database/stickers.c @@ -245,7 +245,7 @@ database_sticker_delete_specify(sqlite3 *db, const char *type, int uri, const ch } snprintf(SQL, LKT_MAX_SQLITE_STATEMENT - 1, "DELETE FROM 'stickers.%s' " - "WHERE 'stickers.%s' = ? ", type, type); + "WHERE 'stickers.%s' = ? ", type, type); SQL[LKT_MAX_SQLITE_STATEMENT - 1] = 0; /* If there is a name specified. */ diff --git a/src/main/lkt.c b/src/main/lkt.c index c64c68282eae6ae333d951cde7f817a6a71b9ef2..d28fb1a93bfa1d9447b2cc41cf08b1a1b780c16d 100644 --- a/src/main/lkt.c +++ b/src/main/lkt.c @@ -21,6 +21,7 @@ #include <assert.h> #include <stdbool.h> #include <stdarg.h> +#include <limits.h> #define LKT_KEY_VALUE_SEP ": \n\t\0" #define STR_MATCH(str1, str2) (! strcasecmp(str1, str2)) @@ -47,6 +48,7 @@ help(void) " status: get the status of lektor.\n" " current: get the currently playing song.\n" " add <query>: add a kara to the playlist with a query.\n" + " delete <id>: delete the id from the queue.\n" " clear: clear the queue of lektor.\n" " prev: play previous kara in the queue.\n" " next: play the next kara in the queue.\n" @@ -113,12 +115,16 @@ static int lkt_valid_type(const char *type) { return (STR_MATCH(type, "all") || + STR_MATCH(type, "any") || STR_MATCH(type, "a") || + STR_MATCH(type, "id") || + STR_MATCH(type, "title") || STR_MATCH(type, "type") || STR_MATCH(type, "cat") || STR_MATCH(type, "category") || STR_MATCH(type, "author") || STR_MATCH(type, "auth") || + STR_MATCH(type, "source") || STR_MATCH(type, "lang") || STR_MATCH(type, "language")); } @@ -475,6 +481,33 @@ shuffle__(struct lkt_cmd_args *args) lkt_send_and_exit(shuffle_str__, strlen(shuffle_str__)); } +noreturn void +delete__(struct lkt_cmd_args *args) +{ + if (args->argc != 1) + fail("Invalid argument, need onlt one argument"); + + static const char *cmd_id__ = "deleteid %d\nclose\n"; + int dumy = 0; + FILE *sock = lkt_connect(); + char buff[3]; + + sscanf(args->argv[0], "%d", &dumy); + if (dumy != 0) { + write_socket_format(sock, cmd_id__, dumy); + goto check; + } + + fail("Invalid argument"); + +check: + assert(read_socket(sock, buff, 2) > 0); + if (buff[0] == 'O' && buff[1] == 'K') + exit(EXIT_SUCCESS); + else + exit(EXIT_FAILURE); +} + noreturn void add__(struct lkt_cmd_args *args) { @@ -486,7 +519,7 @@ add__(struct lkt_cmd_args *args) fail("Invalid argument, the add command should takes at least two arguments"); if (!lkt_valid_type(args->argv[0])) - fail("Invalid argument, the type given to the add command is invalid\n"); + fail("Invalid argument, the type given to the add command is invalid"); FILE *sock = lkt_connect(); @@ -681,6 +714,7 @@ static struct lkt_cmd_opt options_[] = { { .name = "clear", .call = clear__ }, { .name = "current", .call = current__ }, { .name = "play", .call = play__ }, + { .name = "delete", .call = delete__ }, { .name = "next", .call = next__ }, { .name = "previous", .call = prev__ }, { .name = "queue", .call = list__ }, diff --git a/src/net/listen.c b/src/net/listen.c index d42e68ffd5973791b9665e9c2bcf73282570daa4..58b2743261ccb31c37f0f414f2e9868858abd834 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -208,12 +208,9 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) err = !command_add(srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); else if (!strcmp(cmd.name, "addid")) err = !command_addid(srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); - else if (!strcmp(cmd.name, "delete")) - err = ! (cmd.args[0] != NULL && - command_del(srv->db, &srv->win, cmd.args[0], &srv->mpd_idle_events)); else if (!strcmp(cmd.name, "deleteid")) err = ! (cmd.args[0] != NULL && - command_del(srv->db, &srv->win, cmd.args[0], &srv->mpd_idle_events)); + command_delid(srv->db, &srv->win, cmd.args[0], &srv->mpd_idle_events)); else if (!strcmp(cmd.name, "playlistclear")) err = ! command_plt_clear(srv->db, cmd.args, &srv->mpd_idle_events);