Sélectionner une révision Git
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);
}