Sélectionner une révision Git
commands.c 42,52 Kio
#include <lektor/common.h>
#include <lektor/commands.h>
#include <lektor/database.h>
#include <lektor/net.h>
#include <lektor/uri.h>
#include <lektor/cmd.h>
#include <lektor/stb/ds.h>
#include <poll.h>
bool
command_restart(struct lkt_state *srv, size_t c, char UNUSED *__argv[LKT_MESSAGE_ARGS_MAX])
{
const char *const argv[] = { cmd_get_executable_name(), NULL };
struct lkt_queue_state sta;
memset(&sta, 0, sizeof(struct lkt_queue_state));
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
if (!argv[0]) {
LOG_ERROR("GENERAL", "Can't restart if the executable path was not found at start-up");
return false;
}
database_queue_state(srv->db, &sta);
env_set(LKT_ENV_RESTART, "1");
size_t len = (size_t)long_length(LONG_MAX);
if (len > 0) {
char *pos = LKT_ALLOC_ARRAY(char, len);
safe_snprintf(pos, len, "%d", sta.current);
env_set(LKT_ENV_CURRENT, pos);
free(pos);
} else
env_set(LKT_ENV_CURRENT, "NULL");
lkt_close_server(srv);
execv(argv[0], (char *const *)argv);
LOG_ERROR("GENERAL", "Failed to exec lektor or OS not supported");
exit(EXIT_FAILURE);
}
bool
command_update(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
srv->mpd_idle_events |= MPD_IDLE_UPDATE;
srv->mpd_idle_events |= MPD_IDLE_DATABASE;
struct lkt_uri *user_uri = lkt_uri_new();
if (argv[0] != NULL)
lkt_uri_from_list(user_uri, argv);
const bool ret = !MOD_CALL(srv->repo_mod, "update", user_uri);
lkt_uri_free(user_uri);
return ret;
}
bool
command_dry_update(struct lkt_state *srv, size_t c, char UNUSED *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
srv->mpd_idle_events |= MPD_IDLE_UPDATE;
srv->mpd_idle_events |= MPD_IDLE_DATABASE;
return !MOD_PROC(srv->repo_mod, "dry-update");
}
bool
command_import(struct lkt_state *srv, size_t c, char UNUSED *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
srv->mpd_idle_events |= MPD_IDLE_UPDATE;
srv->mpd_idle_events |= MPD_IDLE_DATABASE;
return !MOD_PROC(srv->repo_mod, "import");
}
bool
command_rescan(struct lkt_state *srv, size_t c, char UNUSED *argv[LKT_MESSAGE_ARGS_MAX],
int UNUSED forced)
{
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
srv->mpd_idle_events |= MPD_IDLE_UPDATE;
srv->mpd_idle_events |= MPD_IDLE_DATABASE;
return !MOD_PROC(srv->repo_mod, "rescan");
}
bool
command_kill(struct lkt_state *srv, size_t c, char UNUSED *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
srv->___signal_INT(srv);
LOG_FATAL("The SIGINT handler returned, now exiting");
}
bool
command_config(struct lkt_state *srv, size_t c, char UNUSED *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
struct lkt_message *out;
char db_file[LKT_LINE_MAX];
memset(db_file, 0, sizeof(db_file));
database_config_get_text(srv->db, "database", "db_path", db_file, LKT_LINE_MAX);
out = lkt_message_new_fmt("max_clients: %ld\n"
"db_file: %s\n"
"music_directory: %s\n",
srv->fds_max, db_file, srv->kara_prefix);
lkt_state_send(srv, c, out);
return true;
}
bool
command_currentsong(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
struct lkt_message *out;
struct kara_metadata kara;
memset(&kara, 0, sizeof(struct kara_metadata));
if (!database_queue_current_kara(srv->db, &kara, NULL))
LOG_ERROR("COMMAND", "Failed to get information about the current kara");
out = lkt_message_new_fmt("Title: %s\n"
"Author: %s\n"
"Source: %s\n"
"Type: %s%d\n"
"Category: %s\n"
"Language: %s\n",
kara.song_name, kara.author_name, kara.source_name, kara.song_type,
kara.song_number, kara.category, kara.language);
lkt_state_send(srv, c, out);
return true;
}
bool
command_stats(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
int artists = 0, albums = 0, songs = 0;
long ts_update = 0;
uint64_t playtime = 0;
uint64_t queue_playtime = 0;
database_get_update(srv->db, &ts_update, NULL, NULL);
database_stats(srv->db, &artists, &albums, &songs);
database_total_playtime(srv->db, &playtime);
database_queue_playtime(srv->db, &queue_playtime);
struct lkt_message *out =
lkt_message_new_fmt("__is_updating: %d\n" /* Custom field here */
"__update_progress: %ld:%ld\n"
"db_playtime: %ld\n"
"playtime: %ld\n"
"db_update: %ld\n"
"uptime: %ld\n"
"artists: %d\n" /* Number of authors */
"albums: %d\n" /* Number of source names */
"songs: %d\n", /* Number of songs */
srv->is_updating, srv->update_current, srv->update_total, playtime,
queue_playtime, (uint64_t)ts_update,
(uint64_t)(time(NULL) - srv->start_date), artists, albums, songs);
lkt_state_send(srv, c, out);
return true;
}
bool
command_status(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
struct lkt_queue_state queue_state;
int elapsed, duration, songid = 0;
long update_job;
const char *play_state;
RETURN_UNLESS(srv, "Invalid argument", false);
RETURN_UNLESS(database_queue_state(srv->db, &queue_state), "Can't get playback status", false);
database_queue_current_kara(srv->db, NULL, &songid);
MOD_CALL(srv->window_mod, "get_elapsed", &elapsed);
MOD_CALL(srv->window_mod, "get_duration", &duration);
database_get_update(srv->db, NULL, &update_job, NULL);
play_state = queue_state.current <= 0 ? "stop" : (queue_state.paused ? "pause" : "play");
struct lkt_message *out =
lkt_message_new_fmt("volume: %d\n"
"repeat: %d\n"
"random: %d\n"
"single: %d\n"
"consume: %d\n"
"state: %s\n"
"song: %d\n"
"playlistlength: %d\n"
"elapsed: %d\n"
"duration: %d\n"
"updating_db: %d\n"
"songid: %d\n",
queue_state.volume, queue_state.repeat, queue_state.random,
queue_state.single, queue_state.consume, play_state,
queue_state.current < 0 ? -1 : queue_state.current - 1,
queue_state.length, elapsed, duration, update_job, songid);
lkt_state_send(srv, c, out);
return true;
}
bool
command_next(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
char filepath[PATH_MAX];
if (!database_queue_next(srv->db, filepath)) {
LOG_DEBUG("COMMAND", "Failed to get next, stop the player for end of queue");
command_stop(srv, c, NULL);
return false;
}
if (MOD_CALL(srv->window_mod, "load", filepath))
return false;
else {
config_handle_hook(srv->db, "kara_load");
LOG_INFO("COMMAND", "Loaded next kara %s", filepath);
return true;
}
}
bool
command_pause(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
/* The simple toggle version */
if ((!args) || (args && !args[0])) {
toggle_anyway:
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
RETURN_UNLESS(database_queue_toggle_pause(srv->db), "Failed to toggle pause in db", false);
return !MOD_PROC(srv->window_mod, "toggle");
}
/* Set the 'pause' state */
else {
long state = strtol(args[0], NULL, 0);
struct lkt_queue_state queue;
LOG_DEBUG("COMMAND", "Forcing the paused state to %ld", state);
GOTO_UNLESS(database_queue_state(srv->db, &queue),
"Failed to get queue state, try to toggle pause state anyway", toggle_anyway);
/* Pause := 0 | 1
* current := (kara_id > 0) | (NULL --> -1) */
/* Play! */
if (queue.current < 0) {
LOG_DEBUG("COMMAND", "Not playing anything, force play from begening of the queue");
return command_play(srv, c, NULL);
}
/* Set play/unpause or the pause */
else if (queue.paused != state) {
RETURN_UNLESS(database_queue_set_paused(srv->db, state), "Oupsi, can't get queue state",
false);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
return !MOD_PROC(srv->window_mod, "toggle");
}
/* Nop */
else {
LOG_DEBUG("COMMAND", "Playback was already %s", state ? "paused" : "unpaused");
return true;
}
}
}
bool
command_previous(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
char filepath[PATH_MAX];
if (!database_queue_prev(srv->db, filepath))
return false;
if (MOD_CALL(srv->window_mod, "load", filepath))
return false;
else {
config_handle_hook(srv->db, "kara_load");
LOG_INFO("COMMAND", "Loaded previous kara %s", filepath);
return true;
}
}
PRIVATE_FUNCTION bool
__play_that_file(struct lkt_state *win, int pos)
{
char filepath[PATH_MAX];
RETURN_UNLESS(pos, "Invalid argument", false);
RETURN_UNLESS(database_queue_play(win->db, pos), "DB error", false);
RETURN_UNLESS(database_queue_get_current_file(win->db, filepath), "Can't get current kara",
false);
RETURN_IF(MOD_CALL(win->window_mod, "load", filepath), "Can't load file", false);
RETURN_IF(MOD_CALL(win->window_mod, "set_paused", 0), "Can't unset pause", false);
LOG_DEBUG("COMMAND", "Playing queue at index %d, file %s", pos, filepath);
return true;
}
bool
command_play(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
char *endptr = NULL, err;
long pos = 1;
struct lkt_queue_state queue_state;
/* Argument handle. */
if (args && args[0]) {
STRTOL(pos, args[0], endptr, err);
RETURN_IF(err, "STRTOL failed", false);
}
/* If position is after the queue, don't do anything */
RETURN_UNLESS(database_queue_state(srv->db, &queue_state), "Can't get playback status", false);
if (queue_state.length < pos) {
LOG_INFO("COMMAND",
"Don't perform play command, queue is empty or position is after queue length");
return false;
}
/* Do the actual job here. */
database_queue_stop(srv->db);
RETURN_IF(MOD_CALL(srv->window_mod, "new", &srv->queue, srv->db), "Can't create window", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
/* Hooks */
if (__play_that_file(srv, (int)pos)) {
config_handle_hook(srv->db, "queue_begin");
config_handle_hook(srv->db, "kara_load");
return true;
} else
return false;
}
bool
command_dump(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args[0], "Invalid argument", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
return database_queue_dump(srv->db, args[0]);
}
bool
command_playid(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
char *endptr = NULL;
int pos = 0;
char err;
long id;
/* Argument handle. */
RETURN_IF(args[0] == NULL, "Invalid argument", false);
STRTOL(id, args[0], endptr, err);
RETURN_IF(err, "STRTOL failed", false);
/* Do the work. */
database_queue_stop(srv->db);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
RETURN_IF(MOD_CALL(srv->window_mod, "new", &srv->queue, srv->db), "Can't create window", false);
database_queue_seekid(srv->db, (int)id, &pos);
/* Hooks */
if (__play_that_file(srv, pos)) {
config_handle_hook(srv->db, "queue_begin");
config_handle_hook(srv->db, "kara_load");
return true;
} else
return false;
}
bool
command_stop(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(database_queue_stop(srv->db), "DB error on stop", false);
MOD_PROC(srv->window_mod, "close");
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
return true;
}
bool
command_add(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX], int priority)
{
RETURN_UNLESS(args && args[0], "Invalid argument", false);
struct lkt_uri *uri = lkt_uri_new();
RETURN_UNLESS(lkt_uri_from(uri, args), "Failed to parse query", false);
bool ret = database_queue_add_uri(srv->db, uri, priority);
lkt_uri_free(uri);
if (!ret)
LOG_ERROR("COMMAND", "Failed to add with priority %d in queue", priority);
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return ret;
}
bool
command_addid(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args, "Invalid argument", false);
bool error = false;
size_t *ids = NULL;
/* Only one ID */
if (args[0] && (args[1] == NULL)) {
long id = strtol(args[0], NULL, 0);
if (id <= INT_MIN || id >= INT_MAX) {
LOG_ERROR("COMMAND", "Tried to add a kara with an ID to big! Id was: %zu", id);
goto end_of_function; /* Use goto here to have a single return statement */
}
error = database_queue_add_id(srv->db, (int)id, 1);
}
/* One ID or more */
else {
FOR_EACH_ARGUMENT (i, args) {
long id = strtol(args[i], NULL, 0);
if (id < 0) {
LOG_ERROR("COMMAND", "Tried to add a kara with a negative ID! Id was: %z", id);
goto end_of_function;
}
arrput(ids, (size_t)id);
}
LOG_DEBUG("COMMAND", "Add a total of %zu IDs to the queue", arrlen(ids));
error = database_queue_multi_add_id(srv->db, ids, 1);
}
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
end_of_function:
arrfree(ids); /* Handles the `ids == NULL` */
return error;
}
bool
command_clear(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return command_stop(srv, c, args) && database_queue_clear(srv->db);
}
bool
command_crop(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return database_queue_crop(srv->db);
}
bool
command_flat(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return database_queue_flat(srv->db);
}
PRIVATE_FUNCTION bool
skip_current_kara__(struct lkt_state *srv)
{
char filepath[PATH_MAX];
if (database_queue_skip_current(srv->db, filepath)) {
if (MOD_CALL(srv->window_mod, "load", filepath)) {
LOG_ERROR("COMMAND", "Failed to skip current kara (loading %s)", filepath);
return false;
}
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
}
else {
LOG_WARN("COMMAND", "Failed to skip current kara, stop playback");
MOD_PROC(srv->window_mod, "close");
return false;
}
LOG_DEBUG("COMMAND", "Skiped current kara and loaded %s instead", filepath);
return true;
}
bool
command_del(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
struct lkt_queue_state queue = { .current = -2 /* Really need this field only */ };
long pos = -1;
char *endptr = NULL, err = 0;
RETURN_UNLESS(args && args[0], "No position provided", false);
STRTOL(pos, args[0], endptr, err);
RETURN_IF(err, "STRTOL failed", false);
database_queue_state(srv->db, &queue); /* At worst, -1 != -2 */
RETURN_IF(pos == (long)queue.current && !skip_current_kara__(srv),
"Failed to skip current kara", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return database_queue_del_pos(srv->db, (int)pos);
}
bool
command_delid(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
long id;
char *endptr = NULL, err = 0;
int uri = 0;
RETURN_UNLESS(args && args[0], "No id provided", false);
STRTOL(id, args[0], endptr, err);
RETURN_IF(err, "STRTOL failed", false);
database_queue_current_kara(srv->db, NULL, &uri);
RETURN_IF(id == (long)uri && !skip_current_kara__(srv), "Failed to skip current kara", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return database_queue_del_id(srv->db, (int)id);
}
bool
command_move(struct lkt_state UNUSED *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
long from, to;
char *endptr = NULL, err;
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);
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return database_queue_move(srv->db, (int)from, (int)to);
}
static bool
__swap_positions(struct lkt_state *srv, int from, int to)
{
RETURN_IF(to == from, "Can't swap with two identic positions", 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, 0, NULL);
}
return false;
}
return sta;
}
bool
command_swapid(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0] && args[1], "Invalid argument", false);
int from = database_queue_probe_id(srv->db, (int)strtol(args[0], NULL, 0));
int to = database_queue_probe_id(srv->db, (int)strtol(args[1], NULL, 0));
return __swap_positions(srv, from, to);
}
bool
command_swap(struct lkt_state *srv, UNUSED size_t c, 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);
return __swap_positions(srv, (int)from, (int)to);
}
bool
command_help(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
lkt_state_send(srv, c, lkt_message_new_fmt("HELP\n"));
return true;
}
bool
command_idle(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
bool once;
size_t i;
for (once = false, i = 0; args[i]; ++i, once |= true) {
MPD_IDLE_FLAG idle_mask =
(STR_MATCH(args[i], "database") * MPD_IDLE_DATABASE) +
(STR_MATCH(args[i], "update") * MPD_IDLE_UPDATE) +
(STR_MATCH(args[i], "stored_playlist") * MPD_IDLE_STORED_PLAYLIST) +
(STR_MATCH(args[i], "playlist") * MPD_IDLE_PLAYLIST) +
(STR_MATCH(args[i], "player") * MPD_IDLE_PLAYER) +
(STR_MATCH(args[i], "mixer") * MPD_IDLE_MIXER) +
(STR_MATCH(args[i], "output") * MPD_IDLE_OUTPUT) +
(STR_MATCH(args[i], "options") * MPD_IDLE_OPTIONS) +
(STR_MATCH(args[i], "partition") * MPD_IDLE_PARTITION) +
(STR_MATCH(args[i], "sticker") * MPD_IDLE_STICKER) +
(STR_MATCH(args[i], "subscription") * MPD_IDLE_SUBSCRIPTION) +
(STR_MATCH(args[i], "message") * MPD_IDLE_MESSAGE);
idle_mask = idle_mask + ((unsigned int)(!(bool)(idle_mask)) * MPD_IDLE_ALL);
lkt_client_add_mask(srv, c, idle_mask);
}
if (!once)
lkt_client_add_mask(srv, c, MPD_IDLE_ALL);
LOG_INFO("COMMAND", "Idle mask for client number %ld is 0x%X", c, lkt_client_get_mask(srv, c));
return true;
}
bool
command_noidle(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
lkt_client_clear_mask(srv, c);
return true;
}
/* Functions for the searchadd and the search mpd commands */
PRIVATE_FUNCTION bool
lkt_callback_send_row_v1(void *_args, int pos_len, int pos, int id, int id_len, const char *sql_row)
{
struct lkt_callback *args = (struct lkt_callback *)_args;
struct lkt_message *out =
lkt_message_new_fmt("%*d: %*d %s\n", pos_len, pos, id_len, id, sql_row);
lkt_state_send(args->srv, args->c, out);
return true;
}
PRIVATE_FUNCTION bool
lkt_callback_send_row_v2(struct lkt_state *srv, size_t c, int id, int id_len, const char *sql_row)
{
lkt_state_send(srv, c, lkt_message_new_fmt("%*d %s\n", id_len, id, sql_row));
return true;
}
PRIVATE_FUNCTION bool
lkt_callback_send_list_plts(struct lkt_state *srv, size_t c, const char *plt_name)
{
/* If the playlist is named OK or ACK... */
lkt_state_send(srv, c, lkt_message_new_fmt("name: %s\n", plt_name));
return true;
}
static bool
command_findid(struct lkt_state *srv, size_t c, char *id_str)
{
uint64_t kara_duration;
struct kara_metadata kara;
char filepath[PATH_MAX] = { 0 };
long id = strtol(id_str, NULL, 0); /* XXX: Must be valid here */
int s = 0, h = 0, m = 0;
memset(&kara, 0, sizeof(kara));
if (!database_kara_by_id(srv->db, (int)id, &kara, filepath)) {
LOG_ERROR("COMMAND", "Can't get a kara with id %ld", id);
return false;
}
if (!database_get_kara_mtime_id(srv->db, (int)id, &kara_duration)) {
LOG_WARN("COMMAND", "Can't get duration for kara %ld", id);
kara_duration = 0;
}
if (kara_duration < (uint64_t)INT_MAX) {
s = (int)kara_duration;
h = s / 3600;
s = s % 3600;
m = s / 60;
s = s % 60;
}
static const char *send_format = "Title: %s\n"
"Author: %s\n"
"Source: %s\n"
"Type: %s%d\n"
"Category: %s\n"
"Language: %s\n"
"Duration: %d:%02d:%02d\n";
struct lkt_message *out;
out = lkt_message_new_fmt(send_format, kara.song_name, kara.author_name, kara.source_name,
kara.song_type, kara.song_number, kara.category, kara.language, h, m,
s);
lkt_state_send(srv, c, out);
return true;
}
PRIVATE_FUNCTION bool
iter_search__(struct lkt_search *search)
{
size_t count;
for (count = 0; database_search_iter(search); ++count)
continue;
if (count) {
struct lkt_state *srv = database_search_get_srv(search);
size_t client = database_search_get_client(search);
size_t continuation = database_search_get_continuation(search);
lkt_set_continuation(srv, client, (int)(continuation + count));
}
else
LOG_WARN("COMMAND", "Nothing found");
return true;
}
bool
command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], long continuation,
database_search_init_func init)
{
RETURN_UNLESS(args[0], "Invalid argument", false);
/* Just an id */
long count;
if (!args[1] && (count = strtol(args[0], NULL, 0)))
return command_findid(srv, c, args[0]);
/* With an URI */
struct lkt_uri *find_uri = lkt_uri_new();
struct lkt_search *search =
database_search_new(srv, c, (int)continuation, FUNCTION_POINTER(lkt_callback_send_row_v2));
database_search_set_name(search, args[0]);
database_search_set_uri(search, find_uri);
if (!lkt_uri_from(find_uri, args)) {
/* Try from idx 1, in case of playlust searches */
LOG_DEBUG("COMMAND",
"URI may not starts at idx 0, may be because of "
"playlist search. At idx 0, value was '%s'",
args[0]);
unless (lkt_uri_from(find_uri, &args[1])) {
lkt_uri_free(find_uri);
LOG_ERROR("COMMAND", "Failed to create the uri");
return false;
}
}
unless (init(srv->db, search)) {
LOG_ERROR("COMMAND", "Failed to init the search structure");
lkt_uri_free(find_uri);
return false;
}
const bool ret = iter_search__(search);
lkt_uri_free(find_uri);
return ret;
}
bool
command_plt_list(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX], int cont)
{
struct lkt_search *search =
database_search_new(srv, c, cont, FUNCTION_POINTER(lkt_callback_send_list_plts));
RETURN_UNLESS(database_search_listplaylist_init(srv->db, search), "Failed to init search",
false);
return iter_search__(search);
}
bool
command_plt_ctx(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], int cont)
{
struct lkt_uri *null_uri = lkt_uri_new();
struct lkt_search *search =
database_search_new(srv, c, cont, FUNCTION_POINTER(lkt_callback_send_row_v2));
database_search_set_uri(search, null_uri);
database_search_set_name(search, args[0]);
RETURN_UNLESS(database_search_playlist_init(srv->db, search), "Failed to init search", false);
const bool ret = iter_search__(search);
lkt_uri_free(null_uri);
return ret;
}
bool
command_set_playback_option(struct lkt_state *srv, size_t UNUSED c, LKT_PLAYBACK_OPTION opt,
char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(srv, "Invalid argument", false);
long val;
char *endptr = NULL;
bool ret = false;
if (args == NULL || args[0] == NULL)
val = 0;
else {
STRTOL(val, args[0], endptr, ret);
RETURN_IF(ret, "STRTOL failed", false);
// No values can exceed those boundings, no matter the option //
if (val < 0)
val = 0;
else if (val > 100)
val = 100;
}
const int val_int = (int)val;
switch (opt) {
case LKT_PLAYBACK_OPTION_RANDOM: ret = database_config_queue(srv->db, "random", val_int); break;
case LKT_PLAYBACK_OPTION_SINGLE: ret = database_config_queue(srv->db, "single", val_int); break;
case LKT_PLAYBACK_OPTION_CONSUME:
ret = database_config_queue(srv->db, "consume", val_int);
break;
case LKT_PLAYBACK_OPTION_REPEAT: ret = database_config_queue(srv->db, "repeat", val_int); break;
case LKT_PLAYBACK_OPTION_VOLUME:
ret = database_config_queue(srv->db, "volume", val_int);
LOG_INFO("COMMAND", "Set volume to %ld", val_int);
ret &= !MOD_CALL(srv->window_mod, "set_volume", val_int);
srv->mpd_idle_events |= MPD_IDLE_MIXER;
break;
case LKT_PLAYBACK_OPTION_NONE: break;
}
if (ret)
srv->mpd_idle_events |= MPD_IDLE_OPTIONS;
return !ret;
}
bool
command_set_pos(struct lkt_state *srv, int index)
{
char filepath[PATH_MAX];
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
RETURN_UNLESS(database_queue_set_current_index(srv->db, index),
"Failed to set position in queue", false);
RETURN_UNLESS(database_queue_get_current_file(srv->db, filepath), "Failed to get filename",
false);
return !MOD_CALL(srv->window_mod, "load", filepath);
}
bool
command_seek(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
long pos, seconds, err_flag = 0;
char *endptr = NULL;
RETURN_UNLESS(args && args[0] && args[1], "Invalid arguments", false);
STRTOL(pos, args[0], endptr, err_flag);
RETURN_IF(err_flag, "Failed to get a number!", false);
STRTOL(seconds, args[1], endptr, err_flag);
RETURN_IF(err_flag, "Failed to get a number!", false);
RETURN_UNLESS(command_set_pos(srv, (int)pos), "Failed to get to the right file", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
return !MOD_CALL(srv->window_mod, "set_position", seconds);
}
bool
command_seekid(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
long id, seconds, err_flag = 0;
char *endptr = NULL;
int position;
RETURN_UNLESS(args && args[0] && args[1], "Invalid arguments", false);
STRTOL(id, args[0], endptr, err_flag);
RETURN_IF(err_flag, "Failed to get a number!", false);
STRTOL(seconds, args[1], endptr, err_flag);
RETURN_IF(err_flag, "Failed to get a number!", false);
RETURN_UNLESS(database_get_kara_position(srv->db, (int)id, &position),
"Can't find kara in queue", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
RETURN_UNLESS(command_set_pos(srv, (int)position), "Failed to set position to right kara",
false);
return !MOD_CALL(srv->window_mod, "set_position", (int)seconds);
}
bool
command_seekcur(struct lkt_state *srv, UNUSED size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
long seconds, err_flag = 0;
char *endptr = NULL;
RETURN_UNLESS(args && args[0], "Invalid arguments", false);
STRTOL(seconds, args[0], endptr, err_flag);
RETURN_IF(err_flag, "Failed to get a number!", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYER;
return !MOD_CALL(srv->window_mod, "set_position", (int)seconds);
}
bool
command_plt_add(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0], "Invalid argument", false);
bool ret = false;
if (args[1] == NULL)
ret = database_plt_create(srv->db, args[0]);
else {
struct lkt_uri *uri = lkt_uri_new();
if (!lkt_uri_from(uri, args[1])) {
LOG_ERROR("COMMAND", "Failed to get uri");
goto end_plt_add_uri;
}
else if (!database_plt_add_uri(srv->db, args[0], uri)) {
LOG_ERROR("COMMAND", "Failed to add uri '%s' to playlist", args[1]);
goto end_plt_add_uri;
}
ret = true;
end_plt_add_uri:
lkt_uri_free(uri);
}
if (ret)
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return ret;
}
bool
command_plt_remove(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0], "Invalid argument", false);
char *endptr = NULL, err;
long pos;
if (args[1] == NULL) {
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return database_plt_remove(srv->db, args[0]);
}
STRTOL(pos, args[1], endptr, err);
RETURN_IF(err, "STRTOL failed", false);
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return database_plt_remove_pos(srv->db, args[0], (int)pos);
}
bool
command_plt_clear(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0], "Invalid argument", false);
RETURN_UNLESS(database_plt_clear(srv->db, args[0]), "Failed to clear playlist", false);
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return true;
}
bool
command_plt_rename(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0] && args[1], "Invalid argument", false);
RETURN_UNLESS(database_plt_rename(srv->db, args[0], args[1]), "Failed to rename playlist",
false);
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return true;
}
bool
command_plt_export(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0] && args[1], "Invalid argument", false);
RETURN_UNLESS(database_attach(srv->db, args[0], args[1]), "Failed to attach database", false);
RETURN_UNLESS(database_plt_export(srv->db, args[0]), "Failed to export playlist", false);
RETURN_UNLESS(database_detach(srv->db, args[0]), "Failed to detach database", false);
LOG_INFO("COMMAND", "Exported playlist %s with path '%s'", args[0], args[1]);
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return true;
}
bool
command_plt_import(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0] && args[1], "Invalid argument", false);
RETURN_UNLESS(database_attach(srv->db, args[0], args[1]), "Failed to attach database", false);
RETURN_UNLESS(database_plt_import(srv->db, args[0]), "Failed to import playlist", false);
RETURN_UNLESS(database_detach(srv->db, args[0]), "Failed to detach playlist", false);
LOG_INFO("COMMAND", "Imported playlist %s with path '%s'", args[0], args[1]);
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return true;
}
bool
command_plt_add_uri(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0] && args[1], "Invalid argument", false);
struct lkt_uri *uri = lkt_uri_new();
RETURN_UNLESS(lkt_uri_from(uri, args[1]), "Failed to parse uri", false);
bool ret = database_plt_add_uri(srv->db, args[0], uri);
lkt_uri_free(uri);
RETURN_UNLESS(ret, "Failed to add uri to plt", false);
srv->mpd_idle_events |= MPD_IDLE_STORED_PLAYLIST;
return true;
}
bool
command_shuffle(struct lkt_state *srv, UNUSED size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(database_queue_shuffle(srv->db), "Failed to shuffle", false);
srv->mpd_idle_events |= MPD_IDLE_PLAYLIST;
return true;
}
bool
command_queue_listid(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(args && args[0], "Invalid arguments", false);
long id = strtol(args[0], NULL, 0);
return database_queue_probe_id(srv->db, (int)id) && command_findid(srv, c, args[0]);
}
bool
command_queue_list(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
unsigned int from, to, tmp_switch;
long val;
char *endptr = NULL, err, *str;
struct lkt_callback callback = {
.call = lkt_callback_send_row_v1,
.srv = srv,
.c = c,
};
RETURN_UNLESS(args && args[0] && strlen(args[0]), "Invalid argument", false);
/* Convert the first integer. */
STRTOL(val, args[0], endptr, err);
RETURN_IF(err, "STRTOL failed (from)", false);
from = (unsigned int)labs(val);
/* There is nothing after, is is the song pos version. */
if (endptr && (*endptr == '\0'))
goto only_one;
/* There is something after, this is the absolute forme of the command. Then
parse the second value. Skip the supposed ':' character. */
else {
str = endptr + strspn(endptr, "-+: ");
STRTOL(val, str, endptr, err);
RETURN_IF(err, "STRTOL failed (to)", false);
to = (unsigned int)labs(val);
if (to < from) {
tmp_switch = to;
to = from;
from = tmp_switch;
LOG_INFO("COMMAND", "Switch range values wrong order, now %d:%d", from, to);
}
goto is_a_range;
}
return false;
/* The command is used in its relative forme, display elements from the
current one. */
only_one:
return database_queue_list(srv->db, from, from, &callback);
/* The command is used with a range specifier. */
is_a_range:
if (to - from + 1 < lkt_remaining_msg(srv, c) - 2) {
LOG_INFO("COMMAND", "Got range %d:%d, no continuation needed", from, to);
lkt_set_continuation(srv, c, 0);
return database_queue_list(srv->db, from, to, &callback);
} else {
LOG_INFO("COMMAND", "Got range %d:%d, continuation needed", from, to);
to = from + (unsigned int)lkt_remaining_msg(srv, c) - 3;
lkt_set_continuation(srv, c, (int)(to + 1));
return database_queue_list(srv->db, from, to, &callback);
}
}
bool
command_password(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(argv[0], "Invalid argument", false);
RETURN_UNLESS(database_user_authentificate(srv->db, argv[0]), "Failed to auth user", false);
lkt_client_auth(srv, c, true);
return true;
}
bool
command_user_add(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX])
{
RETURN_UNLESS(argv[0] && argv[1], "Invalid argument", false);
RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false);
RETURN_UNLESS(database_user_add(srv->db, argv[0], argv[1]), "Failed to add user", false);
return false;
}
/* Stickers */
// clang-format off
PRIVATE_FUNCTION bool command_sticker_create(struct lkt_state *, size_t, char *[LKT_MESSAGE_ARGS_MAX - 1]);
PRIVATE_FUNCTION bool command_sticker_get (struct lkt_state *, size_t, char *[LKT_MESSAGE_ARGS_MAX - 1]);
PRIVATE_FUNCTION bool command_sticker_set (struct lkt_state *, size_t, char *[LKT_MESSAGE_ARGS_MAX - 1]);
PRIVATE_FUNCTION bool command_sticker_delete(struct lkt_state *, size_t, char *[LKT_MESSAGE_ARGS_MAX - 1]);
// clang-format on
PRIVATE_FUNCTION bool
sticker_send(struct lkt_state *srv, size_t c, char *name, char UNUSED *type, int id, int value)
{
lkt_state_send(srv, c, lkt_message_new_fmt("%d: %s -> %d\n", id, name, value));
return true;
}
PRIVATE_FUNCTION bool
command_sticker_null(struct lkt_state UNUSED *srv, size_t UNUSED c,
char UNUSED *args[LKT_MESSAGE_ARGS_MAX - 1])
{
return false;
}
bool
command_sticker_handle(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX])
{
typedef enum {
LKT_STICKER_HANDLER_NULL = 0,
LKT_STICKER_HANDLER_GET = 1,
LKT_STICKER_HANDLER_SET = 2,
LKT_STICKER_HANDLER_DEL = 3,
LKT_STICKER_HANDLER_CRATE = 4,
} LKT_STICKER_HANDLER_TYPE;
typedef bool (*handler_func)(struct lkt_state *, size_t, char * [LKT_MESSAGE_ARGS_MAX - 1]);
static const handler_func handler_lookup[] = {
command_sticker_null, command_sticker_get, command_sticker_set,
command_sticker_delete, command_sticker_create,
};
LKT_STICKER_HANDLER_TYPE handler_type =
((STR_MATCH(args[0], "get") || STR_MATCH(args[0], "list") || STR_MATCH(args[0], "find")) *
LKT_STICKER_HANDLER_GET) +
((STR_MATCH(args[0], "set")) * LKT_STICKER_HANDLER_SET) +
((STR_MATCH(args[0], "delete")) * LKT_STICKER_HANDLER_DEL) +
((STR_MATCH(args[0], "__create")) * LKT_STICKER_HANDLER_CRATE);
return handler_lookup[handler_type](srv, c, args + 1);
}
PRIVATE_FUNCTION bool
command_sticker_create(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX - 1])
{
LOG_INFO("COMMAND", "Client %ld is using the sticker create command", c);
RETURN_UNLESS(argv[0], "Invalid argument", false);
if (!database_sticker_create(srv->db, argv[0])) {
LOG_ERROR("COMMAND", "Failed to create sticker '%s'", argv[0]);
return false;
}
LOG_INFO("COMMAND", "Created sticker '%s'", argv[0]);
return true;
}
PRIVATE_FUNCTION bool
command_sticker_set(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX - 1])
{
RETURN_UNLESS(argv[0] && argv[1] && argv[2] && argv[3] && !argv[4], "Invalid argument", false);
long uri, value;
char *endptr = NULL, err1, err2;
STRTOL(uri, argv[1], endptr, err1);
STRTOL(value, argv[3], endptr, err2);
RETURN_IF(err1 || err2, "STRTOL failed", false);
LOG_INFO("COMMAND", "Client %ld is using the sticker set command", c);
RETURN_UNLESS(database_sticker_set(srv->db, argv[0], argv[2], (int)uri, (int)value),
"Can't get sticker", false);
srv->mpd_idle_events |= MPD_IDLE_STICKER;
return true;
}
PRIVATE_FUNCTION bool
command_sticker_get(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX - 1])
{
LOG_INFO("COMMAND", "Client %ld is using the sticker get command", c);
struct lkt_sticker_opt stkr = LKT_STICKER_OPT_INIT;
struct lkt_search *callback = database_search_new(srv, c, 0, FUNCTION_POINTER(sticker_send));
database_search_set_name(callback, argv[0]);
/* Simple list {type} {uri} command */
if (argv[0] != NULL && argv[1] != NULL && argv[2] == NULL) {
stkr.uri = atoi(argv[1]);
database_search_set_sticker(callback, &stkr);
if (!database_search_sticker_init(srv->db, callback))
return false;
}
/* list {type} {uri} {name} command */
else if (argv[0] != NULL && argv[1] != NULL && argv[2] != NULL && argv[3] == NULL) {
stkr.name = argv[2];
stkr.uri = atoi(argv[1]);
database_search_set_sticker(callback, &stkr);
if (!database_search_sticker_init(srv->db, callback))
return false;
}
/* list {type} {uri} {name} `op` {value} command */
else if (argv[0] != NULL && argv[1] != NULL && argv[2] != NULL && argv[3] != NULL &&
argv[4] != NULL && argv[5] == NULL) {
stkr.name = argv[2];
stkr.uri = atoi(argv[1]);
stkr.op = argv[3][0];
stkr.value = atoi(argv[4]);
database_search_set_sticker(callback, &stkr);
if (!database_search_sticker_init(srv->db, callback))
return false;
}
/* Just list all stickers */
else if ((argv[0] != NULL && argv[1] == NULL) || argv[0] == NULL) {
if (!database_search_sticker_init(srv->db, callback))
return false;
}
else
goto unknown;
/* Send results */
while (database_search_iter(callback))
continue;
return true;
unknown:
LOG_ERROR("COMMAND", "Specified command is invalid or unknown");
return false;
}
PRIVATE_FUNCTION bool
command_sticker_delete(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX - 1])
{
LOG_INFO("COMMAND", "Client %ld is using the sticker delete command", c);
RETURN_UNLESS(argv[0] && argv[1] && argv[2], "Invalid argument", false);
int uri = atoi(argv[1]);
srv->mpd_idle_events |= MPD_IDLE_STICKER;
return database_sticker_delete_specify(srv->db, argv[0], uri, argv[2]);
}
PRIVATE_FUNCTION bool
command_sticker_destroy(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX])
{
LOG_INFO("COMMAND", "Client %ld is using the sticker destroy command", c);
RETURN_UNLESS(argv[0], "Invalid argument", false);
return database_sticker_delete(srv->db, argv[0]);
}