From 117830455904c44ace12aca306f2f39960d3a435 Mon Sep 17 00:00:00 2001 From: Kubat <mael.martin31@gmail.com> Date: Mon, 23 Jan 2023 16:57:56 +0100 Subject: [PATCH] RUST: Continue to implement the rust lib of lektord with searches - Add C commands for new things needed for lkt-rs - WIP: Query informations about karas in two steps --- inc/lektor/commands.h | 6 + inc/lektor/internal/commands.def | 4 + inc/liblektor-rs/database.h | 17 ++- src/.vscode/settings.json | 3 +- src/base/commands.c | 30 +++++ .../lektor_c_compat/src/c_types.rs | 1 - .../lektor_c_compat/src/rs_types/uri.rs | 50 +++---- src/rust/liblektor-rs/lektor_db/Cargo.toml | 1 + .../liblektor-rs/lektor_db/src/connexion.rs | 124 +++++++++++++++++- src/rust/liblektor-rs/lektor_db/src/models.rs | 12 ++ src/rust/liblektor-rs/lektor_unsafe/src/db.rs | 28 ++++ 11 files changed, 247 insertions(+), 29 deletions(-) diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index aa6681bc..ab481327 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -101,6 +101,12 @@ typedef enum { LKT_PLAYBACK_OPTION_VOLUME, } LKT_PLAYBACK_OPTION; +/* Other search commands */ +bool command_kara_info(struct lkt_state *srv, size_t, char *[LKT_MESSAGE_ARGS_MAX], int cont); +bool command_kara_tags(struct lkt_state *srv, size_t, char *[LKT_MESSAGE_ARGS_MAX], int cont); +bool command_kara_infotags(struct lkt_state *srv, size_t, char *[LKT_MESSAGE_ARGS_MAX], int cont); + +/* Playback commands */ bool command_set_playback_option(struct lkt_state *, size_t, LKT_PLAYBACK_OPTION, char *[LKT_MESSAGE_ARGS_MAX]); PRIVATE_FUNCTION bool diff --git a/inc/lektor/internal/commands.def b/inc/lektor/internal/commands.def index 6828306d..0594fa98 100644 --- a/inc/lektor/internal/commands.def +++ b/inc/lektor/internal/commands.def @@ -28,6 +28,10 @@ mpd_command("swap", command_swap) mpd_command("swapid", command_swapid) mpd_command("__flat", command_flat) +mpd_command("__getinfo", command_kara_info) +mpd_command("__gettags", command_kara_tags) +mpd_command("readcomments", command_kara_infotags) + mpd_command("playlistid", command_queue_listid) mpd_command("playlist", command_queue_list) mpd_command("playlistinfo", command_queue_list) diff --git a/inc/liblektor-rs/database.h b/inc/liblektor-rs/database.h index 23bab61f..34b6ffd5 100644 --- a/inc/liblektor-rs/database.h +++ b/inc/liblektor-rs/database.h @@ -10,11 +10,22 @@ extern "C" { struct lkt_sqlite_connection; typedef struct lkt_sqlite_connection lkt_sqlite_connection; +/* The callback called on every information when calling the + * `lkt_database_get_kara_{info,tag}` functions. The first argument is the name + * of the info/tag, the second is the text representation of such tag/info. The + * last parameter of the function is a `void*` passed by the user to the parent + * function. The function can't fail. + */ +typedef void (*lkt_info_cb)(const char *, const char *, void *restrict); + lkt_sqlite_connection *lkt_database_establish_connection(const char *); -void lkt_database_close_connection(lkt_sqlite_connection *const); +void lkt_database_close_connection(lkt_sqlite_connection *); + +bool lkt_database_delete_kara_by_repo(lkt_sqlite_connection *, int64_t, int64_t); +bool lkt_database_delete_kara_by_local_id(lkt_sqlite_connection *, int64_t); -bool lkt_database_delete_kara_by_repo(lkt_sqlite_connection *const, int64_t, int64_t); -bool lkt_database_delete_kara_by_local_id(lkt_sqlite_connection *const, int64_t); +bool lkt_database_get_kara_info(lkt_sqlite_connection *, int64_t, lkt_info_cb, void *restrict); +bool lkt_database_get_kara_tags(lkt_sqlite_connection *, int64_t, lkt_info_cb, void *restrict); #if defined(__cplusplus) } diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json index 1106b5ac..96d2140d 100644 --- a/src/.vscode/settings.json +++ b/src/.vscode/settings.json @@ -1,5 +1,6 @@ { "files.exclude": { "**/rust": true, + "rust": true, }, -} \ No newline at end of file +} diff --git a/src/base/commands.c b/src/base/commands.c index 54ac40dd..7cad7dbc 100644 --- a/src/base/commands.c +++ b/src/base/commands.c @@ -779,6 +779,36 @@ command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], return ret; } +/* Constate for base continuations regarding tags in the `command_kara_infotags` + * function. Below that limit, continuation will be for info, above (ge) the + * continuation will be for tags. This limits informations to `UINT64_MAX / 2` + * the number of informations for a kara, this limit should be fine. + */ +static const int64_t BASE_TAGS_CONTINUATION = UINT64_MAX / 2; + +bool +command_kara_info(struct lkt_state UNUSED *srv, size_t UNUSED c, + char UNUSED *args[LKT_MESSAGE_ARGS_MAX], int UNUSED cont) +{ + LOG_ERROR("COMMAND", "The command kara info is not implemented for now"); + return false; +} + +bool +command_kara_tags(struct lkt_state UNUSED *srv, size_t UNUSED c, + char UNUSED *args[LKT_MESSAGE_ARGS_MAX], int UNUSED cont) +{ + LOG_ERROR("COMMAND", "The command kara tags is not implemented for now"); + return false; +} + +bool +command_kara_infotags(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], int cont) +{ + LOG_ERROR("COMMAND", "The command kara infotags is not implemented for now"); + return false; +} + bool command_plt_list(struct lkt_state *srv, size_t c, char UNUSED *args[LKT_MESSAGE_ARGS_MAX], int cont) { diff --git a/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs b/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs index 8e49a097..1aac3ddf 100644 --- a/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs +++ b/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs @@ -156,7 +156,6 @@ extern "C" { pub(crate) fn lkt_uri_get_value_as_str(_: LktUriPtr) -> *const c_char; pub(crate) fn lkt_uri_get_value_as_int(_: LktUriPtr) -> c_int; pub(crate) fn lkt_uri_get_value_type(_: LktUriPtr) -> LktUriValueType; - pub(crate) fn lkt_uri_get_column_name(_: LktUriPtr) -> *const c_char; } extern "C" { diff --git a/src/rust/liblektor-rs/lektor_c_compat/src/rs_types/uri.rs b/src/rust/liblektor-rs/lektor_c_compat/src/rs_types/uri.rs index e8184010..d58c370c 100644 --- a/src/rust/liblektor-rs/lektor_c_compat/src/rs_types/uri.rs +++ b/src/rust/liblektor-rs/lektor_c_compat/src/rs_types/uri.rs @@ -16,12 +16,6 @@ pub enum LktUriField { /// The origin of the kara must match the URI. Origin, - /// The title of the kara must match the query. - Title, - - /// The source of the kara must match the query. - Source, - /// The type of the kara must match the query. Type, @@ -31,6 +25,16 @@ pub enum LktUriField { /// Do a fuzzy search with the value if the URI. This is the default action /// for an URI. FuzzySearch, + + /// Do a search by the name of the playlist. + Playlist, +} + +/// The value of an URI, can be a string or an integer. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum LktUriValue<'a> { + String(&'a str), + Integer(i32), } impl Default for LktUriField { @@ -46,8 +50,18 @@ impl From<LktUriPtr> for LktCUri { } impl LktCUri { - pub fn get_type(&self) -> LktUriType { - unsafe { lkt_uri_get_type(self.ptr) } + pub fn get_type(&self) -> Option<LktUriField> { + use LktUriField::*; + match unsafe { lkt_uri_get_type(self.ptr) } { + LktUriType::Null => None, + LktUriType::Id => Some(Id), + LktUriType::Playlist => Some(Playlist), + LktUriType::Type => Some(Type), + LktUriType::Author => Some(KaraMaker), + LktUriType::Category => Some(Origin), + LktUriType::Lanquage => Some(Language), + LktUriType::Query => Some(FuzzySearch), + } } pub fn get_value_type(&self) -> LktUriValueType { @@ -76,21 +90,11 @@ impl LktCUri { } } - /// Get the field that query the uri. See the `inc/lektor/common.h`, - /// `LKT_DB_*` defines. If those defines change, the implementation of this - /// function must also change. - pub fn get_query_field(&self) -> Option<LktUriField> { - use LktUriField::*; - match unsafe { ptr_to_str(lkt_uri_get_column_name(self.ptr)) } { - "id" => Some(Id), - "source_name" => Some(Source), - "song_title" => Some(Title), - "category" => Some(Origin), - "song_type" => Some(Type), - "author_name" => Some(KaraMaker), - "language" => Some(Language), - "string" => Some(FuzzySearch), - _ => None, + pub fn get_value(&self) -> Option<LktUriValue> { + match self.get_value_type() { + LktUriValueType::Null => None, + LktUriValueType::String => self.get_value_as_str().map(LktUriValue::String), + LktUriValueType::Integer => self.get_value_as_int().map(LktUriValue::Integer), } } } diff --git a/src/rust/liblektor-rs/lektor_db/Cargo.toml b/src/rust/liblektor-rs/lektor_db/Cargo.toml index 5410dc3a..60166440 100644 --- a/src/rust/liblektor-rs/lektor_db/Cargo.toml +++ b/src/rust/liblektor-rs/lektor_db/Cargo.toml @@ -13,3 +13,4 @@ diesel_migrations = "2" diesel = { version = "2", default-features = false, features = ["sqlite"] } kurisu_api = { path = "../kurisu_api" } +lektor_c_compat = { path = "../lektor_c_compat" } diff --git a/src/rust/liblektor-rs/lektor_db/src/connexion.rs b/src/rust/liblektor-rs/lektor_db/src/connexion.rs index 75907ae3..493584eb 100644 --- a/src/rust/liblektor-rs/lektor_db/src/connexion.rs +++ b/src/rust/liblektor-rs/lektor_db/src/connexion.rs @@ -4,6 +4,7 @@ use crate::{ *, }; use kurisu_api::v1 as api_v1; +use lektor_c_compat::rs::LktCUri; /// Create a connexion to a database and run automatically the migrations. fn establish_connection(path: impl AsRef<str>) -> Result<SqliteConnection, String> { @@ -21,6 +22,50 @@ pub struct LktDatabaseConnection { queue: queue::LktDatabaseQueue, } +/// Get the value of a [LktCUri] and returns Err(String) in case of invalid +/// value. +macro_rules! uri_val { + ($uri: literal :/ $expr: expr) => { + $expr + .get_value() + .ok_or_else(|| format!("the value of the {}:// uri is invalid", $uri))? + }; + + ($expr: expr) => { + $expr + .get_value() + .ok_or_else(|| format!("the value of the uri is invalid"))? + }; +} + +/// Do the same thing as [uri_val], but we ensure that the value is a string and +/// then return such string. +macro_rules! uri_str { + ($what: literal :/ $uri: expr) => {{ + let LktUriValue::String(str) = uri_val!($what :/ $uri) else { + return Err(format!("try to pass integer `{}` as {} in uri", String::from($uri), $what)) + }; + str + }}; +} + +/// Do the same thing as [uri_val], but we ensure that the value is an integer +/// and then return integer. If the value is a string, we try to convert it into +/// an integer. +macro_rules! uri_as_int { + ($what: literal :/ $uri: expr) => { + match uri_val!($what :/ $uri) { + LktUriValue::Integer(int) => int, + LktUriValue::String(str) => { + let str = str.parse::<u64>().map_err(|err| { + format!("the {} `{str}` is not a correct integer: {err}", $what) + })?; + i32::try_from(str).map_err(|err| format!("the {} `{str}` {err}", $what))? + } + } + }; +} + /// Load the diesel DSL for a given table. With the loaded dsl execute the /// expression... macro_rules! with_dsl { @@ -48,7 +93,7 @@ impl LktDatabaseConnection { } /// Get a tag id by its name. - pub fn get_tag_id_by_name(&mut self, tag_name: impl AsRef<str>) -> LktDatabaseResult<i32> { + fn get_tag_id_by_name(&mut self, tag_name: impl AsRef<str>) -> LktDatabaseResult<i32> { Ok(with_dsl!(tag => tag.filter(name.is(tag_name.as_ref())) .first::<Tag>(&mut self.sqlite)?.id )) @@ -201,4 +246,81 @@ impl LktDatabaseConnection { pub fn insert_kara_with_priority(&mut self, local_id: u64, priority: LktDatabasePriority) { self.queue.insert_kara_with_priority(local_id, priority) } + + /// Search karas by URIs. We returns the local ids. + pub fn search(&mut self, uri: LktCUri) -> Result<Vec<i32>, String> { + use lektor_c_compat::rs::{LktUriField, LktUriValue}; + let uri_type = uri + .get_type() + .ok_or_else(|| "passed an URI which has no valid type...".to_string())?; + + match uri_type { + LktUriField::Id => Ok(vec![with_dsl!(kara => kara + .filter(id.is(uri_as_int!("id" :/ uri))) + .select(id) + .first::<i32>(&mut self.sqlite) + .map_err(|err| format!("{err}"))? + )]), + + LktUriField::KaraMaker => Ok(with_dsl!(kara_makers => kara_makers + .filter(name.like(uri_str!("author" :/ uri))) + .inner_join(with_dsl!(kara => kara)) + .select(id) + .load::<i32>(&mut self.sqlite) + .map_err(|err| format!("{err}"))? + )), + + LktUriField::Origin => Ok(with_dsl!(kara => kara + .filter(song_origin.like(uri_str!("origin" :/ uri))) + .select(id).load::<i32>(&mut self.sqlite) + .map_err(|err| format!("{err}"))? + )), + + LktUriField::Type => Ok(with_dsl!(kara => kara + .filter(song_type.is(uri_str!("type" :/ uri))) + .select(id).load::<i32>(&mut self.sqlite) + .map_err(|err| format!("{err}"))? + )), + + LktUriField::Language => Ok(with_dsl!(kara_langs => kara_langs + .filter(code.like(uri_str!("language" :/ uri))) + .inner_join(with_dsl!(kara => kara)) + .select(id) + .load::<i32>(&mut self.sqlite) + .map_err(|err| format!("{err}"))? + )), + + LktUriField::FuzzySearch => { + let _ = uri_str!("query" :/ uri); + todo!() + } + + LktUriField::Playlist => { + let _ = uri_str!("playlist" :/ uri); + todo!() + } + } + } + + /// Get all infos about a kara, its metadata, its tags, repo, repo id, + /// languages, karamakers... + pub fn get_info(&mut self, local_id: u64) -> Result<(), String> { + let local_id = i32::try_from(local_id) + .map_err(|err| format!("the id {local_id} is too big to be an i32: {err}"))?; + let _repo: String = with_dsl!(repo_kara => repo_kara + .filter(local_kara_id.is(local_id)) + .inner_join(with_dsl!(repo => repo.on(id.is(repo_id)))) + .select(schema::repo::name) + .first::<String>(&mut self.sqlite) + .map_err(|err| format!("failed to get parent repo of kara {local_id}: {err}"))? + ); + let _kara_makers: Vec<String> = with_dsl!(kara => kara + .filter(id.is(local_id)) + .inner_join(schema::kara_makers::table) + .select(schema::kara_makers::name) + .load::<String>(&mut self.sqlite) + .map_err(|err| format!("failed to get makers for kara {local_id}: {err}"))? + ); + todo!() + } } diff --git a/src/rust/liblektor-rs/lektor_db/src/models.rs b/src/rust/liblektor-rs/lektor_db/src/models.rs index 60940e02..9cbf5321 100644 --- a/src/rust/liblektor-rs/lektor_db/src/models.rs +++ b/src/rust/liblektor-rs/lektor_db/src/models.rs @@ -81,3 +81,15 @@ pub struct Tag { pub id: i32, pub name: String, } + +#[derive(Queryable, Selectable)] +#[diesel(table_name = kara)] +pub struct Kara { + pub id: i32, + pub is_dl: bool, + pub song_title: String, + pub song_type: String, + pub song_origin: String, + pub source_name: String, + pub file_hash: String, +} diff --git a/src/rust/liblektor-rs/lektor_unsafe/src/db.rs b/src/rust/liblektor-rs/lektor_unsafe/src/db.rs index bc2e8f7a..2468c6b5 100644 --- a/src/rust/liblektor-rs/lektor_unsafe/src/db.rs +++ b/src/rust/liblektor-rs/lektor_unsafe/src/db.rs @@ -82,3 +82,31 @@ pub unsafe extern "C" fn lkt_database_delete_kara_by_local_id( .map_err(|err| error!(target: "DB", "delete_kara_by_local_id failed: {err}")) .is_ok() } + +/// Get the related informations to a kara and call the `cb` for each one. +/// ### Safety +/// The passed db pointer must be created by +/// [`lkt_database_establish_connection`]. +#[no_mangle] +pub unsafe extern "C" fn lkt_database_get_kara_info( + db: *mut LktDatabaseConnection, + local_id: u64, + cb: extern "C" fn(*const c_char, *const c_char, *mut c_void), + user: *mut c_void, +) -> bool { + unimplemented!() +} + +/// Get the related tags to a kara and call the `cb` for each one. +/// ### Safety +/// The passed db pointer must be created by +/// [`lkt_database_establish_connection`]. +#[no_mangle] +pub unsafe extern "C" fn lkt_database_get_kara_tags( + db: *mut LktDatabaseConnection, + local_id: u64, + cb: extern "C" fn(*const c_char, *const c_char, *mut c_void), + user: *mut c_void, +) -> bool { + unimplemented!() +} -- GitLab