diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index aa6681bc32d12049d1f5bfc554ff7fa911c442a2..ab48132758365411a28c778ee3cff022510c7f3b 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 6828306de22e5e51cac8e59cfb41bf3e40284685..0594fa986e1025993edd8a94e97f06999723a2e1 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 23bab61ffc3d11dd96644f61aaa175106e6d540b..34b6ffd59d34f5016b4b520268ea0d7d79e4ae42 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 1106b5acd293c929a3aa0f1016f3a2754bc106bf..96d2140d5d00fb4f8a21c32a47e8be69c798088a 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 54ac40dd9935427e1155d81a08b133303e4f2bf4..7cad7dbcfc98a83f79d1ec6075d67ebb6482e41f 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 8e49a097b9d0d5d0c79aaa8397424d9744e5cba3..1aac3ddf18f4bc8f8aad07397ec2a09085ed2bd8 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 e8184010730e6be879d828afb98d345d81939786..d58c370c2bf216e895d52e615f6d9ef2a9ed0592 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 5410dc3a4246390205c36a68108da7601e74cadf..6016644038606c3d8415dd7536ab61b8c8d736ee 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 75907ae3ebe8f507b593886cd389cf2b4de05ed9..493584ebb0d8d8d9c4f20c12007c65dcb047f501 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 60940e027310bf9858fd64f62fabfefe7c7e57fd..9cbf5321b415bbf74946b222bb83e470278981fd 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 bc2e8f7ac685da4abf9ed3f922d9b7dc73352430..2468c6b55b5aa3a38dea03a2bff10032c4139a8f 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!() +}