From 6580aa33a304fc61f5039806efeeb990d18f2e6f Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Sat, 30 Jan 2021 21:23:00 +0100
Subject: [PATCH] MPD: Add the "swap" command

---
 README.md             |  4 +--
 inc/lektor/commands.h |  1 +
 inc/lektor/database.h |  1 +
 src/base/commands.c   | 45 ++++++++++++++++++++++++-
 src/database/queue.c  | 76 ++++++++++++++++++++++++++++++++++++++++---
 src/net/listen.c      |  7 +++-
 6 files changed, 125 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index 3f3f16de..51dcdb12 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 a519b81a..7fc6240f 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 61bea9e2..86f8672c 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 4c6f4afa..97fc71db 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 69cd0365..d961fbaa 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 bd2b9811..8ff1d10e 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);
-- 
GitLab