Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • c0b29298e237495f96f7c5ba2a0656cd94e1adc9
  • master par défaut protégée
  • rust-playlist-sync
  • rust
  • fix-qt-deprecated-qvariant-type
  • fix-mpris-qtwindow-race-condition
  • rust-appimage-wayland
  • windows-build-rebased
  • v2.5 protégée
  • v2.4 protégée
  • v2.3-1 protégée
  • v2.3 protégée
  • v2.2 protégée
  • v2.1 protégée
  • v2.0 protégée
  • v1.8-3 protégée
  • v1.8-2 protégée
  • v1.8-1 protégée
  • v1.8 protégée
  • v1.7 protégée
  • v1.6 protégée
  • v1.5 protégée
  • v1.4 protégée
  • v1.3 protégée
  • v1.2 protégée
  • v1.1 protégée
  • v1.0 protégée
27 résultats

lkt.c

Blame
  • lkt.c 35,58 Kio
    #define _POSIX_C_SOURCE 200809L
    
    #include <lektor/common.h>
    #include <lektor/net.h>
    #include <lektor/cmd.h>
    #include <lektor/config.h>
    
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <netdb.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <strings.h>
    #include <unistd.h>
    #include <signal.h>
    #include <locale.h>
    #include <stdnoreturn.h>
    #include <stdbool.h>
    #include <stdarg.h>
    #include <limits.h>
    #include <ctype.h>
    
    #define LKT_KEY_VALUE_SEP               ": \n\t\0"
    #define fail_if(cond, msg)              { if (cond) { LOG_ERROR("LKT", "%s", msg); exit(EXIT_FAILURE); } }
    #define fail(msg)                       { LOG_ERROR("LKT", "%s", msg); exit(EXIT_FAILURE); }
    
    #define exit_with_status(sock, buff) {                \
        read_socket(sock, buff, 2);                       \
        fail_if(buff[0] != 'O' && buff[1] != 'K', "ACK"); \
        exit(EXIT_SUCCESS);                               \
    }
    
    /* Type definition. */
    
    typedef struct {
        const char *host;   /* Serveur host, may be resolved.               */
        const char *port;   /* Serveur port.                                */
        const char *pwd;    /* The password for the user.                   */
        const char **argv;  /* Pointer to the argv from the main function.  */
        int argc;           /* Argument count of the args.                  */
    } args_t;
    
    /* Global variables. */
    
    static const char *host;
    static const char *port;
    static const char *password;
    static const char *LKT_QUEUE_DEFAULT[] = { "10" };
    
    /* Communication functions and fonction that interact with lektor stuff. */
    
    static inline void
    __concat_strings(char *const res, size_t len, const char **tab, size_t count)
    {
        size_t i;
        for (i = 0; i < count - 1; ++i) {
            strncat(res, tab[i], len - 1);
            strncat(res, " ", len - 1);
        }
        strncat(res, tab[i], len - 1);
    }
    
    static int
    lkt_get_query_type(struct cmd_args *args, char *const regex, size_t regex_len)
    {
        if (args->argc == 0) {
            LOG_ERROR("LKT", "Invalid empty query");
            return 1;
        }
    
        /* id, type, lang, category */
        if (args->argc == 1) {
            /* Type ?== id */
            long id = 0;
            errno = 0;
            id = strtol(args->argv[0], NULL, 10);
            if (id != 0 && errno == 0) {
                safe_snprintf(regex, regex_len, "id://%ld", id);
                return 0;
            }
    
            /* Type ?== type OR Type ?== category OR Type ?== lang */
    #define kara_type(tp)                                                   \
    if (STR_MATCH(tp, args->argv[0])) {                                     \
        safe_snprintf(regex, regex_len, "type://%s", args->argv[0]);        \
        return 0;                                                           \
    }
    #define kara_category(ct)                                               \
    if (STR_MATCH(ct, args->argv[0])) {                                     \
        safe_snprintf(regex, regex_len, "category://%s", args->argv[0]);    \
        return 0;                                                           \
    }
    #define kara_language(lg)                                               \
    if (STR_MATCH(lg, args->argv[0])) {                                     \
        safe_snprintf(regex, regex_len, "lang://%s", args->argv[0]);        \
        return 0;                                                           \
    }
    #include <lektor/database.def>
    #undef kara_category
    #undef kara_language
    #undef kara_type
        }
    
        memset(regex, 0, regex_len * sizeof(char));
    
        /* author, playlist/plt */
        if (args->argc >= 2) {
            /* Type ?== author */
            if (STR_MATCH("author", args->argv[0])) {
                strncat(regex, "author://", regex_len - 1);
                __concat_strings(regex, regex_len, args->argv + 1, args->argc - 1);
                return 0;
            }
    
            /* Type ?== playlist */
            else if (STR_MATCH("plt", args->argv[0]) || STR_MATCH("playlist", args->argv[0])) {
                strncat(regex, "playlist://", regex_len - 1);
                __concat_strings(regex, regex_len, args->argv + 1, args->argc - 1);
                return 0;
            }
    
            /* Explicit type for lang, category and type */
    
            /* Type ?== category */
            else if (STR_MATCH("cat", args->argv[0]) || STR_MATCH("category", args->argv[0])) {
                strncat(regex, "category://", regex_len - 1);
                safe_snprintf(regex, regex_len, "category://%s", args->argv[0]);
                return 0;
            }
    
            /* Type ?== lang */
            else if (STR_MATCH("lang", args->argv[0]) || STR_MATCH("language", args->argv[0])) {
                safe_snprintf(regex, regex_len, "lang://%s", args->argv[0]);
                return 0;
            }
    
            /* Type ?== type */
            if (STR_MATCH("type", args->argv[0])) {
                safe_snprintf(regex, regex_len, "type://%s", args->argv[0]);
                return 0;
            }
        }
    
        /* If 'query' is specified, skip it */
        int index = STR_MATCH("query", args->argv[0]) ? 1 : 0;
        strncat(regex, "query://%", regex_len - 1);
        __concat_strings(regex, regex_len, args->argv + index, args->argc - index);
        strncat(regex, "%", regex_len - 1);
        return 0;
    }
    
    static int
    read_socket(FILE *sock, char *buff, size_t max_len)
    {
        size_t i, len;
        for (i = 0; i < max_len; ++i) {
            len = fread(buff + i, sizeof(char), 1, sock);
            if (buff[i] == '\n' || len != 1)
                break;
        }
        fail_if(len == 0, "Connexion error");
        return i;
    }
    
    static FILE *
    create_socket(const char *host, const char *port)
    {
        FILE *socket_desc;
        struct addrinfo hints, *found;
        socklen_t sock_len;
        struct sockaddr sock;
    
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_flags = 0;
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
    
        getaddrinfo(host, port, &hints, &found); //  <---- Another assert yeeted because it makes php panic
    
        sock = *found->ai_addr;
        sock_len = found->ai_addrlen;
        freeaddrinfo(found);
    
        int cx = socket(AF_INET, SOCK_STREAM, 0);
    
        if (cx <= 0 || connect(cx, &sock, sock_len))
            exit(EXIT_FAILURE);
    
        socket_desc = fdopen(cx, "r+");
        fail_if(!socket_desc, "Failed to connect to lektord");
        return socket_desc;
    }
    
    static FILE *
    lkt_connect(void)
    {
        char buff[LKT_MESSAGE_MAX];
        FILE *sock = create_socket(host, port);
        read_socket(sock, buff, LKT_MESSAGE_MAX);
        return sock;
    }
    
    static void
    write_socket(FILE *sock, const char *format, ...)
    {
        int size;
        char buff[LKT_MESSAGE_MAX];
        va_list ap;
    
        va_start(ap, format);
        size = vsnprintf(buff, LKT_MESSAGE_MAX - 1, format, ap);
        buff[LKT_MESSAGE_MAX - 1] = 0;
    
        if (size < 0)
            fail("Connexion error");
    
        if (fwrite(buff, sizeof(char), size, sock) < (unsigned int) size)
            fail("Connexion error");
    
        va_end(ap);
    }
    
    noreturn static inline void
    lkt_send_and_exit(const char *buffer, size_t len)
    {
        write_socket(lkt_connect(), buffer, len);
        exit(EXIT_SUCCESS);
    }
    
    static char *
    lkt_skip_key(char *buffer)
    {
        size_t len = strcspn(buffer, LKT_KEY_VALUE_SEP);
        char *ret = &buffer[len + 1];
        len = strspn(ret, ": ");
        return ret + len;
    }
    
    static inline void
    send_cmd_with_uri(FILE *sock, char *cmd, int argc, const char **argv)
    {
        char buf[LKT_MESSAGE_MAX];
        struct cmd_args args = {
            .argc = argc,
            .argv = argv,
        };
        memset(buf, 0, LKT_MESSAGE_MAX * sizeof(char));
        fail_if(lkt_get_query_type(&args, buf, LKT_MESSAGE_MAX), "Query is invalid");
        write_socket(sock, "%s %s\n", cmd, buf);
    }
    
    /* Functions implementing options. */
    
    #define just_send_one_arg(func, cmd)                \
    noreturn void func (struct cmd_args *args) {        \
        fail_if(args->argc != 1, "Invalid argument");   \
        FILE *sock = lkt_connect();                     \
        char buff[2];                                   \
        write_socket(sock, cmd " %s\n", args->argv[0]); \
        exit_with_status(sock, buff);                   \
    }
    just_send_one_arg(stickers_create__,  "sticker __create")
    just_send_one_arg(stickers_destroy__, "sticker __destroy")
    just_send_one_arg(plt_destroy__,      "rm")
    just_send_one_arg(plt_create__,       "playlistadd")
    just_send_one_arg(queue_dump__,       "__dump")
    #undef just_send_one_arg
    
    #define just_send(func, msg) /* Just send a simple string functions */  \
        noreturn void func (struct cmd_args *args) {                        \
            fail_if(args->argc, "This command takes no arguments");         \
            lkt_send_and_exit(msg, sizeof(msg));                            \
        }
    just_send(queue_clear__, "clear\n")
    just_send(queue_crop__,  "crop\n")
    just_send(next__,        "next\n")
    just_send(prev__,        "previous\n")
    just_send(stop__,        "stop\n")
    just_send(shuffle__,     "shuffle\nplay\n")
    #undef just_send
    
    noreturn void
    simple_send_with_password__(const char *cmd)
    {
        if (!password)
            fail("Password not provided");
        FILE *sock = lkt_connect();
        write_socket(sock, "command_list_begin\n");
        write_socket(sock, "password %s\n", password);
        write_socket(sock, "%s\n", cmd);
        write_socket(sock, "command_list_end\n");
        exit(EXIT_SUCCESS);
    }
    
    noreturn void
    restart__(struct cmd_args *args)
    {
        if (args->argc != 0)
            fail("Invalid argument, the previous command takes no arguments");
        simple_send_with_password__("__restart");
    }
    
    noreturn void
    kill__(struct cmd_args *args)
    {
        if (args->argc != 0)
            fail("Invalid argument, the previous command takes no arguments");
        simple_send_with_password__("kill");
    }
    
    noreturn void
    rescan_or_update__(struct cmd_args *args, const char *cmd)
    {
        if (!password)
            fail("Password not provided");
        FILE *sock = lkt_connect();
    
        /* All the db */
        if (args->argc == 0) {
            write_socket(sock, "command_list_begin\n");
            write_socket(sock, "password %s\n", password);
            write_socket(sock, "%s\n", cmd);
            write_socket(sock, "command_list_end\n");
        }
    
        /* With a query */
        else {
            char buff[LKT_MESSAGE_MAX];
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            fail_if(lkt_get_query_type(args, buff, LKT_MESSAGE_MAX), "Query is not valid");
            write_socket(sock, "command_list_begin\n");
            write_socket(sock, "password %s\n", password);
            write_socket(sock, "%s %s\n", cmd, buff);
            write_socket(sock, "command_list_end\n");
        }
    
        exit(EXIT_SUCCESS);
    }
    
    noreturn void
    rescan__(struct cmd_args *args)
    {
        rescan_or_update__(args, "rescan");
    }
    
    noreturn void
    update__(struct cmd_args *args)
    {
        rescan_or_update__(args, "update");
    }
    
    noreturn void
    populate__(struct cmd_args *args)
    {
        rescan_or_update__(args, "__rescan");
    }
    
    noreturn void
    queue_replace__(struct cmd_args *args)
    {
        if (args->argc != 1)
            fail("Invalid argument");
        bool play = false;
        char buff[LKT_MESSAGE_MAX];
        FILE *sock = lkt_connect();
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            size_t len = strcspn(buff, LKT_KEY_VALUE_SEP);
    
            if (STR_NMATCH(buff, "state", len)) {
                char *it = lkt_skip_key(buff);
                play = STR_NMATCH(it, "play", 4);
                break;
            }
    
            if (STR_NMATCH(buff, "OK", 2))
                break;
            else if (STR_NMATCH(buff, "ACK", 3))
                fail("ACK");
        }
    
        fclose(sock);
        sock = lkt_connect();
        write_socket(sock, "command_list_begin\n");
        write_socket(sock, "clear\n");
        write_socket(sock, "add playlist://%s\n", args->argv[0]);
        if (play)
            write_socket(sock, "play\n");
        write_socket(sock, "command_list_end\n");
        exit_with_status(sock, buff);
    }
    
    noreturn void
    config__(struct cmd_args *args)
    {
        int ret = EXIT_SUCCESS;
        if (args->argc == 0)
            fwrite(lkt_default_config_file, sizeof(char),
                   strlen(lkt_default_config_file), stdout);
        exit(ret);
    }
    
    noreturn void
    play__(struct cmd_args *args)
    {
        if (args->argc > 1)
            fail("Invalid argument");
    
        int pos = 0;
        if (args->argc != 0)
            pos = atoi(args->argv[0]);
    
        static const char status__[]        = "status\n";
        static const char cmd_play__[]      = "play\n";
        static const char cmd_play_from__[] = "play %d\n";
        static const char cmd_pause__[]     = "pause\n";
    
        char buff[LKT_MESSAGE_MAX];
        FILE *sock = lkt_connect();
        write_socket(sock, status__);
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            size_t len = strcspn(buff, LKT_KEY_VALUE_SEP);
    
            if (STR_NMATCH(buff, "state", len)) {
                fclose(sock);
    
                if (STR_NMATCH(lkt_skip_key(buff), "stop", 4)) {
                    if (!pos)
                        lkt_send_and_exit(cmd_play__, sizeof(cmd_play__));  /* In bytes. */
                    else {
                        write_socket(lkt_connect(), cmd_play_from__, pos);
                        exit(EXIT_SUCCESS);
                    }
                }
    
                else
                    lkt_send_and_exit(cmd_pause__, sizeof(cmd_pause__));    /* In bytes. */
    
                exit(EXIT_FAILURE);
            }
        }
    }
    
    noreturn void
    ping__(struct cmd_args *args)
    {
        if (args->argc != 0)
            fail("Invalid argument, the ping command takes no arguments");
        char buff[6] = {0};
        FILE *sock = lkt_connect();
        write_socket(sock, "ping\n");
        read_socket(sock, buff, 6 * sizeof(char));
        if (!STR_NMATCH(buff, "OK", 2))
            fail("ACK");
        exit(write(1, "OK\n", sizeof("OK\n")) == 0);
    }
    
    noreturn void
    current__(struct cmd_args *args)
    {
        if (args->argc != 0)
            fail("Invalid argument, the current command takes no arguments");
    
        static const char current_song__[] = "currentsong\n";
        char buff[LKT_MESSAGE_MAX];
    
        char *mem = NULL;
        FILE *sock = lkt_connect();
    
        write_socket(sock, current_song__);
        assert(mem = calloc(6 * LKT_MESSAGE_MAX, sizeof(char)));
        assert(memset(mem, 0, 6 * LKT_MESSAGE_MAX * sizeof(char)));
    
        char *const title    = &mem[0];
        char *const author   = &mem[LKT_MESSAGE_MAX];
        char *const source   = &mem[2 * LKT_MESSAGE_MAX];
        char *const type     = &mem[3 * LKT_MESSAGE_MAX];
        char *const category = &mem[4 * LKT_MESSAGE_MAX];
        char *const language = &mem[5 * LKT_MESSAGE_MAX];
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
    
            const size_t len = strcspn(buff, LKT_KEY_VALUE_SEP);
            char *value = lkt_skip_key(buff);
            const size_t value_size = strcspn(value, "\n\0") * sizeof(char);
    
            if (STR_NMATCH("title", buff, len))
                assert(memcpy(title, value, value_size));
    
            if (STR_NMATCH("author", buff, len))
                assert(memcpy(author, value, value_size));
    
            if (STR_NMATCH("source", buff, len))
                assert(memcpy(source, value, value_size));
    
            if (STR_NMATCH("type", buff, len))
                assert(memcpy(type, value, value_size));
    
            if (STR_NMATCH("language", buff, len))
                assert(memcpy(language, value, value_size));
    
            if (STR_NMATCH("category", buff, len))
                assert(memcpy(category, value, value_size));
    
            /* At this point every key has been parsed. */
            if (STR_NMATCH(buff, "OK", 2))
                goto ok;
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
        }
    
    ok:
        if (language[0])
            printf("%s - %s / %s - %s - %s [ %s ]\n",
                   category, language, source, type, title, author);
        else
            printf("%s / %s - %s - %s [ %s ]\n",
                   category, source, type, title, author);
    
        exit(EXIT_SUCCESS);
    }
    
    noreturn void
    queue_pop__(struct cmd_args *args)
    {
        fail_if(args->argc, "Invalid argument");
        int songid = 0;
        char buff[LKT_MESSAGE_MAX];
        FILE *sock = lkt_connect();
    
        /* Get lektor's status. */
        write_socket(sock, "status\n");
    
    #define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (atoi(lkt_skip_key(buff))); continue; }
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            size_t len = strcspn(buff, LKT_KEY_VALUE_SEP);
            assign_int("songid", songid)
    
            /* At this point every key has been parsed. */
            if (STR_NMATCH(buff, "OK", 2))
                break;
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
        }
    #undef assign_int
    
        fclose(sock);
        sock = lkt_connect();
        if (!songid)
            exit(EXIT_FAILURE);
        write_socket(sock, "next\ndeleteid %d\n", songid);
        exit_with_status(sock, buff);
    }
    
    noreturn void
    status__(struct cmd_args *args)
    {
        if (args->argc != 0)
            fail("Invalid argument, the status command takes no arguments");
    
        static const char *const status_str__ = "status\n";
        static const char *const stats_str__  = "stats\n";
    
        char buff[LKT_MESSAGE_MAX];
        char flags[24];
    
        bool play = false, stopped = true, is_updating = false;
        int time_pos = 0, time_duration = 0, song_index = 0, plt_len = 0, ret = EXIT_FAILURE, it = 0;
        size_t len;
        time_t update_ts = 0;
    
        memset(flags, 0, 24 * sizeof(char));
        FILE *sock = lkt_connect();
    
    #define assign_flag(str, f)  if (STR_NMATCH(buff, str, len) && (atoi(lkt_skip_key(buff)) == 1)) { flags[it++] = f; continue; }
    #define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (strtoll(lkt_skip_key(buff), NULL, 0)); continue; }
    #define check_end()                                     \
        if (STR_NMATCH(buff, "OK", 2)) break;               \
        else if (STR_NMATCH(buff, "ACK", 3)) goto error;
    
        /* Get lektor's status */
        write_socket(sock, status_str__);
        for (;;) {
            len       = read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            buff[len] = '\0';
            len       = strcspn(buff, LKT_KEY_VALUE_SEP);
    
            if (STR_NMATCH(buff, "state", len)) {
                char *it = lkt_skip_key(buff);
                play     = STR_NMATCH(it, "play", 4);
                stopped  = STR_NMATCH(it, "stop", 4);
                continue;
            }
    
            assign_flag("random",  'r')
            assign_flag("repeat",  'l')
            assign_flag("single",  's')
            assign_flag("consume", 'c')
    
            assign_int("elapsed",        time_pos)
            assign_int("duration",       time_duration)
            assign_int("song",           song_index)
            assign_int("playlistlength", plt_len)
    
            check_end()
        }
    
        /* Get lektor's stats */
        write_socket(sock, stats_str__);
        for (;;) {
            len       = read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            buff[len] = '\0';
            len       = strcspn(buff, LKT_KEY_VALUE_SEP);
    
            assign_int("__is_updating", is_updating)
            assign_int("db_update",     update_ts)
    
            check_end()
        }
    
        /* End of communication */
        fclose(sock);
    
    #undef assign_flag
    #undef assign_int
    #undef check_end
    
        struct tm *p_tm = localtime(&update_ts);
        len = strftime(buff, LKT_MESSAGE_MAX - 1, "%F %H:%M:%S", p_tm);
        buff[len] = '\0';
    
        int  pla_m = time_pos / 60;
        char pla_s = time_pos % 60;
        int  dur_m = time_duration / 60;
        char dur_s = time_duration % 60;
    
        printf("Lektor: %s\n"
               "Queue: #%d/%d\n"
               "Playback: %d:%02d/%d:%02d\n"
               "Flags: %s\n"
               "%s: %s\n",
               play ? "play" : "stopped",
               song_index + 1, plt_len,
               pla_m, pla_s, dur_m, dur_s,
               flags[0] ? flags : "(none)",
               is_updating ? "Updating" : "Last update", buff);
    
        /* If there is a kara loaded in mpv, get this kara. */
        if (!stopped) {
            printf("Kara: ");
            current__(args);
        }
    
        ret = EXIT_SUCCESS;
    error:
        exit(ret);
    }
    
    noreturn void
    queue_delete__(struct cmd_args *args)
    {
        if (args->argc != 1)
            fail("Invalid argument, need onlt one argument: queue delete <id>");
    
        static const char *cmd_id__ = "deleteid %d\n";
        int dumy = 0;
        FILE *sock = lkt_connect();
        char buff[3];
    
        sscanf(args->argv[0], "%d", &dumy);
        if (dumy != 0) {
            write_socket(sock, cmd_id__, dumy);
            exit_with_status(sock, buff);
        }
    
        fail("Invalid argument");
    }
    
    noreturn void
    queue_add__(struct cmd_args *args)
    {
        char buff[3];
        fail_if(args->argc < 1, "Invalid arguments");
        FILE *sock = lkt_connect();
        send_cmd_with_uri(sock, "add", args->argc, args->argv);
        exit_with_status(sock, buff);
    }
    
    noreturn void
    queue_insert__(struct cmd_args *args)
    {
        char buff[3];
        fail_if(args->argc < 1, "Invalid arguments");
        FILE *sock = lkt_connect();
        send_cmd_with_uri(sock, "__insert", args->argc, args->argv);
        exit_with_status(sock, buff);
    }
    
    noreturn void
    queue_seek__(struct cmd_args *args)
    {
        fail_if(args->argc != 1, "The seek command needs one argument: queue seek <id>");
    
        char *endptr, buf[3];
        long id = strtol(args->argv[0], &endptr, 0);
    
        if ((errno == ERANGE && (id == LONG_MAX || id == LONG_MIN)) ||
            (errno != 0 && id == 0)                                 ||
            (endptr == args->argv[0]))
            fail("Invalid argument, not an integer");
    
        if (*endptr != '\0')
            fail("Invalid argument, must be only one integer");
    
        FILE *sock = lkt_connect();
        write_socket(sock, "playid %ld\n", id);
        exit_with_status(sock, buf);
    }
    
    noreturn void
    queue_pos__(struct cmd_args *args)
    {
        char buff[LKT_MESSAGE_MAX], *endptr;
    
        if (args->argc != 1)
            fail("Invalid argument for the pos command: queue pos <arg> where arg is a position or a range");
    
        long continuation = 0, up = 0;
    
        continuation = strtol(args->argv[0], &endptr, 0);
        if ((errno == ERANGE && (continuation == LONG_MAX || continuation == LONG_MIN)) || (errno != 0
                && continuation == 0) || (endptr == args->argv[0]))
            fail("Invalid argument, not an integer: queue pos <arg> where arg is a position or a range");
    
        if (*endptr != '\0') {
            /* A range */
            if (*(++endptr) == '\0')
                fail("Invalid argument, a range is two integers");
            up = atoi(endptr);
        }
    
    
        FILE *sock = NULL;
    redo:
        sock = lkt_connect();
        if (up != 0)
            write_socket(sock, "playlist %d:%d\n", continuation, up);
        else
            write_socket(sock, "playlist %d\n", continuation);
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
    
            if (STR_NMATCH(buff, "continue:", strlen("continue:"))) {
                continuation = atoi(lkt_skip_key(buff));
                if (continuation > 0) {
                    fclose(sock);
                    goto redo;
                }
            }
    
            if (STR_NMATCH(buff, "OK", 2))
                exit(EXIT_SUCCESS);
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
    
            fprintf(stdout, "%s", buff);
        }
    }
    
    noreturn void
    queue_list__(struct cmd_args *args)
    {
        char buff[LKT_MESSAGE_MAX], *endptr;
        FILE *sock = NULL;
        long continuation = 0, song_index = 1;
    
        /* Arguments stuff. */
    
        if (args->argc == 0)
            args->argv = LKT_QUEUE_DEFAULT;
        else if (args->argc != 1)
            fail("Invalid argument: queue <count?>");
    
        continuation = strtol(args->argv[0], &endptr, 0);
        if ((errno == ERANGE && (continuation == LONG_MAX || continuation == LONG_MIN)) || (errno != 0
                && continuation == 0) || (endptr == args->argv[0]))
            fail("Invalid argument, not an integer");
    
        if (*endptr != '\0')
            fail("Invalid argument");
    
        /* Get the current pos to get limits for the playlist command. */
    
        sock = lkt_connect();
        write_socket(sock, "status\n");
    
    #define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (atoi(lkt_skip_key(buff))); continue; }
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            size_t len = strcspn(buff, LKT_KEY_VALUE_SEP);
            assign_int("song", song_index);
    
            /* At this point every key has been parsed. */
            if (STR_NMATCH(buff, "OK", 2))
                break;
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
        }
    #undef assign_int
        fclose(sock);
    
        /* Get the content of the queue. */
    
        continuation = labs(continuation);
        song_index = MAX(song_index + 1, 1);
        safe_snprintf(buff, LKT_MESSAGE_MAX, "%ld:%ld", song_index, song_index + continuation - 1);
        args->argc = 1;
        args->argv[0] = buff;
        queue_pos__(args);
    }
    
    /* Functions implementing options, but for for playlists. */
    
    noreturn void
    plt_add__(struct cmd_args *args)
    {
        char buff[2];
        fail_if(args->argc < 2,
                "Invalid argument, need at least three arguments: plt add <plt> <query>");
    
        char regex[LKT_MESSAGE_MAX];
        struct cmd_args args_regex = {
            .argc = args->argc - 1,
            .argv = args->argv + 1,
        };
    
        fail_if(lkt_get_query_type(&args_regex, regex, LKT_MESSAGE_MAX), "Query is invalid");
        FILE *sock = lkt_connect();
        write_socket(sock, "playlistadd %s %s\n", args->argv[0], regex);
        exit_with_status(sock, buff);
    }
    
    noreturn void
    plt_delete__(struct cmd_args *args)
    {
        FILE *sock = lkt_connect();
        char buff[2];
        char cmd[128];
        fail_if(args->argc < 3, "Invalid argument");
        snprintf(cmd, 128 - 1, "playlistdelete %s", args->argv[0]);
        cmd[127] = '\0';
        send_cmd_with_uri(sock, cmd, args->argc - 1, &args->argv[1]);
        exit_with_status(sock, buff);
    }
    
    noreturn void
    stickers_get__(struct cmd_args *args)
    {
        char buff[LKT_MESSAGE_MAX];
        FILE *sock;
        if (args->argc == 2)
            write_socket(sock = lkt_connect(), "sticker get %s %s\n", args->argv[0], args->argv[1]);
        else if (args->argc == 3)
            write_socket(sock = lkt_connect(), "sticker get %s %s %s\n", args->argv[0], args->argv[1],
                         args->argv[2]);
        else if (args->argc == 5) {
            const char *op = args->argv[3];
            op = op[0] == 'e' ? "=" : op[0] == 'l' ? "<" : op[0] == 'g' ? ">" : NULL;
            fail_if(!op, "Invalid argument");
            write_socket(sock = lkt_connect(), "sticker get %s %s %s %s %s\n", args->argv[0],
                         args->argv[1], args->argv[2], op, args->argv[4]);
        } else
            fail("Invalid argument");
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            if (STR_NMATCH(buff, "OK", 2))
                exit(EXIT_SUCCESS);
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
            fprintf(stdout, "%s", buff);
        }
    }
    
    noreturn void
    stickers_set__(struct cmd_args *args)
    {
        fail_if(args->argc != 4, "Invalid argument");
        FILE *sock = lkt_connect();
        char buff[2];
        write_socket(sock, "sticker set %s %s %s %s\n", args->argv[0],
                     args->argv[1], args->argv[2], args->argv[3]);
        exit_with_status(sock, buff);
    }
    
    noreturn void
    stickers_delete__(struct cmd_args *args)
    {
        FILE *sock;
        char buff[2];
        if (args->argc == 2)
            write_socket(sock = lkt_connect(), "sticker delete %s %s",
                         args->argv[0], args->argv[1]);
        else if (args->argc == 3)
            write_socket(sock = lkt_connect(), "sticker delete %s %s %s",
                         args->argv[0], args->argv[1], args->argv[2]);
        else
            fail("Invalid argument");
        exit_with_status(sock, buff);
    }
    
    /* Search functions. */
    
    noreturn void
    __continuation_calls(const char *cmd)
    {
        char buff[LKT_MESSAGE_MAX];
        int continuation = 0;
        FILE *sock = NULL;
    redo:
        sock = lkt_connect();
    
        write_socket(sock, "%d %s\n", continuation, cmd);
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
    
            if (STR_NMATCH(buff, "continue:", strlen("continue:"))) {
                continuation = atoi(lkt_skip_key(buff));
                fclose(sock);
                goto redo;
            }
    
            if (STR_NMATCH(buff, "OK", 2))
                exit(EXIT_SUCCESS);
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
    
            fprintf(stdout, "%s", buff);
        }
    }
    
    noreturn void
    list_plt__(struct cmd_args *args)
    {
        fail_if(args->argc != 0, "Invalid argument number");
        __continuation_calls("listplaylists");
    }
    
    noreturn void
    list_plt_content__(struct cmd_args *args)
    {
        fail_if(args->argc != 1, "Invalid argument number");
        char buff[LKT_MESSAGE_MAX];
        safe_snprintf(buff, LKT_MESSAGE_MAX, "listplaylist %s", args->argv[0]);
        __continuation_calls(buff);
        exit(EXIT_FAILURE);
    }
    
    noreturn void
    search_with_cmd__(struct cmd_args *args, const char *cmd)
    {
        char buff[LKT_MESSAGE_MAX];
        char regex[LKT_MESSAGE_MAX];
        int continuation = 0;
        FILE *sock = NULL;
    
        fail_if(lkt_get_query_type(args, regex, LKT_MESSAGE_MAX), "Query is invalid");
    
    redo:
        sock = lkt_connect();
        write_socket(sock, "%d %s %s\n", continuation, cmd, regex);
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
    
            if (STR_NMATCH(buff, "continue:", strlen("continue:"))) {
                continuation = atoi(lkt_skip_key(buff));
                fclose(sock);
                goto redo;
            }
    
            if (STR_NMATCH(buff, "OK", 2))
                exit(EXIT_SUCCESS);
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
    
            fprintf(stdout, "%s", buff);
        }
    }
    
    noreturn void
    search_get__(struct cmd_args *args)
    {
        fail_if(args->argc != 1, "Invalid number of arguments");
        fail_if(!strtol(args->argv[0], NULL, 0), "Invalid id");
        char buff[LKT_MESSAGE_MAX];
        FILE *sock = lkt_connect();
    
        write_socket(sock, "find %s\n", args->argv[0]);
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
            if (STR_NMATCH(buff, "OK", 2))
                exit(EXIT_SUCCESS);
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
            (void) write(1, buff, strlen(buff));
        }
    }
    
    noreturn void
    search_plt__(struct cmd_args *args)
    {
        if (args->argc == 0)
            list_plt__(args);
        if (args->argc == 1)
            list_plt_content__(args);
    
        char buff[LKT_MESSAGE_MAX];
        char regex[LKT_MESSAGE_MAX];
        int continuation = 0;
        FILE *sock = NULL;
        struct cmd_args args_regex = {
            .argc = args->argc - 1,
            .argv = args->argv + 1,
        };
    
        fail_if(args->argc < 2, "Invalid number of arguments");
        fail_if(lkt_get_query_type(&args_regex, regex, LKT_MESSAGE_MAX), "Query is invalid");
    redo:
        sock = lkt_connect();
        write_socket(sock, "%d listplaylistinfo %s %s\n", continuation, args->argv[0], regex);
    
        for (;;) {
            memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
            read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
    
            if (STR_NMATCH(buff, "continue:", strlen("continue:"))) {
                continuation = atoi(lkt_skip_key(buff));
                fclose(sock);
                goto redo;
            }
    
            if (STR_NMATCH(buff, "OK", 2))
                exit(EXIT_SUCCESS);
            else if (STR_NMATCH(buff, "ACK", 3))
                exit(EXIT_FAILURE);
    
            fprintf(stdout, "%s", buff);
        }
    }
    
    #define search_with_cmd(func, cmd) /* I don't want to write always the same things */ \
        noreturn void func (struct cmd_args *args) { search_with_cmd__(args, #cmd); }
    search_with_cmd(search_db__,     search)
    search_with_cmd(search_count__,  count)
    search_with_cmd(search_queue__,  playlistfind)
    #undef search_with_cmd
    
    /* Parsing stuff. */
    
    static struct cmd_opt options_queue[] = {
        { .name = "insert",     .call = queue_insert__  },
        { .name = "pos",        .call = queue_pos__     },
        { .name = "pop",        .call = queue_pop__     },
        { .name = "add",        .call = queue_add__     },
        { .name = "seek",       .call = queue_seek__    },
        { .name = "delete",     .call = queue_delete__  },
        { .name = "clear",      .call = queue_clear__   },
        { .name = "crop",       .call = queue_crop__    },
        { .name = "replace",    .call = queue_replace__ },
        { .name = "dump",       .call = queue_dump__    },
        CMD_OPT_DEFAULT(queue_list__),
    };
    
    static struct cmd_opt options_plt[] = {
        { .name = "add",        .call = plt_add__     },
        { .name = "delete",     .call = plt_delete__  },
        { .name = "destroy",    .call = plt_destroy__ },
        { .name = "create",     .call = plt_create__  },
        { .name = "list",       .call = search_plt__  },
        CMD_OPT_NULL,
    };
    
    static struct cmd_opt options_search[] = {
        { .name = "database",   .call = search_db__     },
        { .name = "get",        .call = search_get__    },
        { .name = "plt",        .call = search_plt__    },
        { .name = "count",      .call = search_count__  },
        { .name = "queue",      .call = search_queue__  },
        CMD_OPT_NULL,
    };
    
    static struct cmd_opt options_admin[] = {
        { .name = "ping",       .call = ping__     },
        { .name = "kill",       .call = kill__     },
        { .name = "restart",    .call = restart__  },
        { .name = "rescan",     .call = rescan__   },
        { .name = "update",     .call = update__   },
        { .name = "populate",   .call = populate__ },
        { .name = "config",     .call = config__   },
        CMD_OPT_NULL,
    };
    
    static struct cmd_opt options_stickers[] = {
        { .name = "get",        .call = stickers_get__      },
        { .name = "set",        .call = stickers_set__      },
        { .name = "delete",     .call = stickers_delete__   },
        { .name = "create",     .call = stickers_create__   },
        { .name = "destroy",    .call = stickers_destroy__  },
        CMD_OPT_NULL,
    };
    
    #define sub_command(name) /* Create sub-commands here */                            \
        noreturn void name ## __(struct cmd_args *args) {                               \
            fail_if(!args->argc, "Invalid command, specify a sub command for " #name);  \
            cmd_parse(options_ ## name, args->argc, args->argv);                        \
        }
    sub_command(stickers)
    sub_command(search)
    sub_command(plt)
    sub_command(admin)
    
    noreturn void
    queue__(struct cmd_args *args)
    {
        if (args->argc == 0)
            queue_list__(args);
        cmd_parse(options_queue, args->argc, args->argv);
    }
    #undef sub_command
    
    static struct cmd_opt options_[] = {
        { .name = "current",  .call = current__  },
        { .name = "play",     .call = play__     },
        { .name = "next",     .call = next__     },
        { .name = "previous", .call = prev__     },
        { .name = "queue",    .call = queue__    },
        { .name = "shuffle",  .call = shuffle__  },
        { .name = "status",   .call = status__   },
        { .name = "stop",     .call = stop__     },
        { .name = "plt",      .call = plt__      },
        { .name = "search",   .call = search__   },
        { .name = "admin",    .call = admin__    },
        { .name = "stickers", .call = stickers__ },
        CMD_OPT_NULL,
    };
    
    /* The sigpipe function, if SIGPIPE signal is sent. */
    
    static void
    sigpipe__(int sig)
    {
        LOG_ERROR("GENERAL", "Exit because of signal sigpipe (%d)", sig);
        exit(EXIT_FAILURE);
    }
    
    /* Functions declarations. */
    
    static void
    parse_args(args_t *args, int argc, const char **argv)
    {
        int i, got = 0;
        size_t len;
    
        for (i = 1; i < argc && i < 3; ++i) {
            len = strcspn(argv[i], "=:");
    
            if (STR_NMATCH("host", argv[i], len)) {
                args->host = (argv[i] + len + 1);
                ++got;
            }
    
            else if (STR_NMATCH("port", argv[i], len)) {
                args->port = (argv[i] + len + 1);
                ++got;
            }
    
            else if (STR_NMATCH("pwd", argv[i], len)) {
                args->pwd = (argv[i] + len + 1);
                ++got;
            }
        }
    
        args->argv = &argv[got + 1];
        args->argc = argc - (got + 1);
    }
    
    int
    main(int argc, const char **argv)
    {
        __log_level = ERROR;
        executable_name = "lkt";
        assert(NULL != setlocale(LC_ALL, "en_US.UTF-8")); /* BECAUSE! */
        if (signal(SIGPIPE, sigpipe__))
            LOG_ERROR("SYS", "Failed to install handler for SIGPIPE signal (you may be using php...)");
    
        args_t args = {
            .host = "localhost",
            .port = "6600",
        };
    
        parse_args(&args, argc, argv);
    
        host     = args.host;
        port     = args.port;
        password = args.pwd;
    
        /* Communication with lektor. */
        cmd_parse(options_, args.argc, args.argv);
    }