diff --git a/README.md b/README.md index 3f3f16deb69bf9ac0466d0edd252334907c54160..51dcdb1274c83198d4be6852f7aa94561f57b2d3 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ For the compatibility column, the possible values are the following: | `playlist` | `playlist` | + | | | `playlistfind {tag} {needle}` | `playlistfind {uri}` | ~ | uses lektord URIs | | `playlistid {songid}` | | TODO | not implemented | -| `playlistinfo [[songpos]/[start:end]]` | `playlistinfo` | - | is an alias to `playlist` | +| `playlistinfo [[songpos]/[start:end]]` | `playlistinfo` | - | is an alias to `playlist` | | `playlistsearch {taf} {needle}` | `playlistsearch {uri}` | ~ | uses lektord URIs | | `plchanges {version} [start:end]` | | | not implemented | | `plchangesposid {version} [start:end]` | | | not implemented | @@ -226,7 +226,7 @@ For the compatibility column, the possible values are the following: | `prioid {prio} {id...}` | | | not implemented | | `rangeid {id} {start:end}` | | | not implemented | | `shuffle [start:end]` | `shuffle` | ~ | shuffles all the queue | -| `swap {song1} {song2}` | | TODO | not implemented | +| `swap {song1} {song2}` | `swap {song1} {song2}` | + | | | `swap {songid1} {songid2}` | | | not implemented | | `listplaylist {name}` | `listplaylist {name}` | + | | | `listplaylistinfo {name}` | `listplaylistinfo {name} {uri}` | - | do a search in the playlist | diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index a519b81ac1f5877eec5afda11c606174437ea617..7fc6240f78405acfb545b862820d136f715726c3 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -43,6 +43,7 @@ bool command_crop (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_move (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_shuffle(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_dump (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); +bool command_swap (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_queue_list(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]); diff --git a/inc/lektor/database.h b/inc/lektor/database.h index 61bea9e27167ce20dbfa65f886daf334144a8978..86f8672c9288ccd8012cae4cf6306abfc91b144c 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -65,6 +65,7 @@ bool database_queue_del_pos(volatile sqlite3 *db, int pos); bool database_queue_clear (volatile sqlite3 *db); bool database_queue_crop (volatile sqlite3 *db); bool database_queue_move (volatile sqlite3 *db, int from, int to); +bool database_queue_swap (volatile sqlite3 *db, int from, int to); bool database_queue_shuffle(volatile sqlite3 *db); bool database_queue_seekid (volatile sqlite3 *db, int id, int *out_pos); bool database_queue_dump (volatile sqlite3 *db, const char *plt_name); diff --git a/src/base/commands.c b/src/base/commands.c index 4c6f4afa6c6e43f2a7fc39aa718d34082351fea8..97fc71db1f8c134bd4142c76ae030c25a4e0e752 100644 --- a/src/base/commands.c +++ b/src/base/commands.c @@ -430,7 +430,7 @@ command_delid(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]) } bool -command_move(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]) +command_move(struct lkt_state UNUSED *srv, char *args[LKT_MESSAGE_ARGS_MAX]) { long from, to; char *endptr, err; @@ -445,8 +445,51 @@ command_move(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]) STRTOL(to, args[1], endptr, err); RETURN_IF(err, "STRTOL failed", false); + /* srv->mpd_idle_events |= MPD_IDLE_PLAYLIST; return database_queue_move(srv->db, from, to); + */ + LOG_FATAL("Unimplemented"); + return false; +} + +bool +command_swap(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]) +{ + long from, to; + char *endptr = NULL, err = 0; + + RETURN_UNLESS(args && args[0] && args[1], "Invalid argument", false); + + /* First argument: from */ + STRTOL(from, args[0], endptr, err); + RETURN_IF(err, "STRTOL failed", false); + + /* Second argument: to */ + STRTOL(to, args[1], endptr, err); + RETURN_IF(err, "STRTOL failed", false); + + struct lkt_queue_state queue = { .current = -1 }; + database_queue_state(srv->db, &queue); + + + int new_current = (((long) queue.current) == to) ? from + : (((long) queue.current) == from) ? to + : -1; + + srv->mpd_idle_events |= MPD_IDLE_PLAYLIST; + bool sta = database_queue_swap(srv->db, from, to); + + if (sta && (new_current >= 0) && !database_queue_set_current_index(srv->db, new_current)) { + LOG_ERROR("COMMAND", "Failed to set current index to new location"); + if (!database_queue_swap(srv->db, from, to)) { + LOG_ERROR("COMMAND", "Reswap failed, uncertain state! Stopping the playback"); + command_stop(srv, NULL); + } + return false; + } + + return sta; } bool diff --git a/src/database/queue.c b/src/database/queue.c index 69cd03651617e411a5812cd0da58382941d47cc3..d961fbaa80533e9d3b563f3ba75ff3870d3f6e0b 100644 --- a/src/database/queue.c +++ b/src/database/queue.c @@ -336,7 +336,7 @@ database_queue_del_pos(volatile sqlite3 *db, int pos) SQLITE_BIND_INT(db, stmt, 1, pos, failed_in_delete_before); SQLITE_STEP_DONE(db, stmt, failed_in_delete_before); sta = true; -failed_in_delete_before: + failed_in_delete_before: sqlite3_finalize(stmt); if (!sta) SQLITE_DO_ROLLBACK(db); @@ -350,7 +350,7 @@ failed_in_delete_before: SQLITE_BIND_INT(db, stmt, 1, pos, failed_in_delete_after); SQLITE_STEP_DONE(db, stmt, failed_in_delete_after); sta = true; -failed_in_delete_after: + failed_in_delete_after: sqlite3_finalize(stmt); return sta; } @@ -481,11 +481,78 @@ error: return status; } +bool +database_queue_swap(volatile sqlite3 *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 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; +error: + sqlite3_finalize(stmt); +error_no_stmt: + SQLITE_DO_ROLLBACK(db); +error_no_rollback: + return sta; +} + bool database_queue_move(volatile sqlite3 *db, int from, int to) { /* TODO: See if there is a solution with less lines of code */ - static const char *SQL_TEMPLATE_toINFfrom = /* Clean queue_tmp */ "DELETE FROM queue_tmp;" @@ -644,8 +711,7 @@ error: bool database_queue_set_current_index(volatile sqlite3 *db, int idx) { - static const char *SQL_GET_TEMPLATE = - "UPDATE queue_state SET current = %d;"; + static const char *SQL_GET_TEMPLATE = "UPDATE queue_state SET current = %d;"; char SQL_GET[LKT_MAX_SQLITE_STATEMENT]; if (idx <= 0) { diff --git a/src/net/listen.c b/src/net/listen.c index bd2b98112bac6aec052d8045bcb68a55448a76ea..8ff1d10ea829c0b69a5e4bf7513ca0fb1f22d859 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -973,7 +973,7 @@ handle_signals(struct lkt_state *srv) void lkt_listen(struct lkt_state *srv) { - srv->fds_max = 16; + srv->fds_max = 16; /* FIXME: Get this value from [server.max_clients] */ srv->fds = safe_zero_malloc(srv->fds_max * sizeof(struct pollfd)); srv->clients = safe_zero_malloc(srv->fds_max * sizeof(struct lkt_client)); @@ -994,6 +994,11 @@ lkt_listen(struct lkt_state *srv) char perms_s[6] = {0}; int perms = 0600; + /* FIXME: Use the server register, and add a socket type (which is + * unix/tcp) and place the 'socket name' in a [server.socket] and not + * [server.host]. On the config, the type 'unix/tcp' will result in a + * function call 'create_unix', 'create_tcp' as [server.use] or something + * line that. */ if (is_unix_socket) { if (!database_config_get(srv->db, "server", "permission", perms_s, 6)) LOG_WARN("INIT", "Failed to get server socket permission, using default 0%o", perms);