diff --git a/doc/lektor.1 b/doc/lektor.1 index 24b05b1c042f9fabd8bc29752ac5a6bc6345abbf..8ef2076d1624d3953ab077dc780a15700f7b419a 100644 --- a/doc/lektor.1 +++ b/doc/lektor.1 @@ -60,11 +60,7 @@ them. the \fBarg\fP should be the id of the kara in the sqlite3 database. .TP \(bu -\fBfs://${arg}\fP -the \fBarg\fP should be a part of the path to the kara. -.TP -\(bu -\fBlang://${arg}\fP / \fBlanguage://${arg}\fP +\fBlang://${arg}\fP the \fBarg\fP should be the language of the kara. .TP \(bu @@ -72,12 +68,16 @@ the \fBarg\fP should be the language of the kara. the \fBarg\fP should be the type of the kara. .TP \(bu -\fBcat://${arg}\fP / \fBcategory://${arg}\fP +\fBcategory://${arg}\fP the \fBarg\fP should be the category of the kara. .TP \(bu \fBauthor://${arg}\fP -the \fBarg\fP should be the author of the kara +the \fBarg\fP should be the author of the kara. +.TP +\(bu +\fBplaylist://${plt-name}\fP +The \fBplt-name\fP should be the name of a playlist. .TP \(bu \fBquery://${arg}\fP diff --git a/doc/lkt.1 b/doc/lkt.1 index 54cdef401d7d38579de35205b1a3abbbac34b3b9..3976f00b303da4ea953a2cefa5ad59793a4207c1 100644 --- a/doc/lkt.1 +++ b/doc/lkt.1 @@ -108,6 +108,9 @@ Clear the queue and set the state to \fIstopped\fP \fBqueue crop\fP Crop the queue, delete every kara from it appart from the currently playing one +.TP +\fBqueue replace\fP <plt-name> +Replace the queue with the content of a playlist. Keep the playling state .PP \fISEARCH-COMMANDS\fP @@ -213,19 +216,18 @@ type and the next of the line is the SQL regex that the kara must verify. In SQL regexes, the wildcard is the "%" character and the jocker the character "_". Queries are case insensitive. .PP -Valid types for a query are the following: \fIid\fP, \fIlanguage\fP, or -\fIlang\fP, \fItype\fP, \fIcat\fP or \fIcategory\fP, \fIauthor\fP, \fIquery\fP, -\fIsource\fP and \fItitle\fP. +Valid types for a query are the following: \fIid\fP, \fIlang\fP, \fItype\fP, +\fIcategory\fP, \fIauthor\fP, \fIquery\fP and \fIplaylist\fP. .PP For the type \fItype\fP, the valid values are the following: \fIOP\fP, \fIED\fP, \fIIS\fP, \fIAMV\fP, \fIVOCA\fP, \fIMV\fP, \fIPV\fP and \fILIVE\fP. .PP -For the type \fIlanguage\fP or \fIlang\fP, the valid values are the following: -\fIjp\fP, \fIfr\fP, \fIsp\fP, \fIen\fP, \fIlatin\fP, \fIit\fP, \fIru\fP, -\fImulti\fP and \fIundefined\fP. +For the type \fIlang\fP, the valid values are the following: \fIjp\fP, +\fIfr\fP, \fIsp\fP, \fIen\fP, \fIlatin\fP, \fIit\fP, \fIru\fP, \fImulti\fP +and \fIundefined\fP. .PP -For the \fIcat\fP or \fIcategory\fP type, the valid values are the following: +For the \fIcategory\fP type, the valid values are the following: \fIvo\fP, \fIva\fP, \fIcdg\fP, \fIamv\fP, \fIvocaloid\fP and \fIautres\fP. .PP Here are some examples of queries: diff --git a/inc/common/macro.h b/inc/common/macro.h index c3175cd5a2476939b30d84436a5728675346c6c4..c585059c7ad39800c1fde456f2f4a517eb57217f 100644 --- a/inc/common/macro.h +++ b/inc/common/macro.h @@ -74,30 +74,29 @@ enum log_level { #define URL_MAX_LEN 1024 -#define SELF_EXECUTABLE_LINUX "/proc/self/exe" -#define SELF_EXECUTABLE_FREEBSD "/proc/curproc/file" -#define SELF_EXECUTABLE_SOLARIS "/proc/self/path/a.out" - -#define LKT_ENV_RESTART "__LKT_RESTART" -#define LKT_ENV_CURRENT "__LKT_CURRENT" - -#define LKT_MAX_SQLITE_STATEMENT 1024 -#define PROTECTED_DATABASE "disk" - -#define LKT_DATABASE_NAME_KID "id" -#define LKT_DATABASE_NAME_KNAME "source_name" -#define LKT_DATABASE_NAME_KTITLE "song_title" -#define LKT_DATABASE_NAME_KCAT "category" -#define LKT_DATABASE_NAME_KTYPE "song_type" -#define LKT_DATABASE_NAME_KAUTHOR "author_name" -#define LKT_DATABASE_NAME_KLANG "language" -#define LKT_DATABASE_KARA_COLUMNT_ANY "any_col" -#define LKT_DATABASE_KARA_ALL "string" - -#define LEKTOR_TAG_MAX 256 -#define LKT_MESSAGE_ARGS_MAX 32 -#define LKT_MESSAGE_MAX 2048 -#define LKT_DEFAULT_LIST_SIZE 10 +#define SELF_EXECUTABLE_LINUX "/proc/self/exe" +#define SELF_EXECUTABLE_FREEBSD "/proc/curproc/file" +#define SELF_EXECUTABLE_SOLARIS "/proc/self/path/a.out" + +#define LKT_ENV_RESTART "__LKT_RESTART" +#define LKT_ENV_CURRENT "__LKT_CURRENT" + +#define LKT_MAX_SQLITE_STATEMENT 1024 +#define PROTECTED_DATABASE "disk" + +#define LKT_DB_ID "id" +#define LKT_DB_NAME "source_name" +#define LKT_DB_TITLE "song_title" +#define LKT_DB_CAT "category" +#define LKT_DB_TYPE "song_type" +#define LKT_DB_AUTHOR "author_name" +#define LKT_DB_LANG "language" +#define LKT_DB_ALL "string" + +#define LEKTOR_TAG_MAX 256 +#define LKT_MESSAGE_ARGS_MAX 32 +#define LKT_MESSAGE_MAX 2048 +#define LKT_DEFAULT_LIST_SIZE 10 typedef volatile enum { MPD_IDLE_NONE = 0, @@ -146,7 +145,8 @@ typedef volatile enum { #define SQLITE_BIND_TEXT(db, stmt, pos, text, error) \ if (sqlite3_bind_text(stmt, pos, text, -1, 0) != SQLITE_OK) { \ LOG_ERROR("DB", "Failed to bind text %s at pos %d: %s", \ - text, pos, sqlite3_errmsg((sqlite3 *) db)); \ + (const char *) text, pos, \ + sqlite3_errmsg((sqlite3 *) db)); \ goto error; \ } diff --git a/inc/common/queue.h b/inc/common/queue.h index fc3809ae205af4aa4dc43fa1eaf6491f13bbde95..5d5e5115f2658086054e999a6bab423f4226b1ba 100644 --- a/inc/common/queue.h +++ b/inc/common/queue.h @@ -7,7 +7,7 @@ enum lkt_event_type { lkt_event_null = 0, /* NULL */ lkt_event_play_pos = (1 << 1), /* size_t */ lkt_event_play_file = (1 << 2), /* const char* */ -} type; +}; #define lkt_event_play (lkt_event_play_pos | lkt_event_play_file) diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index 617046afe664f37f5bbd4af2bf5ae855f76cef8e..dae78100702bc29dd707a051f5a8bab8b611ce01 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -38,6 +38,7 @@ bool command_crop (volatile sqlite3 *db, bool command_move (volatile sqlite3 *db, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr); bool command_shuffle(volatile sqlite3 *db, mpd_idle_flag *watch_mask_ptr); bool command_playid (volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr); +bool command_dump (volatile sqlite3 *db, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr); bool command_queue_list(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]); diff --git a/inc/lektor/database.h b/inc/lektor/database.h index 87c63aa1f5fcfea8ec479d0fd679987b09402b90..b6fe3bf2aa4333f7223d80d612ef99b1849be357 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -55,7 +55,7 @@ void database_update_touch (volatile sqlite3 *db, int id); /* Control the content of the queue. */ bool database_queue_add_uri(volatile sqlite3 *db, struct lkt_uri *uri, int priority); -bool database_queue_add_id(volatile sqlite3 *db, int id, int priority); +bool database_queue_add_id (volatile sqlite3 *db, int id, int priority); bool database_queue_del_id (volatile sqlite3 *db, int id); bool database_queue_del_pos(volatile sqlite3 *db, int pos); bool database_queue_clear (volatile sqlite3 *db); @@ -63,6 +63,7 @@ bool database_queue_crop (volatile sqlite3 *db); bool database_queue_move (volatile sqlite3 *db, int from, int to); bool database_queue_shuffle(volatile sqlite3 *db); bool database_queue_seekid (volatile sqlite3 *db, int id, int *out_pos); +bool database_queue_dump (volatile sqlite3 *db, const char *plt_name); /* Control the playing state of the queue. */ bool database_queue_toggle_pause(volatile sqlite3 *db); @@ -91,19 +92,24 @@ struct lkt_search { lkt_search_sticker, } type; - void (*call)(void); /* Called during the iter phase, casted */ + void (*call)(void); /* Called during the iter phase, casted */ struct lkt_state *srv; size_t c; - long continuation; /* The continuation state of the client */ - int msg_count; /* How much messages we can send */ - const char *name; /* Stickers and playlists */ - int st_value; /* The value of a sticker */ - int st_uri; /* URI of a sticker */ - char st_op; /* Comparaison operator for stickers */ - char *st_type; /* Type of sticker */ - char *ka_rgx; /* Regex for the content of the selected column */ + long continuation; /* The continuation state of the client */ + int msg_count; /* How much messages we can send */ + + union { + const char *st_name; /* Sticker name */ + const char *plt_name; /* Playlist name */ + }; + int st_value; /* The value of a sticker */ + int st_uri; /* URI of a sticker */ + char st_op; /* Comparaison operator for stickers */ + char *st_type; /* Type of sticker */ + + struct lkt_uri ka_uri; /* Search by uri */ }; typedef bool (*lkt_search_init_add_func)(volatile sqlite3 *, struct lkt_uri *, int); @@ -114,6 +120,7 @@ typedef bool (*lkt_search_sticker_func) (struct lkt_state *srv, size_t c, const bool database_search_database_init(volatile sqlite3 *db, struct lkt_search *ret); bool database_search_queue_init (volatile sqlite3 *db, struct lkt_search *ret); bool database_search_sticker_init (volatile sqlite3 *db, struct lkt_search *ret); +bool database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret); bool database_search_iter(struct lkt_search *item); /* Next and prev operation on the queue. */ diff --git a/inc/lektor/uri.h b/inc/lektor/uri.h index c24f82ef38b92034bdc84f80e7bcb340fef0cba8..513091d46033659ff7a896d8ce2e7bca934efb29 100644 --- a/inc/lektor/uri.h +++ b/inc/lektor/uri.h @@ -5,7 +5,6 @@ #include <stdbool.h> enum lkt_uri_type { - uri_fs, uri_id, uri_playlist, uri_type, @@ -17,17 +16,17 @@ enum lkt_uri_type { struct lkt_uri { enum lkt_uri_type type; + const char *column_name; union { void *value; size_t id; }; - - bool _allocated; }; /* Create and delete URIs */ bool lkt_uri_from(struct lkt_uri *ret, char *const str); +bool lkt_uri_from_list(struct lkt_uri *ret, char **list); void lkt_uri_free(struct lkt_uri *ret); /* Make an URL to download from kurisu. diff --git a/src/commands.c b/src/commands.c index 5d53ae23ea0f33641ac397d7bcd6aaa3e8876e30..497786cee00542d7dac11879057c1949d7179be9 100644 --- a/src/commands.c +++ b/src/commands.c @@ -216,6 +216,15 @@ command_play(volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_A return __play_that_file(db, win, pos); } +bool +command_dump(volatile sqlite3 *db, char *args[LKT_MESSAGE_ARGS_MAX], + mpd_idle_flag *watch_mask_ptr) +{ + RETURN_UNLESS(args[0], "Invalid argument", false); + *watch_mask_ptr |= MPD_IDLE_PLAYLIST; + return database_queue_dump(db, args[0]); +} + bool command_playid(volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr) { @@ -251,13 +260,12 @@ command_add(volatile sqlite3 *db, struct lkt_win *win, char *args[LKT_MESSAGE_ARGS_MAX], mpd_idle_flag *watch_mask_ptr, int priority) { - RETURN_UNLESS(args, "Invalid argument", false); + RETURN_UNLESS(args && args[0], "Invalid argument", false); *watch_mask_ptr |= MPD_IDLE_PLAYLIST; struct lkt_uri uri; - char *query = args[0]; int ret; /* To be modified according to the command (insert or add) later (TODO) */ UNUSED(win); /* No callbacks to the window for the moment */ - RETURN_UNLESS(lkt_uri_from(&uri, query), "Failed to parse query", false); + RETURN_UNLESS(lkt_uri_from(&uri, args[0]), "Failed to parse query", false); ret = database_queue_add_uri(db, &uri, priority); lkt_uri_free(&uri); if (!ret) @@ -274,9 +282,8 @@ command_addid(volatile sqlite3 *db, struct lkt_win *win, errno = 0; int i; struct lkt_uri uri = { .type = uri_id }; - for (i = 0; (uri.id = strtol(args[i], NULL, 0)) && ! errno; ++i) { + for (i = 0; (uri.id = strtol(args[i], NULL, 0)) && ! errno; ++i) errno |= database_queue_add_id(db, uri.id, 1); - } *watch_mask_ptr |= MPD_IDLE_PLAYLIST; return ! errno; } @@ -427,55 +434,29 @@ lkt_callback_send_row_v2(struct lkt_state *srv, size_t c, int id, int id_len, co } bool -command_find(struct lkt_state *srv, size_t c, char *cmd_args[LKT_MESSAGE_ARGS_MAX], long continuation, - bool(*init)(volatile sqlite3 *, struct lkt_search *)) +command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], + long continuation, bool(*init)(volatile sqlite3 *, struct lkt_search *)) { - char rgx[PATH_MAX], *mpd_tag; 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: */ + /* Reserve slots for OK/ACK and continue: */ + .msg_count = lkt_remaining_msg(srv, c) - 3, .call = (void(*)(void)) lkt_callback_send_row_v2, - .ka_rgx = rgx, + .ka_uri = {0}, + .plt_name = args[0], /* In case of playlist searchs */ }; /* Check args */ - RETURN_UNLESS(cmd_args && cmd_args[0], "Invalid argument", false); - - /* Select the right column */ - mpd_tag = cmd_args[0]; - if (STR_MATCH("any", mpd_tag) || STR_MATCH("all", mpd_tag) || STR_MATCH("query", mpd_tag) || - STR_MATCH("source", mpd_tag) || STR_MATCH("title", mpd_tag)) - search.name = LKT_DATABASE_KARA_COLUMNT_ANY; - - else if (STR_MATCH("author", mpd_tag)) - search.name = LKT_DATABASE_NAME_KAUTHOR; - - else if (STR_MATCH("category", mpd_tag) || STR_MATCH("cat", mpd_tag)) - search.name = LKT_DATABASE_NAME_KCAT; - - else if (STR_MATCH("type", mpd_tag)) - search.name = LKT_DATABASE_NAME_KTYPE; - - else if (STR_MATCH("language", mpd_tag) || STR_MATCH("lang", mpd_tag)) - search.name = LKT_DATABASE_NAME_KLANG; - - else if (STR_MATCH("id", mpd_tag)) - search.name = LKT_DATABASE_NAME_KID; - - else - return false; - - /* Get the regex */ - RETURN_UNLESS(cmd_args[1], "No regex", false); - memset(rgx, 0, PATH_MAX * sizeof(char)); - - for (int i = 1; cmd_args[i]; ++i) { - strncat(rgx, cmd_args[i], PATH_MAX - 1); - if (cmd_args[i + 1]) - strncat(rgx, " ", PATH_MAX - 1); + RETURN_UNLESS(args[0], "Invalid argument", false); + if (!lkt_uri_from_list(&search.ka_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]); + RETURN_UNLESS(lkt_uri_from_list(&search.ka_uri, &args[1]), + "Failed to create the uri", false); } /* Make the search langand do the right action */ @@ -489,6 +470,7 @@ command_find(struct lkt_state *srv, size_t c, char *cmd_args[LKT_MESSAGE_ARGS_MA else LOG_WARN("COMMAND", "%s", "Nothing found"); + lkt_uri_free(&search.ka_uri); return true; } @@ -810,8 +792,8 @@ command_sticker_get(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS /* list {type} {uri} {name} command */ else if (argv[0] != NULL && argv[1] != NULL && argv[2] != NULL && argv[3] == NULL) { - callback.name = argv[2]; - callback.st_uri = atoi(argv[1]); + callback.st_name = argv[2]; + callback.st_uri = atoi(argv[1]); if (!database_search_sticker_init(srv->db, &callback)) return false; } @@ -820,9 +802,9 @@ command_sticker_get(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS else if (argv[0] != NULL && argv[1] != NULL && argv[2] != NULL && argv[3] != NULL && argv[4] != NULL && argv[5] == NULL) { - callback.name = argv[2]; - callback.st_uri = atoi(argv[1]); - callback.st_op = argv[3][0]; + callback.st_name = argv[2]; + callback.st_uri = atoi(argv[1]); + callback.st_op = argv[3][0]; callback.st_value = atoi(argv[4]); if (!database_search_sticker_init(srv->db, &callback)) return false; diff --git a/src/database/find.c b/src/database/find.c index ef8f475ae41df4990e29f008833d2bedeb8db70a..c8abc0febb018e14d56e176763f6c0f196599803 100644 --- a/src/database/find.c +++ b/src/database/find.c @@ -8,24 +8,59 @@ #include <stdio.h> #include <string.h> +static inline int +__check_sticker_type(const char *type) +{ + return ! ( STR_MATCH(type, "kara") || STR_MATCH(type, "plt") ); +} + bool database_search_database_init(volatile sqlite3 *db, struct lkt_search *ret) { RETURN_UNLESS(ret, "Exit because return pointer is NULL", false); static const char *SQL_STMT_TEMPLATE = "WITH content AS (" - " SELECT kara.id AS id, string AS any_col, LENGTH(CAST(kara.id AS TEXT)) AS len" + " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len" " FROM kara WHERE %s LIKE ?)" - "SELECT id, any_col, (SELECT MAX(len) FROM content)" + "SELECT id, string, (SELECT MAX(len) FROM content)" "FROM content LIMIT %d OFFSET %d;"; char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; ret->type = lkt_search_database; /* Search part */ safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_STMT_TEMPLATE, - ret->name, ret->msg_count, ret->continuation); + ret->ka_uri.column_name, ret->msg_count, ret->continuation); + SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); + SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_uri.value, error); + ret->db = db; + return true; +error: + sqlite3_finalize(ret->stmt); + return false; +} + +bool +database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret) +{ + RETURN_UNLESS(ret, "Exit because return pointer is NULL", false); + static const char *SQL_TEMPLATE = + "WITH content AS (" + " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len" + " FROM kara_playlist" + " JOIN playlist ON playlist.id = playlist_id" + " AND playlist.name COLLATE nocase = ?" + " JOIN kara ON kara.id = kara_id" + " AND %s LIKE ?)" + "SELECT id, string, (SELECT MAX(len) FROM content)" + "FROM content LIMIT %d OFFSET %d;"; + char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; + ret->type = lkt_search_playlist; + + safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE, + ret->ka_uri.column_name, ret->msg_count, ret->continuation); SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); - SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_rgx, error); + SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->plt_name, error); + SQLITE_BIND_TEXT(db, ret->stmt, 2, ret->ka_uri.value, error); ret->db = db; return true; error: @@ -36,8 +71,11 @@ error: bool database_search_sticker_init(volatile sqlite3 *db, struct lkt_search *ret) { - /* No bound checks in strcats, should be fine. Possible SQL injection, - depend on the `type`. */ + if (__check_sticker_type(ret->st_type)) { + LOG_ERROR("DB", "Type '%s' is invalid", ret->st_type); + return false; + } + static const char *SQL_all_types = "SELECT name, sts.id, value FROM 'stickers' JOIN " "( SELECT id, sticker, value, 'kara' FROM 'stickers.kara'" @@ -63,11 +101,11 @@ database_search_sticker_init(volatile sqlite3 *db, struct lkt_search *ret) sprintf(SQL + strlen(SQL), " AND sts.value %s %d", ret->st_op == '>' ? ">=" : ret->st_op == '<' ? "<=" : "=", ret->st_value); - strcat(SQL, !ret->name ? ";" : " AND name = ?;"); + strcat(SQL, !ret->st_name ? ";" : " AND name = ?;"); SQLITE_PREPARE(db, ret->stmt, SQL, error); - if (ret->name) - SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->name, error); + if (ret->st_name) + SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->st_name, error); return true; error: sqlite3_finalize(ret->stmt); @@ -82,21 +120,21 @@ database_search_queue_init(volatile sqlite3 *db, struct lkt_search *ret) "WITH content AS (" " SELECT" " kara_id AS id," - " string AS any_col," + " string," " LENGTH(CAST(kara_id AS TEXT)) AS len" " FROM queue" " JOIN kara " " ON kara_id = kara.id AND %s LIKE ?)" - "SELECT id, any_col, (SELECT MAX(len) FROM content)" + "SELECT id, string, (SELECT MAX(len) FROM content)" "FROM content LIMIT %d OFFSET %d;"; char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; ret->type = lkt_search_queue; /* Search part */ safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_STMT_TEMPLATE, - ret->name, ret->msg_count, ret->continuation); + ret->ka_uri.column_name, ret->msg_count, ret->continuation); SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); - SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_rgx, error); + SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->ka_uri.value, error); ret->db = db; return true; error: @@ -125,19 +163,19 @@ database_search_iter(struct lkt_search *item) switch (item->type) { case lkt_search_database: case lkt_search_queue: - 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); + case lkt_search_playlist: + id = sqlite3_column_int (item->stmt, 0); + sql_row = sqlite3_column_chars(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_sticker: - sql_row = (const char *) sqlite3_column_text(item->stmt, 0); /* Name */ - id = sqlite3_column_int(item->stmt, 1); /* Id */ - code = sqlite3_column_int(item->stmt, 2); /* Val */ - type = (const char *) sqlite3_column_text(item->stmt, 3); /* Type */ + sql_row = sqlite3_column_chars(item->stmt, 0); /* Name */ + id = sqlite3_column_int (item->stmt, 1); /* Id */ + code = sqlite3_column_int (item->stmt, 2); /* Val */ + type = sqlite3_column_chars(item->stmt, 3); /* Type */ return ((lkt_search_sticker_func) item->call) (item->srv, item->c, sql_row, type, id, code); - case lkt_search_playlist: default: LOG_WARN("DB", "Search type %d is not implemented", item->type); goto end; diff --git a/src/database/playlist.c b/src/database/playlist.c index 61b0abf7acf810024fdbc143643c27b31b0b3ce3..a4b9b26c71516502a54e74277a9fd2ea12841cd2 100644 --- a/src/database/playlist.c +++ b/src/database/playlist.c @@ -176,20 +176,19 @@ database_plt_add_uri(volatile sqlite3 *db, const char *name, switch (uri->type) { case uri_type: - column = LKT_DATABASE_NAME_KTYPE; + column = LKT_DB_TYPE; break; case uri_author: - column = LKT_DATABASE_NAME_KAUTHOR; + column = LKT_DB_AUTHOR; break; case uri_category: - column = LKT_DATABASE_NAME_KCAT; + column = LKT_DB_CAT; break; case uri_language: - column = LKT_DATABASE_NAME_KLANG; + column = LKT_DB_LANG; break; case uri_query: - case uri_fs: - column = LKT_DATABASE_KARA_ALL; + column = LKT_DB_ALL; break; default: return false; diff --git a/src/database/queue.c b/src/database/queue.c index 6c131af43e9d1d4355f8892993afe144dfbef209..92d82e63b59c3e80a1a30828716d045993e21578 100644 --- a/src/database/queue.c +++ b/src/database/queue.c @@ -258,29 +258,23 @@ error: } bool -database_queue_add_uri(volatile sqlite3 *db, struct lkt_uri *uri, int priority) +database_queue_add_uri(volatile sqlite3 *db, struct lkt_uri *uri, int prio) { switch (uri->type) { case uri_query: - case uri_fs: - return queue_add_with_col_like_str(db, LKT_DATABASE_KARA_ALL, - uri->value, priority); + return queue_add_with_col_like_str(db, LKT_DB_ALL, uri->value, prio); case uri_id: - return database_queue_add_id(db, uri->id, priority); + return database_queue_add_id(db, uri->id, prio); case uri_type: - return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KTYPE, - uri->value, priority); + return queue_add_with_col_like_str(db, LKT_DB_TYPE, uri->value, prio); case uri_category: - return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KCAT, - uri->value, priority); + return queue_add_with_col_like_str(db, LKT_DB_CAT, uri->value, prio); case uri_language: - return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KLANG, - uri->value, priority); + return queue_add_with_col_like_str(db, LKT_DB_LANG, uri->value, prio); case uri_author: - return queue_add_with_col_like_str(db, LKT_DATABASE_NAME_KAUTHOR, - uri->value, priority); + return queue_add_with_col_like_str(db, LKT_DB_AUTHOR, uri->value, prio); case uri_playlist: - return database_queue_add_plt(db, (char *) uri->value, priority); + return database_queue_add_plt(db, (char *) uri->value, prio); default: LOG_WARN("DB", "Add to queue for uri of type %d is not" " implemented", uri->type); @@ -664,6 +658,27 @@ error: return ret; } +bool +database_queue_dump(volatile sqlite3 *db, const char *plt_name) +{ + static const char *SQL = + "WITH plt_id AS (SELECT playlist.id AS id FROM playlist" + " WHERE name COLLATE nocase = ?) " + "INSERT OR IGNORE INTO kara_playlist (kara_id, playlist_id)" + " SELECT DISTINCT kara_id, plt_id.id" + " FROM queue, plt_id" + " ORDER BY RANDOM();"; + sqlite3_stmt *stmt = NULL; + SQLITE_PREPARE(db, stmt, SQL, error); + SQLITE_BIND_TEXT(db, stmt, 1, plt_name, error); + SQLITE_STEP_DONE(db, stmt, error); + sqlite3_finalize(stmt); + return true; +error: + sqlite3_finalize(stmt); + return false; +} + bool database_queue_seekid(volatile sqlite3 *db, int id, int *out_pos) { diff --git a/src/main/lkt.c b/src/main/lkt.c index cc044f05a918122590e5dcac07e4e10e3c97e282..6057953f84154b01486ddeb97d9d04ad75354885 100644 --- a/src/main/lkt.c +++ b/src/main/lkt.c @@ -58,14 +58,13 @@ static const char *LKT_QUEUE_DEFAULT[] = { "10" }; static int lkt_valid_type(const char *type) { - return (STR_MATCH(type, "all") || STR_MATCH(type, "any") || STR_MATCH(type, "query") || - STR_MATCH(type, "cat") || STR_MATCH(type, "category") || - STR_MATCH(type, "author") || STR_MATCH(type, "auth") || - STR_MATCH(type, "lang") || STR_MATCH(type, "language") || + return (STR_MATCH(type, "query") || + STR_MATCH(type, "category") || + STR_MATCH(type, "author") || + STR_MATCH(type, "lang") || STR_MATCH(type, "id") || - STR_MATCH(type, "title") || STR_MATCH(type, "type") || - STR_MATCH(type, "source")); + STR_MATCH(type, "playlist")); } static int @@ -183,6 +182,7 @@ 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 */ \ @@ -276,6 +276,43 @@ populate__(struct lkt_cmd_args *args) rescan_or_update__(args, "__rescan"); } +noreturn void +queue_replace__(struct lkt_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 lkt_cmd_args *args) { @@ -781,10 +818,46 @@ search_with_cmd__(struct lkt_cmd_args *args, const char *cmd) redo: sock = lkt_connect(); - write_socket(sock, "%d %s", continuation, cmd); - for (i = 0; i < args->argc; ++i) - write_socket(sock, " %s", args->argv[i]); - write_socket(sock, "\n"); + write_socket(sock, "%d %s %s://", continuation, cmd, args->argv[0]); + for (i = 1; i < args->argc - 1; ++i) + write_socket(sock, "%s ", args->argv[i]); + write_socket(sock, "%s\n", args->argv[i]); + + 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_plt__(struct lkt_cmd_args *args) +{ + fail_if(args->argc < 3, "Invalid number of arguments"); + fail_if(!lkt_valid_type(args->argv[1]), "Invalid type for query"); + char buff[LKT_MESSAGE_MAX]; + int continuation = 0, i; + FILE *sock = NULL; +redo: + sock = lkt_connect(); + + write_socket(sock, "%d listplaylist %s %s://", continuation, + args->argv[0], args->argv[1]); + for (i = 2; i < args->argc - 1; ++i) + write_socket(sock, "%s ", args->argv[i]); + write_socket(sock, "%s\n", args->argv[i]); for (;;) { memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); @@ -808,7 +881,6 @@ redo: #define search_with_cmd(func, cmd) /* I don't want to write always the same things */ \ noreturn void func (struct lkt_cmd_args *args) { search_with_cmd__(args, #cmd); } search_with_cmd(search_get__, search) -search_with_cmd(search_plt__, listplaylist) search_with_cmd(search_count__, count) search_with_cmd(search_queue__, playlistfind) #undef search_with_cmd @@ -816,14 +888,16 @@ search_with_cmd(search_queue__, playlistfind) /* Parsing stuff. */ static struct lkt_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 = "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__ }, LKT_OPT_DEFAULT(queue_list__), }; diff --git a/src/net/listen.c b/src/net/listen.c index 05110a2d59a968d6747e98ff6fb43eca4f769163..59ffd99c68c90ee7726d4c9a059d7b7be1301da8 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -159,7 +159,7 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) case MPD_IDLE_NONE: /* Commands that requires authentification. */ if (STR_MATCH(cmd.name, "__adduser")) - err = ! command_user_add(srv, c, (sqlite3 *) srv->db, cmd.args); + err = ! command_user_add(srv, c, srv->db, cmd.args); else if (STR_MATCH(cmd.name, "__restart")) err = ! command_restart(srv, c); else if (STR_MATCH(cmd.name, "kill")) @@ -183,25 +183,25 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) err = 0; else if (STR_MATCH(cmd.name, "next")) - err = !command_next((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events); + err = !command_next(srv->db, &srv->win, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "pause")) - err = !command_pause((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events); + err = !command_pause(srv->db, &srv->win, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "previous")) - err = !command_previous((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events); + err = !command_previous(srv->db, &srv->win, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "play")) - err = ! command_play((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); + err = ! command_play(srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "playid")) - err = ! command_playid((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); + err = ! command_playid(srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "stop")) - err = !command_stop((sqlite3 *) srv->db, &srv->win, &srv->mpd_idle_events); + err = !command_stop(srv->db, &srv->win, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "clear")) - err = !command_clear((sqlite3 *) srv->db, &srv->mpd_idle_events); + err = !command_clear(srv->db, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "crop")) - err = !command_crop((sqlite3 *) srv->db, &srv->mpd_idle_events); + err = !command_crop(srv->db, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "moveid")) - err = !command_move((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = !command_move(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "shuffle")) - err = !command_shuffle((sqlite3 *) srv->db, &srv->mpd_idle_events); + err = !command_shuffle(srv->db, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "playlist") || STR_MATCH(cmd.name, "playlistinfo")) err = !command_queue_list(srv, c, cmd.args); @@ -223,29 +223,39 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) err = !command_help(srv, c); else if (STR_MATCH(cmd.name, "__insert")) - err = ! command_add((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events, 5); - else if (STR_MATCH(cmd.name, "searchadd") || STR_MATCH(cmd.name, "findadd") || STR_MATCH(cmd.name, "add")) - err = ! command_add((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events, 1); + err = ! command_add(srv->db, &srv->win, cmd.args, + &srv->mpd_idle_events, 5); + else if (STR_MATCH(cmd.name, "searchadd") || + STR_MATCH(cmd.name, "findadd") || + STR_MATCH(cmd.name, "add")) + err = ! command_add(srv->db, &srv->win, cmd.args, + &srv->mpd_idle_events, 1); else if (STR_MATCH(cmd.name, "addid")) - err = ! command_addid((sqlite3 *) srv->db, &srv->win, cmd.args, &srv->mpd_idle_events); + err = ! command_addid(srv->db, &srv->win, cmd.args, + &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "deleteid")) err = ! (cmd.args[0] != NULL && - command_delid((sqlite3 *) srv->db, &srv->win, cmd.args[0], &srv->mpd_idle_events)); + command_delid(srv->db, &srv->win, cmd.args[0], + &srv->mpd_idle_events)); else if (STR_MATCH(cmd.name, "playlistclear")) - err = ! command_plt_clear((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_clear(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "rename")) - err = ! command_plt_rename((sqlite3 * ) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_rename(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "playlistdelete")) - err = ! command_plt_remove((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_remove(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "playlistadd")) - err = ! command_plt_add((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_add(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "rm") && cmd.args[0] != NULL && cmd.args[1] == NULL) - err = ! command_plt_remove((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_remove(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "save")) - err = ! command_plt_export((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_export(srv->db, cmd.args, &srv->mpd_idle_events); else if (STR_MATCH(cmd.name, "__import")) - err = ! command_plt_import((sqlite3 *) srv->db, cmd.args, &srv->mpd_idle_events); + err = ! command_plt_import(srv->db, cmd.args, &srv->mpd_idle_events); + else if (STR_MATCH(cmd.name, "__dump")) + err = ! command_dump(srv->db, cmd.args, &srv->mpd_idle_events); + else if (STR_MATCH(cmd.name, "listplaylist")) + err = ! command_find(srv, c, cmd.args, cmd.cont, database_search_playlist_init); else if (STR_MATCH(cmd.name, "random")) err = !command_set_playback_option(srv, c, lkt_playback_option_random, cmd.args); diff --git a/src/uri.c b/src/uri.c index 82731d39d278d3db4c284b945f3a89800b118340..82364fc9eed7ba45aaf6318122fd34b9a17fc618 100644 --- a/src/uri.c +++ b/src/uri.c @@ -9,76 +9,112 @@ #include <errno.h> #include <limits.h> -bool -lkt_uri_from(struct lkt_uri *ret, char *const str) +static inline char * +__prefix(char *str, struct lkt_uri *ret) { - char *val, *endptr; + char *val; size_t len; - long id; - - if (ret == NULL || str == NULL) - return false; - len = strlen(str); + if (!str || !ret) + return NULL; - // The minimal uri is id://1 which is a 6 characters long - if (len <= 5 * sizeof(char)) - return false; + /* The minimal uri is id://1 which is a 6 characters long */ + if ((len = strlen(str)) <= 5) + return NULL; - if (!strncasecmp(str, "fs", 2 * sizeof(char))) { - ret->type = uri_fs; - val = str + 2; - } else if (!strncasecmp(str, "id", 2 * sizeof(char))) { + if (STR_NMATCH(str, "id", 2)) { + ret->column_name = LKT_DB_ID; ret->type = uri_id; val = str + 2; - } else if (!strncasecmp(str, "plt", 3 * sizeof(char))) { + } else if (STR_NMATCH(str, "playlist", 8)) { + ret->column_name = NULL; ret->type = uri_playlist; val = str + 3; - } else if (!strncasecmp(str, "type", 4 * sizeof(char))) { + } else if (STR_NMATCH(str, "type", 4)) { + ret->column_name = LKT_DB_TYPE; ret->type = uri_type; val = str + 4; - } else if (!strncasecmp(str, "author", 6 * sizeof(char))) { + } else if (STR_NMATCH(str, "author", 6)) { + ret->column_name = LKT_DB_AUTHOR; ret->type = uri_author; val = str + 6; - } else if (!strncasecmp(str, "category", 8 * sizeof(char))) { + } else if (STR_NMATCH(str, "category", 8)) { + ret->column_name = LKT_DB_CAT; ret->type = uri_category; val = str + 8; - } else if (!strncasecmp(str, "lang", 4 * sizeof(char))) { + } else if (STR_NMATCH(str, "lang", 4)) { + ret->column_name = LKT_DB_LANG; ret->type = uri_language; val = str + 4; - } else if (!strncasecmp(str, "query", 5 * sizeof(char))) { + } else if (STR_NMATCH(str, "query", 5)) { + ret->column_name = LKT_DB_ALL; ret->type = uri_query; val = str + 5; } else - return false; + return NULL; - // No place for the '://' separator + /* No place for the '://' separator */ if (len - (val - str) < 3) - return false; + return NULL; val += 3; + ret->value = val; + return val; +} - if (ret->type == uri_id) { - id = strtol(val, &endptr, 10); +bool +lkt_uri_from(struct lkt_uri *ret, char *const str) +{ + if (NULL == __prefix(str, ret)) + return false; - if ((errno == ERANGE && (id == LONG_MAX || id == LONG_MIN)) - || (errno != 0 && id == 0) || endptr == str) + if (ret->type == uri_id) { + errno = 0; + ret->id = strtol(ret->value, NULL, 10); /* Override */ + if (errno != 0) return false; + } - ret->value = calloc(1, sizeof(int)); + ret->_allocated = false; + return true; +} - if (ret->value == NULL) - return false; +bool +lkt_uri_from_list(struct lkt_uri *ret, char **list) +{ + int i; + if (!list || !list[0]) + return false; - *(int *) ret->value = id; - ret->_allocated = true; - } + if (!list[1]) + return lkt_uri_from(ret, list[0]); + + if (NULL == __prefix(list[0], ret)) + return false; + + /* Nothing after the separator */ + if ('\0' == * (char *) ret->value) + return false; + + char *buffer = (char *) malloc(sizeof(char) * (strlen(ret->value) + 1)); + if (NULL == buffer) + return false; + strcpy(buffer, ret->value); + + for (i = 1; list[i]; ++i) { + char *new = realloc(buffer, strlen(buffer) + 1 + strlen(list[i]) + 1); + if (NULL == new) { + free(buffer); + return false; + } - else { - ret->_allocated = false; - ret->value = val; + buffer = new; + strcat(buffer, " "); + strcat(buffer, list[i]); } + ret->value = buffer; + ret->_allocated = true; return true; } @@ -113,8 +149,8 @@ __lkt_to_str(struct lkt_uri *uri, char *ret, size_t len) safe_snprintf(ret, len, "search=%s", (char *) uri->value); break; default: - LOG_ERROR("URI", "type %d may not be supported by kurisu, " - "generate an error", uri->type); + LOG_WARN("URI", "type %d may not be supported by kurisu, " + "generate an error", uri->type); return 1; }