diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index 3329c8a48a5e26e2799d045e74eae4251f1797ed..1cc95b662f09d14105422e3284922576e58fdca0 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -70,10 +70,8 @@ enum lkt_find_action { }; /* Find and send karas in the db that match the search expression */ -bool command_find(struct lkt_state *srv, - size_t c, - char *cmd_args[LKT_MESSAGE_ARGS_MAX], - enum lkt_find_action action); +bool command_find(struct lkt_state *srv, size_t c, char *cmd_args[LKT_MESSAGE_ARGS_MAX], + long continuation, enum lkt_find_action action); /* Set options for the lektor such as `random`, `single`, `repeat`, etc */ enum lkt_playback_option { diff --git a/inc/lektor/database.h b/inc/lektor/database.h index 475206c5e695c6419e26f7077487e4d6236edb19..748916227b2fe9597a9e86c5fd5be187302ca837 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -74,18 +74,37 @@ bool database_queue_play(sqlite3 *db, int pos); bool database_queue_stop(sqlite3 *db); /* A search callback to be called after each matched row */ -typedef bool (*database_search_callback_t)(void *args, int id, int id_len, const char *sql_row); +struct lkt_callback { + bool (*call)(void *args, int id, int id_len, const char *sql_row); + struct lkt_state *srv; + size_t c; + int iterations; +}; /* List the content of the queue */ -bool database_queue_list_from(sqlite3 *db, unsigned int count, void *args, - database_search_callback_t callback); -bool database_queue_list_abs(sqlite3 *db, unsigned int from, unsigned int to, void *args, - database_search_callback_t callback); +bool database_queue_list(sqlite3 *db, size_t from, size_t to, struct lkt_callback *callback); /* Search the database */ -bool database_search_init(sqlite3 *db, char *col_name, char *rgx, sqlite3_stmt **ret); -bool database_search_iter(sqlite3 *db, sqlite3_stmt *item, void *args, - database_search_callback_t callback, bool *need_free); +struct lkt_search { + sqlite3 *db; + sqlite3_stmt *stmt; + enum lkt_search_type { + lkt_search_database, + lkt_search_playlist, + lkt_search_queue, + } type; + void (*call)(void); /* Will be casted. */ + struct lkt_state *srv; + size_t c; + long continuation; /* Is this a continuation from a previous command? */ + int msg_count; /* How much messages we can send. */ +}; + +typedef bool (*lkt_search_database_func)(struct lkt_state *srv, size_t c, int id, int id_len, + const char *row); + +bool database_search_queue_init(sqlite3 *db, char *col_name, char *rgx, struct lkt_search *ret); +bool database_search_iter(struct lkt_search *item); /* Next and prev operation on the queue. */ bool database_queue_next(sqlite3 *db, char filepath[PATH_MAX]); @@ -126,14 +145,18 @@ bool database_user_authentificate(sqlite3 *db, const char *password); bool database_user_add(sqlite3 *db, const char *username, const char *password); /* Stickers manipulations. */ -typedef bool (*database_sticker_callback_t)(void *args, const char *sticker, const char *type, - int uri, int value); +struct sticker_callback { + struct lkt_state *srv; + size_t c; + int uri, value, is_ok; + const char *name; + bool (*call)(void *args, const char *sticker, const char *type, int uri, int value); +}; bool database_sticker_create(sqlite3 *db, const char *name); bool database_sticker_delete(sqlite3 *db, const char *name); -bool database_sticker_delete_specify(sqlite3 *sb, const char *type, int uri, - const char *name /* Can be null */); -bool database_sticker_list(sqlite3 *db, const char *type, void *args, database_sticker_callback_t call); +bool database_sticker_delete_specify(sqlite3 *sb, const char *type, int uri, const char *name); +bool database_sticker_list(sqlite3 *db, const char *type, struct sticker_callback *call); bool database_sticker_set(sqlite3 *db, const char *type, const char *name, int uri, int value); -bool database_sticker_get(sqlite3 *db, const char *type, const char *name, int uri, void *args, - database_sticker_callback_t call); +bool database_sticker_get(sqlite3 *db, const char *type, const char *name, int uri, + struct sticker_callback *call); diff --git a/inc/lektor/defines.h b/inc/lektor/defines.h index 600d4b503c1157c510fa4f94eb0d5cc77939c79a..c6eac5cdabfa5a5119f7f8be73bd1c53f2e2603c 100644 --- a/inc/lektor/defines.h +++ b/inc/lektor/defines.h @@ -9,7 +9,7 @@ #define LKT_DATABASE_NAME_KTITLE "song_title" #define LKT_DATABASE_NAME_KCAT "song_type" #define LKT_DATABASE_NAME_KTYPE "category" -#define LKT_DATABASE_NAME_KAUTHOR "author" +#define LKT_DATABASE_NAME_KAUTHOR "author_name" #define LKT_DATABASE_NAME_KAUTHOR_YEAR "author_year" #define LKT_DATABASE_NAME_KLANG "language" #define LKT_DATABASE_KARA_COLUMNT_ANY "any_col" diff --git a/inc/lektor/net.h b/inc/lektor/net.h index f862eb6dd44c0b86fd753e45af3a2721db6dc1d2..0b29e70f41dcafa1f28fc3e345d1863bcba9b975 100644 --- a/inc/lektor/net.h +++ b/inc/lektor/net.h @@ -11,6 +11,7 @@ struct lkt_command { char *name; char *args[LKT_MESSAGE_ARGS_MAX]; + long cont; }; /* Create and destruct a command structure. */ @@ -46,6 +47,12 @@ struct lkt_state { /* Send a message to the connected client. */ void lkt_state_send(struct lkt_state *srv, size_t c, struct lkt_message *msg); +/* Get and set continuation state for a client. */ +size_t lkt_remaining_msg(struct lkt_state *srv, size_t c); + +int lkt_get_continuation(struct lkt_state *srv, size_t c); +void lkt_set_continuation(struct lkt_state *srv, size_t c, int i); + /* Get the mask to watch for events for a given client, to be used with the `idle` command. * Return a pointer to the client's mask */ enum mpd_idle_flag *lkt_client_get_mask(struct lkt_state *srv, size_t c); diff --git a/src/commands.c b/src/commands.c index 1c5254629204d094134de368286e49adac14bd12..9f29e29adf6a6a26fcdccaad8d24905dfa3c25e2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L +#include <lektor/macro.h> #include <lektor/commands.h> #include <lektor/database.h> #include <lektor/net.h> @@ -21,15 +22,6 @@ #define SELF_EXECUTABLE_FREEBSD "/proc/curproc/file" #define SELF_EXECUTABLE_SOLARIS "/proc/self/path/a.out" -struct _client_trace_t { - struct lkt_state *srv; - size_t c; - int uri; - const char *name; - int value; - bool is_ok; -}; - inline bool command_restart(struct lkt_state *srv, size_t c) { @@ -67,12 +59,10 @@ bool command_rescan(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX]) { (void) argv; - if (!lkt_client_auth(srv, c, false)) { fprintf(stderr, " ! command_rescan: Failed, user %lu not authentificated\n", c); return false; } - return ! repo_get_allid_async(); } @@ -83,7 +73,6 @@ command_kill(struct lkt_state *srv, size_t c) fprintf(stderr, " ! command_restart: Failed to restart, user not authentificated %lu\n", c); return false; } - fprintf(stderr, " * Stopping lektord\n"); close(srv->fds[0].fd); exit(EXIT_SUCCESS); @@ -252,10 +241,8 @@ command_stop(sqlite3 *db, struct lkt_win *win, enum mpd_idle_flag *watch_mask_pt { *watch_mask_ptr |= MPD_IDLE_PLAYER; bool res = database_queue_stop(db); - if (res) win->close(win); - return res; } @@ -272,16 +259,11 @@ command_add(sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX], struct lkt_uri_t uri; char *query = args[0]; bool ret, is_insert = false; - int priority = 1; /* To be modified according to the command (insert or add) later */ - + int priority = 1; /* To be modified according to the command (insert or add) later */ (void) is_insert; // Don't know what insert looks like (void) win; // No callbacks to the window for the moment - int i; - for (i = 0; args[i] != NULL; ++i) - printf("%s", args[i]); - if (!lkt_uri_from(&uri, query)) { fprintf(stderr, " ! command_add: failed to parse query '%s', invalid uri\n", query); return false; @@ -351,14 +333,14 @@ command_addid(sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX] return database_queue_add_id(db, id, priority); } -bool +inline bool command_clear(sqlite3 *db, enum mpd_idle_flag *watch_mask_ptr) { *watch_mask_ptr |= MPD_IDLE_PLAYLIST; return database_queue_clear(db); } -bool +inline bool command_crop(sqlite3 *db, enum mpd_idle_flag *watch_mask_ptr) { *watch_mask_ptr |= MPD_IDLE_PLAYLIST; @@ -511,38 +493,27 @@ command_idle(struct lkt_state *srv, size_t c, struct lkt_command *cmd) return true; } -bool +inline bool command_noidle(struct lkt_state *srv, size_t c) { enum mpd_idle_flag *clt_mask = lkt_client_get_mask(srv, c); *clt_mask = MPD_IDLE_NONE; - return true; } /* Functions for the searchadd and the search mpd commands */ static bool -lkt_callback_print_row_v1(void *args, int id, int id_len, const char *sql_row) +lkt_callback_print_row_v1(struct lkt_state *srv, size_t c, int id, int id_len, const char *sql_row) { - printf(" . from client %ld:\t%*d:%s\n", ((struct _client_trace_t *) args)->c, - id_len, id, sql_row); - return true; -} - -static bool -lkt_callback_none(void *args, int id, int id_len, const char *sql_row) -{ - (void) args; - (void) id; - (void) id_len; - (void) sql_row; + (void) srv; + printf(" . from client %ld:\t%*d:%s\n", c, id_len, id, sql_row); return true; } static bool lkt_callback_send_row_v1(void *_args, int id, int id_len, const char *sql_row) { - struct _client_trace_t *args = (struct _client_trace_t *) _args; + struct lkt_callback *args = (struct lkt_callback *) _args; struct lkt_message *out; out = lkt_message_new(); @@ -552,26 +523,35 @@ lkt_callback_send_row_v1(void *_args, int id, int id_len, const char *sql_row) } static bool -lkt_callback_insert_v1(void *_args, int id, int id_len, const char *sql_row) +lkt_callback_send_row_v2(struct lkt_state *srv, size_t c, int id, int id_len, const char *sql_row) +{ + struct lkt_message *out = lkt_message_new(); + out->data_len = snprintf(out->data, LKT_MESSAGE_MAX, "%*d %s\n", id_len, id, sql_row); + lkt_state_send(srv, c, out); + return true; +} + +static bool +lkt_callback_insert_v1(struct lkt_state *srv, size_t c, int id, int id_len, const char *sql_row) { + (void) c; (void) sql_row; (void) id_len; - struct _client_trace_t *args = (struct _client_trace_t *) _args; - return database_queue_add_id(args->srv->db, id, 5); + return database_queue_add_id(srv->db, id, 5); } bool -command_find(struct lkt_state *srv, - size_t c, - char *cmd_args[LKT_MESSAGE_ARGS_MAX], +command_find(struct lkt_state *srv, size_t c, char *cmd_args[LKT_MESSAGE_ARGS_MAX], long continuation, enum lkt_find_action action) { - sqlite3_stmt *stmt; char rgx[PATH_MAX], *col_name, *mpd_tag; - bool once, free_stmt; - struct _client_trace_t args; - struct lkt_message *not_found_msg; - database_search_callback_t callback; + int count; + struct lkt_search search = { + .srv = srv, + .c = c, + .continuation = continuation, + .msg_count = lkt_remaining_msg(srv, c) - 3, /* Reserve slots for OK/ACK and continue: */ + }; // Check args // if (cmd_args == NULL || cmd_args[0] == NULL) { @@ -582,23 +562,17 @@ command_find(struct lkt_state *srv, // Select callback // switch (action) { case LKT_FND_ACT_RESPOND: - callback = lkt_callback_send_row_v1; - args.srv = srv; - args.c = c; + search.call = (void(*)(void)) lkt_callback_send_row_v2; break; case LKT_FND_ACT_PRINT: - callback = lkt_callback_print_row_v1; + search.call = (void(*)(void)) lkt_callback_print_row_v1; break; case LKT_FND_ACT_ENQUEUE: - callback = lkt_callback_insert_v1; + search.call = (void(*)(void)) lkt_callback_insert_v1; srv->mpd_idle_events |= MPD_IDLE_PLAYLIST; - args.srv = srv; - args.c = c; break; - case LKT_FND_ACT_NONE: default: - callback = lkt_callback_none; - break; + return false; } // Select the right column // @@ -637,28 +611,19 @@ command_find(struct lkt_state *srv, } // Make the search langand do the right action // - if (!database_search_init(srv->db, col_name, rgx, &stmt)) { + if (!database_search_queue_init(srv->db, col_name, rgx, &search)) { fprintf(stderr, " ! command_find: Failed to init the search\n"); return false; } - for (once = false; database_search_iter(srv->db, stmt, &args, callback, &free_stmt); once |= true) + for (count = 0; database_search_iter(&search); ++count) continue; - // End // - if (free_stmt) - sqlite3_finalize(stmt); - - if (!once) { -no_rgx: - not_found_msg = lkt_message_new(); - not_found_msg->data_len = snprintf(not_found_msg->data, - LKT_MESSAGE_MAX, - "No kara found with regex\n"); - lkt_state_send(srv, c, not_found_msg); - } - + if (count) + lkt_set_continuation(srv, c, continuation + count); return true; +no_rgx: + return false; } bool @@ -669,8 +634,7 @@ command_set_playback_option(struct lkt_state *srv, size_t c, return false; (void) c; - long val; - bool ret = false; + long val, ret = false; char *endptr; struct lkt_win *win = &srv->win; @@ -924,14 +888,13 @@ command_shuffle(sqlite3 *db, enum mpd_idle_flag *watch_mask_ptr) } bool -command_queue_list(struct lkt_state *srv, size_t c, - char *args[LKT_MESSAGE_ARGS_MAX]) +command_queue_list(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]) { - unsigned int count; unsigned int from, to, tmp_switch; long val; char *endptr, *str; - struct _client_trace_t callback_args = { + struct lkt_callback callback = { + .call = lkt_callback_send_row_v1, .srv = srv, .c = c, }; @@ -957,16 +920,15 @@ command_queue_list(struct lkt_state *srv, size_t c, return false; } - if (*endptr == '\0') { - /* There is nothing after, is is the relative forme of the command. */ - count = labs(val); - goto is_relative; - } + from = labs(val); + /* There is nothing after, is is the song pos version. */ + if (*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 { - /* There is something after, this is the absolute forme of the command. Then - parse the second value. Skip the supposed ':' character. */ - from = labs(val); str = endptr + strspn(endptr, "-+: "); // NO NEGATIVE! // val = strtol(str, &endptr, 0); @@ -990,21 +952,26 @@ command_queue_list(struct lkt_state *srv, size_t c, from = tmp_switch; } - goto is_absolute; + goto is_a_range; } return false; /* The command is used in its relative forme, display elements from the current one. */ -is_relative: - return database_queue_list_from(srv->db, count, &callback_args, - lkt_callback_send_row_v1); +only_one: + return database_queue_list(srv->db, from, from, &callback); /* The command is used with a range specifier. */ -is_absolute: - return database_queue_list_abs(srv->db, from, to, &callback_args, - lkt_callback_send_row_v1); +is_a_range: + if (to - from + 1 < lkt_remaining_msg(srv, c) - 2) { + lkt_set_continuation(srv, c, 0); + return database_queue_list(srv->db, from, to, &callback); + } else { + to = from + lkt_remaining_msg(srv, c) - 3; + lkt_set_continuation(srv, c, to + 1); + return database_queue_list(srv->db, from, to, &callback); + } } bool @@ -1042,16 +1009,16 @@ command_user_add(sqlite3 *db, char *argv[LKT_MESSAGE_ARGS_MAX]) fprintf(stderr, " . command_user_add: Failed to add user %s\n", argv[0]); } +/* Stickers */ + static bool sticker_send_one_value(void *_args, const char *sticker, const char *type, int uri, int value) { - struct _client_trace_t *args = (struct _client_trace_t *) _args; - struct lkt_message *out; (void) sticker; (void) type; (void) uri; - - out = lkt_message_new(); + struct sticker_callback *args = (struct sticker_callback *) _args; + struct lkt_message *out = lkt_message_new(); out->data_len = snprintf(out->data, LKT_MESSAGE_MAX, "value: %d\n", value); lkt_state_send(args->srv, args->c, out); return false; @@ -1060,10 +1027,8 @@ sticker_send_one_value(void *_args, const char *sticker, const char *type, int u static bool sticker_send_all(void *_args, const char *sticker, const char *type, int uri, int value) { - struct _client_trace_t *args = (struct _client_trace_t *) _args; - struct lkt_message *out; - - out = lkt_message_new(); + struct sticker_callback *args = (struct sticker_callback *) _args; + struct lkt_message *out = lkt_message_new(); out->data_len = snprintf(out->data, LKT_MESSAGE_MAX, "%s: %d\nsticker: %s\nvalue: %d\n", type, uri, sticker, value); lkt_state_send(args->srv, args->c, out); @@ -1073,12 +1038,11 @@ sticker_send_all(void *_args, const char *sticker, const char *type, int uri, in static bool sticker_send_check_uri(void *_args, const char *sticker, const char *type, int uri, int value) { - struct _client_trace_t *args = (struct _client_trace_t *) _args; - struct lkt_message *out; + struct sticker_callback *args = (struct sticker_callback *) _args; (void) type; if (uri == args->uri) { - out = lkt_message_new(); + struct lkt_message *out = lkt_message_new(); out->data_len = snprintf(out->data, LKT_MESSAGE_MAX, "%s: %d\n", sticker, value); lkt_state_send(args->srv, args->c, out); } @@ -1089,7 +1053,7 @@ sticker_send_check_uri(void *_args, const char *sticker, const char *type, int u static bool sticker_send_value_check_uri_name(void *_args, const char *sticker, const char *type, int uri, int value) { - struct _client_trace_t *args = (struct _client_trace_t *) _args; + struct sticker_callback *args = (struct sticker_callback *) _args; struct lkt_message *out; (void) type; @@ -1106,7 +1070,7 @@ static bool sticker_check_is_present_eq(void *_args, const char *sticker, const char *type, int uri, int value) { (void) type; - struct _client_trace_t *args = (struct _client_trace_t *) _args; + struct sticker_callback *args = (struct sticker_callback *) _args; args->is_ok |= (uri == args->uri) && !strcasecmp(sticker, args->name) && (value == args->value); return true; } @@ -1115,7 +1079,7 @@ static bool sticker_check_is_present_lt(void *_args, const char *sticker, const char *type, int uri, int value) { (void) type; - struct _client_trace_t *args = (struct _client_trace_t *) _args; + struct sticker_callback *args = (struct sticker_callback *) _args; args->is_ok |= (uri == args->uri) && !strcasecmp(sticker, args->name) && (value < args->value); return true; } @@ -1124,7 +1088,7 @@ static bool sticker_check_is_present_gt(void *_args, const char *sticker, const char *type, int uri, int value) { (void) type; - struct _client_trace_t *args = (struct _client_trace_t *) _args; + struct sticker_callback *args = (struct sticker_callback *) _args; args->is_ok |= (uri == args->uri) && !strcasecmp(sticker, args->name) && (value > args->value); return true; } @@ -1138,12 +1102,13 @@ command_sticker_get(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS } int uri = atoi(argv[1]); /* FIXME: Use strtol. */ - struct _client_trace_t args = { + struct sticker_callback callback = { .srv = srv, .c = c, + .call = sticker_send_one_value, }; - if (!database_sticker_get(srv->db, argv[0], argv[2], uri, &args, sticker_send_one_value)) { + if (!database_sticker_get(srv->db, argv[0], argv[2], uri, &callback)) { fprintf(stderr, " . command_sticker_get: Failed to get sticker '%s' for object %s(%d)\n", argv[2], argv[0], uri); return false; @@ -1170,13 +1135,14 @@ command_sticker_set(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS return false; } + srv->mpd_idle_events |= MPD_IDLE_STICKER; return true; } bool command_sticker_list(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX]) { - struct _client_trace_t args = { + struct sticker_callback callback = { .srv = srv, .c = c, }; @@ -1204,29 +1170,35 @@ command_sticker_list(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARG goto unknown; just_list_all: - return database_sticker_list(srv->db, argv[0], &args, sticker_send_all); + callback.call = sticker_send_all; + return database_sticker_list(srv->db, argv[0], &callback); simple_list_command: - args.uri = atoi(argv[1]); /* FIXME: Use strtol. */ - return database_sticker_list(srv->db, argv[0], &args, sticker_send_check_uri); + callback.uri = atoi(argv[1]); /* FIXME: Use strtol. */ + callback.call = sticker_send_check_uri; + return database_sticker_list(srv->db, argv[0], &callback); list_stickers_in_uri: - args.uri = atoi(argv[1]); /* FIXME: Use strtol. */ - args.name = argv[2]; - return database_sticker_list(srv->db, argv[0], &args, sticker_send_value_check_uri_name); + callback.uri = atoi(argv[1]); /* FIXME: Use strtol. */ + callback.name = argv[2]; + callback.call = sticker_send_value_check_uri_name; + return database_sticker_list(srv->db, argv[0], &callback); return false; list_stickers_check_value: - args.uri = atoi(argv[1]); /* FIXME: Use strtol. */ - args.value = atoi(argv[4]); /* FIXME: Use strtol. */ - args.name = argv[2]; + callback.uri = atoi(argv[1]); /* FIXME: Use strtol. */ + callback.value = atoi(argv[4]); /* FIXME: Use strtol. */ + callback.name = argv[2]; switch (argv[3][0]) { case '=': - return database_sticker_list(srv->db, argv[0], &args, sticker_check_is_present_eq); + callback.call = sticker_check_is_present_eq; + return database_sticker_list(srv->db, argv[0], &callback); case '<': - return database_sticker_list(srv->db, argv[0], &args, sticker_check_is_present_lt); + callback.call = sticker_check_is_present_lt; + return database_sticker_list(srv->db, argv[0], &callback); case '>': - return database_sticker_list(srv->db, argv[0], &args, sticker_check_is_present_gt); + callback.call = sticker_check_is_present_gt; + return database_sticker_list(srv->db, argv[0], &callback); default: return 0; } @@ -1246,5 +1218,6 @@ command_sticker_delete(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_A (void) c; int uri = atoi(argv[1]); + srv->mpd_idle_events |= MPD_IDLE_STICKER; return database_sticker_delete_specify(srv->db, argv[0], uri, argv[2]); } diff --git a/src/database/find.c b/src/database/find.c index f9c8a62d20e01b826b0a36f23dc5b21e2c8fa043..a0366d65482adc127bb0aa9af4d627205cb6be22 100644 --- a/src/database/find.c +++ b/src/database/find.c @@ -8,7 +8,7 @@ #include <string.h> bool -database_search_init(sqlite3 *db, char *col_name, char *rgx, sqlite3_stmt **ret) +database_search_queue_init(sqlite3 *db, char *col_name, char *rgx, struct lkt_search *ret) { if (ret == NULL) { fprintf(stderr, " ! database_search_init: Exit because return pointer is NULL\n"); @@ -20,32 +20,29 @@ database_search_init(sqlite3 *db, char *col_name, char *rgx, sqlite3_stmt **ret) " SELECT kara.id AS id, string AS any_col, LENGTH(CAST(kara.id AS TEXT)) AS len" " FROM kara WHERE %s LIKE ?)" "SELECT id, any_col, (SELECT MAX(len) FROM content)" - "FROM content;"; + "FROM content LIMIT %d OFFSET %d;"; char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; - snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT - 1, SQL_STMT_TEMPLATE, col_name); + snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT - 1, SQL_STMT_TEMPLATE, col_name, + ret->msg_count, ret->continuation); SQL_STMT[LKT_MAX_SQLITE_STATEMENT - 1] = 0; - SQLITE_PREPARE(db, *ret, SQL_STMT, error); - SQLITE_BIND_TEXT(db, *ret, 1, rgx, error); + SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); + SQLITE_BIND_TEXT(db, ret->stmt, 1, rgx, error); + ret->db = db; + /* Assign the callback. */ return true; error: - sqlite3_finalize(*ret); - *ret = NULL; + sqlite3_finalize(ret->stmt); return false; } bool -database_search_iter(sqlite3 *db, - sqlite3_stmt *item, - void *args, - database_search_callback_t callback, - bool *need_free) +database_search_iter(struct lkt_search *item) { const char *sql_row; int id, code, id_len; - *need_free = true; - code = sqlite3_step(item); + code = sqlite3_step(item->stmt); if (item == NULL) { fprintf(stderr, " * database_search_iter: Exit function because the sqlite3_stmt* is NULL\n"); @@ -55,16 +52,24 @@ database_search_iter(sqlite3 *db, if (code == SQLITE_DONE) goto error_or_done; - if (code == SQLITE_ROW) { - id = sqlite3_column_int(item, 0); - sql_row = (const char *) sqlite3_column_text(item, 1); - id_len = sqlite3_column_int(item, 2); - return callback(args, id, id_len, sql_row); + if (code != SQLITE_ROW) + goto error; + + switch (item->type) { + case lkt_search_database: + id = sqlite3_column_int(item->stmt, 0); + sql_row = (const char *) sqlite3_column_text(item->stmt, 1); + id_len = sqlite3_column_int(item->stmt, 2); + return ((lkt_search_database_func) item->call)(item->srv, item->c, id, id_len, sql_row); + case lkt_search_queue: + case lkt_search_playlist: + default: + goto error; } - fprintf(stderr, " ! database_search_iter: sqlite3_step failed: %s\n", sqlite3_errmsg(db)); +error: + fprintf(stderr, " ! database_search_iter: sqlite3_step failed: %s\n", sqlite3_errmsg(item->db)); error_or_done: - sqlite3_finalize(item); - *need_free = false; + sqlite3_finalize(item->stmt); return false; } diff --git a/src/database/queue.c b/src/database/queue.c index 1ec0e4292ae8c8821db2cfc6cddfdbd6335fbf54..2b54a4d4e238300db2422b1b5fd42de49b87a11e 100644 --- a/src/database/queue.c +++ b/src/database/queue.c @@ -79,7 +79,7 @@ database_queue_current_kara(sqlite3 *db, struct kara_metadata *res, int *id) res->song_number = sqlite3_column_int(stmt, 6); no_metadata: /* Most of the time this will be NULL. */ - if (id) + if (id && sqlite3_column_type(stmt, 7) != SQLITE_NULL) *id = sqlite3_column_int(stmt, 7); } else { fprintf(stderr, " ! database_queue_current_kara: failed: %s\n", @@ -673,8 +673,7 @@ error: } bool -database_queue_list_abs(sqlite3 *db, unsigned int from, unsigned int to, void *args, - database_search_callback_t callback) +database_queue_list(sqlite3 *db, size_t from, size_t to, struct lkt_callback *callback) { const char *SQL_STMT = "WITH content AS (" @@ -691,61 +690,8 @@ database_queue_list_abs(sqlite3 *db, unsigned int from, unsigned int to, void *a sqlite3_stmt *stmt; SQLITE_PREPARE(db, stmt, SQL_STMT, error); - SQLITE_BIND_INT(db, stmt, 1, from, error); - SQLITE_BIND_INT(db, stmt, 2, to, error); - - for (;;) { - code = sqlite3_step(stmt); - - - if (code == SQLITE_ROW) { - id = sqlite3_column_int(stmt, 0); - row = (const char *) sqlite3_column_text(stmt, 1); - id_len = sqlite3_column_int(stmt, 2); - if (callback(args, id, id_len, row)) - continue; - else - break; - } - - else if (code == SQLITE_OK || code == SQLITE_DONE) - goto done; - - else - break; - } - -done: - ret = true; -error: - sqlite3_finalize(stmt); - return ret; -} - -bool -database_queue_list_from(sqlite3 *db, unsigned int count, void *args, - database_search_callback_t callback) -{ - const char *SQL_TEMPLATE = - "WITH content AS (" - " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len" - " FROM queue_" - " JOIN queue_state ON queue_.position >= CASE" - " WHEN queue_state.current IS NULL THEN 1" - " ELSE queue_state.current END" - " JOIN kara ON kara_id = kara.id" - " GROUP BY position ORDER BY position LIMIT %d)" - "SELECT id, string, (SELECT MAX(len) FROM content)" - " FROM content;"; - static const int stmt_len = 512; - char SQL_STMT[stmt_len]; - int code, id, id_len; - const char *row; - bool ret = false; - sqlite3_stmt *stmt; - - snprintf(SQL_STMT, stmt_len, SQL_TEMPLATE, count); - SQLITE_PREPARE(db, stmt, SQL_STMT, error); + SQLITE_BIND_INT(db, stmt, 1, (int) from, error); + SQLITE_BIND_INT(db, stmt, 2, (int) to, error); for (;;) { code = sqlite3_step(stmt); @@ -755,7 +701,8 @@ database_queue_list_from(sqlite3 *db, unsigned int count, void *args, id = sqlite3_column_int(stmt, 0); row = (const char *) sqlite3_column_text(stmt, 1); id_len = sqlite3_column_int(stmt, 2); - if (callback(args, id, id_len, row)) + ++(callback->iterations); + if (callback->call(callback, id, id_len, row)) continue; else break; diff --git a/src/database/stickers.c b/src/database/stickers.c index afeb6209b62dd438d7a9cc57e8e1cae23e7fc5b4..1fd2b2375190587c35883a955087f3453a855313 100644 --- a/src/database/stickers.c +++ b/src/database/stickers.c @@ -71,8 +71,7 @@ error: } bool -database_sticker_list(sqlite3 *db, const char *type, void *args, - database_sticker_callback_t call) +database_sticker_list(sqlite3 *db, const char *type, struct sticker_callback *call) { const char *SQL = NULL; int ret = false, uri, value; @@ -112,7 +111,7 @@ database_sticker_list(sqlite3 *db, const char *type, void *args, sticker = (const char *) sqlite3_column_text(stmt, 0); uri = sqlite3_column_int(stmt, 1); value = sqlite3_column_int(stmt, 2); - if (!call(args, sticker, type, uri, value)) + if (!call->call(call, sticker, type, uri, value)) goto end_loop; continue; @@ -179,8 +178,7 @@ error: } bool -database_sticker_get(sqlite3 *db, const char *type, const char *name, int uri, void *args, - database_sticker_callback_t call) +database_sticker_get(sqlite3 *db, const char *type, const char *name, int uri, struct sticker_callback *call) { const char *SQL = NULL; sqlite3_stmt *stmt; @@ -212,7 +210,7 @@ database_sticker_get(sqlite3 *db, const char *type, const char *name, int uri, v switch (sqlite3_step(stmt)) { case SQLITE_ROW: value = sqlite3_column_int(stmt, 1); - if (!call(args, name, type, uri, value)) + if (!call->call(call, name, type, uri, value)) goto end_loop; continue; diff --git a/src/main/lkt.c b/src/main/lkt.c index d28fb1a93bfa1d9447b2cc41cf08b1a1b780c16d..ca1875c915f442f750855d5ac1e85c22cb613598 100644 --- a/src/main/lkt.c +++ b/src/main/lkt.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L +#include <lektor/defines.h> #include <lektor/net.h> #include <lektor/cmd.h> #include <arpa/inet.h> @@ -36,38 +37,49 @@ help(void) "USAGE: lkt [OPTIONS] <COMMAND> [ARGS [...]]\n" "\n" " OPTIONS:\n" - " host: named of the lektor's host, can be resolved.\n" - " port: port on which lektor is listening.\n" + " host named of the lektor's host, can be resolved.\n" + " port port on which lektor is listening.\n" "\n" " options most be passed as one word (no spaced), such as the following:\n" " % lkt host=sakura port=6601 play\n" "\n" " COMMANDS:\n" - " help: display this help message\n" - " play <?idx>: toggle play/pause state of lektor, may start at a certain index,\n" - " status: get the status of lektor.\n" - " current: get the currently playing song.\n" - " add <query>: add a kara to the playlist with a query.\n" - " delete <id>: delete the id from the queue.\n" - " clear: clear the queue of lektor.\n" - " prev: play previous kara in the queue.\n" - " next: play the next kara in the queue.\n" - " shuffle: shuffle lektor's playlist and play it from the begening.\n" - " queue <?arg>: prints the queue. The argument is either a range or a count from the current kara.\n" - " plt: the playlist sub command.\n" + " help display this help message.\n" + " play <?idx> toggle play/pause state of lektor, may start at a certain index.\n" + " stop stop the playback but not the window if it exists.\n" + " status get the status of lektor.\n" + " current get the currently playing song.\n" + " add <query> add a kara to the playlist with a query.\n" + " delete <id> delete the id from the queue.\n" + " clear clear the queue of lektor.\n" + " prev play previous kara in the queue.\n" + " next play the next kara in the queue.\n" + " shuffle shuffle lektor's playlist and play it from the begening.\n" + " queue <?arg> prints the queue. The argument is either a range or a count from the current kara.\n" + " plt the playlist sub command.\n" + " search the search sub command.\n" "\n" " PLAYLIST COMMANDS:\n" - " help: display this help message\n" - " create <plt>: creates a playlist after the argument.\n" - " destroy <plt>: delete a playlist after the argument.\n" - " add <plt> <type> <query>: add something to a playlist from the database.\n" - " delete: <plt> <id>: delete domething from a playlist.\n" + " help display this help message.\n" + " create <plt> creates a playlist after the argument.\n" + " destroy <plt> delete a playlist after the argument.\n" + " add <plt> <type> <query> add something to a playlist from the database.\n" + " delete <plt> <id> delete domething from a playlist.\n" + "\n" + " SEARCH COMMANDS:\n" + " help display this help message.\n" + " get <query> prints the results of the query.\n" + " add <query> add to the queue and prints the results of the query.\n" + " insert <query> insert on top of the aueue and prints the results of the query.\n" + " plt <plt> <query> search inside a playlist and prints the results.\n" + " count <query> count the number of songs that matches the query.\n" + " queue <query> prints kara that matches from the queue.\n" "\n" " QUERY:\n" " A query is passed in argument of a COMMAND and is composed of:\n" " - The first word must be the type\n" " - the rest is used for the sqlite regex\n" - " Supported types are: title, [a]ny, source, [auth]or, [lang]uage, type.\n" + " Supported types are: title, [a]ny, source, [auth]or, [lang]uage, type, title\n" "\n" " RANGE:\n" " A range is specified like in python:\n" @@ -145,7 +157,7 @@ end: return i; } -static int +static inline int write_socket(FILE *sock, const char *buff, size_t len) { int ret = 1; @@ -213,11 +225,11 @@ create_socket(const char *host, const char *port) static FILE * lkt_connect(void) { - char buff[1024]; - size_t buff_len = 1024, recv_len; + char buff[LKT_MESSAGE_MAX]; + size_t recv_len; FILE *sock = create_socket(host, port); - recv_len = read_socket(sock, buff, buff_len); + recv_len = read_socket(sock, buff, LKT_MESSAGE_MAX); assert(recv_len > 0); @@ -246,8 +258,8 @@ clear__(struct lkt_cmd_args *args) { if (args->argc != 0) fail("Invalid argument, the clear command takes no arguments"); - static const char *const cmd__ = "clear\nclose\n"; - lkt_send_and_exit(cmd__, strlen(cmd__)); + static const char cmd__[] = "clear\nclose\n"; + lkt_send_and_exit(cmd__, sizeof(cmd__)); } @@ -256,8 +268,8 @@ next__(struct lkt_cmd_args *args) { if (args->argc != 0) fail("Invalid argument, the next command takes no arguments"); - static const char *const cmd__ = "next\nclose\n"; - lkt_send_and_exit(cmd__, strlen(cmd__)); + static const char cmd__[] = "next\nclose\n"; + lkt_send_and_exit(cmd__, sizeof(cmd__)); /* In bytes. */ } noreturn void @@ -265,8 +277,17 @@ prev__(struct lkt_cmd_args *args) { if (args->argc != 0) fail("Invalid argument, the previous command takes no arguments"); - static const char *const cmd__ = "previous\nclose\n"; - lkt_send_and_exit(cmd__, strlen(cmd__)); + static const char cmd__[] = "previous\nclose\n"; + lkt_send_and_exit(cmd__, sizeof(cmd__)); /* In bytes. */ +} + +noreturn void +stop__(struct lkt_cmd_args *args) +{ + if (args->argc != 0) + fail("Invalid argument, the stop command takes no arguments"); + static const char cmd__[] = "stop\nclose\n"; + lkt_send_and_exit(cmd__, sizeof(cmd__)); /* In bytes. */ } noreturn void @@ -279,22 +300,21 @@ play__(struct lkt_cmd_args *args) if (args->argc != 0) pos = atoi(args->argv[0]); - static const char *const status__ = "status\n"; - static const char *const cmd_play__ = "play\nclose\n"; - static const char *const cmd_play_from__ = "play %d\nclose\n"; - static const char *const cmd_pause__ = "pause\nclose\n"; + static const char status__[] = "status\n"; + static const char cmd_play__[] = "play\nclose\n"; + static const char cmd_play_from__[] = "play %d\nclose\n"; + static const char cmd_pause__[] = "pause\nclose\n"; - const size_t buff_len = 1024; - char buff[buff_len]; + char buff[LKT_MESSAGE_MAX]; FILE *sock = lkt_connect(); - if (write_socket(sock, status__, strlen(status__))) + if (write_socket(sock, status__, sizeof(status__))) /* In bytes. */ goto error; for (;;) { - memset(buff, 0, buff_len * sizeof(char)); - if (read_socket(sock, buff, buff_len - 1) <= 0) + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + if (read_socket(sock, buff, LKT_MESSAGE_MAX - 1) <= 0) exit(EXIT_FAILURE); size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); @@ -304,13 +324,13 @@ play__(struct lkt_cmd_args *args) if (STR_NMATCH(lkt_skip_key(buff), "stop", 4)) { if (!pos) - lkt_send_and_exit(cmd_play__, strlen(cmd_play__)); + lkt_send_and_exit(cmd_play__, sizeof(cmd_play__)); /* In bytes. */ else exit(write_socket_format(lkt_connect(), cmd_play_from__, pos)); } else - lkt_send_and_exit(cmd_pause__, strlen(cmd_pause__)); + lkt_send_and_exit(cmd_pause__, sizeof(cmd_pause__)); /* In bytes. */ goto error; } @@ -326,30 +346,28 @@ current__(struct lkt_cmd_args *args) if (args->argc != 0) fail("Invalid argument, the current command takes no arguments"); - static const char *const current_song__ = "currentsong\n"; - const size_t buff_len = 1024; - char buff[buff_len]; + static const char current_song__[] = "currentsong\n"; + char buff[LKT_MESSAGE_MAX]; char *mem = NULL; FILE *sock = lkt_connect(); - if (write_socket(sock, current_song__, strlen(current_song__))) + if (write_socket(sock, current_song__, sizeof(current_song__))) /* In bytes. */ goto error; - assert(mem = calloc(6 * buff_len, sizeof(char))); - assert(memset(mem, 0, 6 * buff_len * sizeof(char))); + 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[buff_len]; - char *const source = &mem[2 * buff_len]; - char *const type = &mem[3 * buff_len]; - char *const category = &mem[4 * buff_len]; - char *const language = &mem[5 * buff_len]; - + 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, buff_len * sizeof(char)); - if (read_socket(sock, buff, buff_len - 1) <= 0) + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + if (read_socket(sock, buff, LKT_MESSAGE_MAX - 1) <= 0) goto error; const size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); @@ -403,8 +421,7 @@ status__(struct lkt_cmd_args *args) static const char *const status_str__ = "status\n"; int ret = EXIT_FAILURE, it = 0; - const size_t buff_len = 1024; - char buff[buff_len]; + char buff[LKT_MESSAGE_MAX]; char flags[24]; bool play = false, stopped = true; @@ -422,8 +439,8 @@ status__(struct lkt_cmd_args *args) #define assign_int(str, var) if (! strncmp(buff, str, len)) { var = (atoi(lkt_skip_key(buff))); continue; } for (;;) { - memset(buff, 0, buff_len * sizeof(char)); - if (read_socket(sock, buff, buff_len - 1) <= 0) + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + if (read_socket(sock, buff, LKT_MESSAGE_MAX - 1) <= 0) goto error; size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); @@ -511,8 +528,7 @@ check: noreturn void add__(struct lkt_cmd_args *args) { - const size_t buff_len = 1025; - char buff[buff_len]; + char buff[LKT_MESSAGE_MAX]; int i; if (args->argc < 1) @@ -537,8 +553,8 @@ add__(struct lkt_cmd_args *args) write_socket(sock, "\n", sizeof(char)); for (;;) { - memset(buff, 0, buff_len * sizeof(char)); - assert(read_socket(sock, buff, buff_len - 1) > 0); + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + assert(read_socket(sock, buff, LKT_MESSAGE_MAX - 1) > 0); if (! strncmp(buff, "OK", 2)) exit(EXIT_SUCCESS); @@ -551,8 +567,7 @@ add__(struct lkt_cmd_args *args) noreturn void list__(struct lkt_cmd_args *args) { - const size_t buff_len = 1025; - char buff[buff_len]; + char buff[LKT_MESSAGE_MAX], *endptr; if (args->argc == 0) args->argv = LKT_QUEUE_DEFAULT; @@ -560,15 +575,40 @@ list__(struct lkt_cmd_args *args) else if (args->argc > 1) fail("Invalid argument for the queue command"); - FILE *sock = lkt_connect(); + long continuation = 0, up = 0; - write_socket(sock, "playlist ", strlen("playlist ")); - write_socket(sock, args->argv[0], strlen(args->argv[0])); - write_socket(sock, "\n", sizeof(char)); + 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') { + /* 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_format(sock, "playlist %d:%d\n", continuation, up); + else + write_socket_format(sock, "playlist %d\n", continuation); for (;;) { - memset(buff, 0, buff_len * sizeof(char)); - assert(read_socket(sock, buff, buff_len - 1) > 0); + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + assert(read_socket(sock, buff, LKT_MESSAGE_MAX - 1) > 0); + + if (! strncmp(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + if (continuation > 0) { + fclose(sock); + goto redo; + } + } if (! strncmp(buff, "OK", 2)) exit(EXIT_SUCCESS); @@ -639,7 +679,6 @@ plt_delete__(struct lkt_cmd_args *args) write_socket(sock, args->argv[i], strlen(args->argv[i])); write_socket(sock, "\n", sizeof(char)); - assert(read_socket(sock, buff, 2) > 0); if (buff[0] == 'O' && buff[1] == 'K') exit(EXIT_SUCCESS); @@ -689,17 +728,115 @@ plt_create__(struct lkt_cmd_args *args) exit(EXIT_FAILURE); } +/* Search functions. */ + +noreturn void +search_with_cmd__(struct lkt_cmd_args *args, const char *cmd) +{ + if (args->argc < 2) + fail("Invalid number of arguments, need at least a valid query"); + + if (!lkt_valid_type(args->argv[0])) + fail("Invalid type for the query"); + + char buff[LKT_MESSAGE_MAX]; + int continuation = 0, i; + FILE *sock = NULL; +redo: + sock = lkt_connect(); + + write_socket_format(sock, "%d %s", continuation, cmd); + for (i = 0; i < args->argc; ++i) + write_socket_format(sock, " %s", args->argv[i]); + write_socket(sock, "\n", sizeof("\n") / sizeof(char)); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + assert(read_socket(sock, buff, LKT_MESSAGE_MAX - 1) > 0); + + if (! strncmp(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + fclose(sock); + goto redo; + } + + if (! strncmp(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (! strncmp(buff, "ACK", 3)) + exit(EXIT_FAILURE); + + fprintf(stdout, "%s", buff); + } +} + +noreturn void +search_get__(struct lkt_cmd_args *args) +{ + search_with_cmd__(args, "search"); +} + +noreturn void +search_add__(struct lkt_cmd_args *args) +{ + search_with_cmd__(args, "searchadd"); +} + +noreturn void +search_insert__(struct lkt_cmd_args *args) +{ + (void) args; + fail("Not implemented"); +} + +noreturn void +search_plt__(struct lkt_cmd_args *args) +{ + search_with_cmd__(args, "listplaylist"); +} + +noreturn void +search_count__(struct lkt_cmd_args *args) +{ + search_with_cmd__(args, "count"); +} + +noreturn void +search_queue__(struct lkt_cmd_args *args) +{ + search_with_cmd__(args, "playlistinfo"); +} + /* Parsing stuff. */ static struct lkt_cmd_opt options_plt[] = { - { .name = "help", .call = help__ }, - { .name = "add", .call = plt_add__ }, - { .name = "delete", .call = plt_delete__ }, - { .name = "destroy", .call = plt_destroy__ }, - { .name = "create", .call = plt_create__ }, + { .name = "help", .call = help__ }, + { .name = "add", .call = plt_add__ }, + { .name = "delete", .call = plt_delete__ }, + { .name = "destroy", .call = plt_destroy__ }, + { .name = "create", .call = plt_create__ }, LKT_OPT_NULL, }; +static struct lkt_cmd_opt options_search[] = { + { .name = "help", .call = help__ }, + { .name = "get", .call = search_get__ }, + { .name = "add", .call = search_add__ }, + { .name = "insert", .call = search_insert__ }, + { .name = "plt", .call = search_plt__ }, + { .name = "count", .call = search_count__ }, + { .name = "queue", .call = search_queue__ }, + LKT_OPT_NULL, +}; + +noreturn void +search__(struct lkt_cmd_args *args) +{ + if (args->argc == 0) + fail("Invalid argument, you must specify a sub command for the search command"); + + lkt_cmd_parse(options_search, args->argc, args->argv, help); +} + noreturn void plt__(struct lkt_cmd_args *args) { @@ -721,7 +858,9 @@ static struct lkt_cmd_opt options_[] = { { .name = "add", .call = add__ }, { .name = "shuffle", .call = shuffle__ }, { .name = "status", .call = status__ }, + { .name = "stop", .call = stop__ }, { .name = "plt", .call = plt__ }, + { .name = "search", .call = search__ }, LKT_OPT_NULL, }; diff --git a/src/net/command.c b/src/net/command.c index b41d44194965b6409232df722eef73485342df5e..33df59b7ea8856018480657dc5c2f7e42ab93932 100644 --- a/src/net/command.c +++ b/src/net/command.c @@ -4,12 +4,39 @@ #include <stdlib.h> #include <string.h> +#include <errno.h> +#include <limits.h> struct lkt_command lkt_command_parse(char *raw) { - struct lkt_command res = { .name = raw, .args = {0} }; + errno = 0; + char *endptr; + struct lkt_command res = { .name = raw, .args = {0}, .cont = strtol(raw, &endptr, 10) }; + if ((errno == ERANGE && (res.cont == LONG_MAX || res.cont == LONG_MIN)) || (errno != 0)) { + res.cont = 0; + goto skip_cont; + } + + if (endptr == raw) { + res.cont = 0; + goto skip_cont; + } + + if (*endptr == '\0') { + /* That thing is an error. */ + return res; + } + + /* Get the real command name. */ + raw += strspn(raw, " 1234567890"); + if (!*raw) + return res; + res.name = raw; + + /* Get the args. */ +skip_cont: raw += strcspn(raw, " "); if (!*raw) return res; diff --git a/src/net/listen.c b/src/net/listen.c index 58b2743261ccb31c37f0f414f2e9868858abd834..3ec2fdf1e321ecd212cfec5c20a9827065e1be4f 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -54,13 +54,31 @@ struct lkt_client { bool request_close; bool authentificated; + int continuation; }; -static bool +static inline bool lkt_close_client(struct lkt_state *srv, size_t c) { - srv->clients[c - 1].request_close = true; - return true; + return srv->clients[c - 1].request_close = true; +} + +inline int +lkt_get_continuation(struct lkt_state *srv, size_t c) +{ + return srv->clients[c - 1].continuation; +} + +inline void +lkt_set_continuation(struct lkt_state *srv, size_t c, int i) +{ + srv->clients[c - 1].continuation = i; +} + +inline size_t +lkt_remaining_msg(struct lkt_state *srv, size_t c) +{ + return BUFFER_OUT_MAX - srv->clients[c - 1].buffer_out_len; } void @@ -77,6 +95,15 @@ lkt_state_send(struct lkt_state *srv, size_t c, struct lkt_message *msg) srv->fds[c].events |= POLLOUT; } +static inline void +send_continue(struct lkt_state *srv, size_t c, int i) +{ + struct lkt_message *cont = lkt_message_new(); + cont->data_len = snprintf(cont->data, LKT_MESSAGE_MAX, "continue: %d\n", i); + cont->data[LKT_MESSAGE_MAX - 1] = '\0'; + lkt_state_send(srv, c, cont); +} + static void send_ok(struct lkt_state *srv, size_t c) { @@ -133,11 +160,12 @@ command_list_begin(struct lkt_state *srv, size_t c, int list_ok) static int handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) { - int err; + int err, continuation = 0; switch (*lkt_client_get_mask(srv, c)) { case MPD_IDLE_NONE: - /* Commands that require authentification. */ + /* Commands that require authentification. + TODO: Move authentification verification inside commands. */ if (lkt_client_auth(srv, c, false)) { if (!strcmp(cmd.name, "__adduser")) { err = !command_user_add(srv->db, cmd.args); @@ -246,10 +274,10 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) goto end_no_send_status; } else if (!strcmp(cmd.name, "searchadd") || !strcmp(cmd.name, "findadd")) - err = !command_find(srv, c, cmd.args, LKT_FND_ACT_ENQUEUE); + err = !command_find(srv, c, cmd.args, cmd.cont, LKT_FND_ACT_RESPOND); else if (!strcmp(cmd.name, "search") || !strcmp(cmd.name, "find")) - err = !command_find(srv, c, cmd.args, LKT_FND_ACT_RESPOND); + err = !command_find(srv, c, cmd.args, cmd.cont, LKT_FND_ACT_RESPOND); else err = 2; @@ -268,6 +296,11 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) break; } + continuation = lkt_get_continuation(srv, c); + if (continuation > 0) { + fprintf(stderr, " * Client should continue from %d\n", continuation); + send_continue(srv, c, continuation); + } send_status(srv, c, err, cmd.name); end_no_send_status: return err; @@ -321,10 +354,13 @@ static int handle_incoming_data(struct lkt_state *srv, size_t i) { struct lkt_client *cli = &srv->clients[i - 1]; + int n; for (;;) { - int n = recv(srv->fds[i].fd, cli->buffer_in + cli->buffer_in_len, - LKT_MESSAGE_MAX - cli->buffer_in_len, 0); + /* Recieve some data. */ +recv: + n = recv(srv->fds[i].fd, cli->buffer_in + cli->buffer_in_len, + LKT_MESSAGE_MAX - cli->buffer_in_len, 0); if (n < 0) { if (errno == EWOULDBLOCK || errno == EAGAIN) return 0; @@ -334,11 +370,12 @@ handle_incoming_data(struct lkt_state *srv, size_t i) return -1; cli->buffer_in_len += n; + /* Handle the data. */ char *buf = cli->buffer_in; for (;;) { size_t line_len = strcspn(buf, "\n"); if (line_len >= cli->buffer_in_len) - break; + goto recv; buf[line_len] = 0; struct lkt_command cmd = lkt_command_parse(buf); @@ -349,7 +386,7 @@ handle_incoming_data(struct lkt_state *srv, size_t i) } if (cli->buffer_in < buf) - memcpy(cli->buffer_in, buf, cli->buffer_in_len); + memmove(cli->buffer_in, buf, cli->buffer_in_len); else if (cli->buffer_in_len == LKT_MESSAGE_MAX) return -1; }