diff --git a/lektor.code-workspace b/lektor.code-workspace index 92cad9d639e75851c03645ed9c8f0d43153f9748..33dae83601bbc6cb7e9c9a5844a5b6a2f6588984 100644 --- a/lektor.code-workspace +++ b/lektor.code-workspace @@ -16,10 +16,6 @@ "path": "src/rust/liblektor-rs", "name": "Lektor Rust Lib Sources" }, - { - "path": "src/rust/amadeus-rs", - "name": "Amadeus RS Sources" - }, { "path": "src/rust/amadeus-next", "name": "Amadeus Next RS Sources" @@ -36,4 +32,4 @@ "rust-analyzer.procMacro.enable": true, "rust-analyzer.procMacro.attributes.enable": true, } -} \ No newline at end of file +} diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json index 96d2140d5d00fb4f8a21c32a47e8be69c798088a..0d6f1e06aad7b1025de7f83f0b02ac6c57165b65 100644 --- a/src/.vscode/settings.json +++ b/src/.vscode/settings.json @@ -1,6 +1,7 @@ { "files.exclude": { + "/rust": true, + "rust/": true, "**/rust": true, - "rust": true, }, -} +} \ No newline at end of file diff --git a/src/base/commands.c b/src/base/commands.c index 7cad7dbcfc98a83f79d1ec6075d67ebb6482e41f..4bae615ca6047a9695921176120c9507522065a7 100644 --- a/src/base/commands.c +++ b/src/base/commands.c @@ -784,7 +784,7 @@ command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], * 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; +static const int64_t UNUSED BASE_TAGS_CONTINUATION = UINT64_MAX / 2; bool command_kara_info(struct lkt_state UNUSED *srv, size_t UNUSED c, @@ -803,7 +803,8 @@ command_kara_tags(struct lkt_state UNUSED *srv, size_t UNUSED c, } bool -command_kara_infotags(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], int cont) +command_kara_infotags(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 infotags is not implemented for now"); return false; diff --git a/src/rust/amadeus-next/amalib/src/response.rs b/src/rust/amadeus-next/amalib/src/response.rs index e732571626e47873af97968b34dbc5c09fb9ad71..eaa22f679e1f556e6d10a2026002ce3ae74c61af 100644 --- a/src/rust/amadeus-next/amalib/src/response.rs +++ b/src/rust/amadeus-next/amalib/src/response.rs @@ -226,6 +226,12 @@ impl LektorPlaybackStatusResponse { } } +impl LektorKaraInfoResponse { + pub fn into_inner(self) -> Vec<(String, String)> { + self.infos + } +} + impl LektorIntegerResponse { pub fn into_inner(self) -> usize { self.content diff --git a/src/rust/amadeus-rs/rsc/AmadeusLogo.jpg b/src/rust/amadeus-next/rsc/AmadeusLogo.jpg similarity index 100% rename from src/rust/amadeus-rs/rsc/AmadeusLogo.jpg rename to src/rust/amadeus-next/rsc/AmadeusLogo.jpg diff --git a/src/rust/amadeus-rs/rsc/IPAMincho.ttf b/src/rust/amadeus-next/rsc/IPAMincho.ttf similarity index 100% rename from src/rust/amadeus-rs/rsc/IPAMincho.ttf rename to src/rust/amadeus-next/rsc/IPAMincho.ttf diff --git a/src/rust/amadeus-rs/rsc/UbuntuMono-Regular.ttf b/src/rust/amadeus-next/rsc/UbuntuMono-Regular.ttf similarity index 100% rename from src/rust/amadeus-rs/rsc/UbuntuMono-Regular.ttf rename to src/rust/amadeus-next/rsc/UbuntuMono-Regular.ttf diff --git a/src/rust/amadeus-rs/rsc/UbuntuMono-UFL.txt b/src/rust/amadeus-next/rsc/UbuntuMono-UFL.txt similarity index 100% rename from src/rust/amadeus-rs/rsc/UbuntuMono-UFL.txt rename to src/rust/amadeus-next/rsc/UbuntuMono-UFL.txt diff --git a/src/rust/amadeus-rs/.gitignore b/src/rust/amadeus-rs/.gitignore deleted file mode 100644 index 27aff38a798f227cca94d0093cb29736ad803cf5..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -*.lock diff --git a/src/rust/amadeus-rs/.gitlab-ci.yml b/src/rust/amadeus-rs/.gitlab-ci.yml deleted file mode 100644 index 1d9669e1ad5d4dc7c17b7a827ec4dc0f04b0dd16..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/.gitlab-ci.yml +++ /dev/null @@ -1,10 +0,0 @@ -image: "rust:latest" - -before_script: - - apt-get update -yqq - - apt-get install -yqq --no-install-recommends build-essential libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-xfixes0-dev - -test:cargo: - script: - - rustc --version && cargo --version - - cargo test --verbose diff --git a/src/rust/amadeus-rs/Cargo.toml b/src/rust/amadeus-rs/Cargo.toml deleted file mode 100644 index 99a44cbce3f207cf1c86f5138e1fdb53609381c3..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[profile.release] -strip = true -lto = true -opt-level = "s" -codegen-units = 1 - -[workspace] -resolver = "2" -members = [ - "amadeus", - "amadeus-lib", - "amadeus-macro", - "lkt-rs", - "lkt-lib" -] diff --git a/src/rust/amadeus-rs/LICENSE b/src/rust/amadeus-rs/LICENSE deleted file mode 100644 index 14d093867d2df165a8eb9060c47071f471d171e6..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Maël MARTIN - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/rust/amadeus-rs/README.md b/src/rust/amadeus-rs/README.md deleted file mode 100644 index 8544a75dcf2ebfa29a438bc2a3475b68ee01e49a..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Amadeus RS - -Amadeus, the rust version. - -## Build - -You need a rust toolchain. When cloned just enter `cargo build` to build the -application. You will need some XCB libraries on Linux. On Ubuntu you will need -the following packets : `libxcb-render0-dev`, `libxcb-render-util0-dev`, -`libxcb-shape0-dev`, `libxcb-xfixes0-dev` diff --git a/src/rust/amadeus-rs/amadeus-lib/Cargo.toml b/src/rust/amadeus-rs/amadeus-lib/Cargo.toml deleted file mode 100644 index 60061d423543db5397159d64ca3a0a03ea767d52..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-lib/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "amadeus_lib" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -lkt_lib = { path = "../lkt-lib" } -amadeus_macro = { path = "../amadeus-macro" } -log = { version = "0.4" } -lazy_static = "1" diff --git a/src/rust/amadeus-rs/amadeus-lib/src/actions.rs b/src/rust/amadeus-rs/amadeus-lib/src/actions.rs deleted file mode 100644 index 00743374e7532fc96385bff8827cd4a44950ae49..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-lib/src/actions.rs +++ /dev/null @@ -1,104 +0,0 @@ -#[derive(Clone)] -pub enum Action { - /// Resume playback from that kara. - PlayFromKara, - - /// Pop the kara out of the queue. - DeleteKaraFromQueue, - - /// Insert the kara in the queue. The kara will be inserted at the top of - /// the queue, but after all the other inserted karas. - InsertKaraInQueue, - - /// Add the kara at the end of the playlist. - AddKaraToQueue, - - /// Add the kara to a playlist. The playlist will be selected by the user in - /// a pop-up or something like that. - AddKaraToPlaylist, - - /// Action to delete a kara from a playlist. The passed `u64` is the unique - /// id of the playlist, the internal one. - DeleteKaraFromPlaylist(u64), - - /// Open the playlist in a window for the user to browse. - OpenPlaylist, - - /// Add all the content of a playlist to the queue. - AddPlaylistToQueue, - - /// Insert all the content of a playlist in the queue. - InsertPlaylistToQueue, - - /// Clear the content of the playlist, delete all the contained karas. - ClearPlaylistContent, - - /// Connect to lektord. - ConnectToLektord, - - /// Disconnect from lektord. - DisconnectFromLektord, - - /// Refresh the list of playlists - RefreshPlaylists, - - PlaybackPrevious, - PlaybackPlay, - PlaybackPause, - PlaybackNext, -} - -impl std::fmt::Debug for Action { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use Action::*; - match self { - PlayFromKara => write!(f, "PlayFromKara"), - DeleteKaraFromQueue => write!(f, "DeleteKaraFromQueue"), - InsertKaraInQueue => write!(f, "InsertKaraInQueue"), - AddKaraToQueue => write!(f, "AddKaraToQueue"), - AddKaraToPlaylist => write!(f, "AddKaraToPlaylist"), - DeleteKaraFromPlaylist(arg0) => { - f.debug_tuple("DeleteKaraFromPlaylist").field(arg0).finish() - } - OpenPlaylist => write!(f, "OpenPlaylist"), - AddPlaylistToQueue => write!(f, "AddPlaylistToQueue"), - InsertPlaylistToQueue => write!(f, "InsertPlaylistToQueue"), - ClearPlaylistContent => write!(f, "ClearPlaylistContent"), - ConnectToLektord => write!(f, "ConnectToLektord"), - DisconnectFromLektord => write!(f, "DisconnectFromLektord"), - PlaybackPrevious => write!(f, "PlaybackPrevious"), - PlaybackPlay => write!(f, "PlaybackPlay"), - PlaybackPause => write!(f, "PlaybackPause"), - PlaybackNext => write!(f, "PlaybackNext"), - RefreshPlaylists => write!(f, "RefreshPlaylists"), - } - } -} - -pub fn get_card_action_name(act: &Action) -> &'static str { - use Action::*; - match act { - PlayFromKara => "Play from that kara", - DeleteKaraFromQueue => "Delete from the queue", - - InsertKaraInQueue => "Insert in the queue", - AddKaraToQueue => "Add to the queue", - AddKaraToPlaylist => "Add to playlist", - - DeleteKaraFromPlaylist(_) => "Delete from playlist", - OpenPlaylist => "Open playlist", - AddPlaylistToQueue => "Add playlist to the queue", - InsertPlaylistToQueue => "Insert the playlist in the queue", - ClearPlaylistContent => "Clear the playlist content", - - ConnectToLektord => "Connect to lektord", - DisconnectFromLektord => "Disconnect from lektord", - - PlaybackPrevious => "Previous", - PlaybackPlay => "Play", - PlaybackPause => "Pause", - PlaybackNext => "Next", - - RefreshPlaylists => "Refresh playlists", - } -} diff --git a/src/rust/amadeus-rs/amadeus-lib/src/deamon/command_deamon.rs b/src/rust/amadeus-rs/amadeus-lib/src/deamon/command_deamon.rs deleted file mode 100644 index d02a88feb3235619beaf432b80f4412c63e31cef..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-lib/src/deamon/command_deamon.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::*; - -pub struct CommandDeamon { - thread: Arc<Mutex<Cell<Option<thread::JoinHandle<()>>>>>, - quit: Arc<AtomicBool>, - joined: Arc<AtomicBool>, -} - -impl Deamon for CommandDeamon { - type Channels = (Sender<LektorQuery>, Receiver<LektorResponse>); - implement_deamon_quit!(); - implement_deamon_joined!(); - - fn spawn(hostname: String, port: i16) -> io::Result<(Self::Channels, Self)> { - let mut connexion = LektorConnexion::new(hostname, port)?; - - let (responses_send, responses_recv) = channel::<LektorResponse>(); - let (commands_send, commands_recv) = channel::<LektorQuery>(); - let quit = Arc::<AtomicBool>::new(AtomicBool::default()); - let joined = Arc::<AtomicBool>::new(AtomicBool::default()); - quit.store(false, atomic::Ordering::SeqCst); - joined.store(false, atomic::Ordering::SeqCst); - let quit_deamon = quit.clone(); - - let thread = thread::spawn(move || { - loop { - break_when_flagged!(quit_deamon); - match commands_recv.recv_timeout(Duration::from_secs(1)) { - Ok(command) => { - let res = match connexion.send_query(command) { - Ok(ok) => ok, - Err(e) => { - error!("failed to send query to lektor: {e}"); - info!("quiting the status deamon"); - quit_deamon.store(false, atomic::Ordering::SeqCst); - break; - } - }; - if let Err(e) = responses_send.send(res) { - error!("failed to send response to amadeus: {e}"); - info!("quiting the status deamon"); - quit_deamon.store(false, atomic::Ordering::SeqCst); - break; - } - } - Err(RecvTimeoutError::Timeout) => {} - Err(e) => { - error!("failed to get command from amadeus: {e}"); - info!("quiting the command deamon"); - quit_deamon.store(false, atomic::Ordering::SeqCst); - break; - } - }; - } - info!("quiting the command deamon!"); - }); - - let ret = Self { - thread: Arc::new(Mutex::new(Cell::new(Some(thread)))), - quit, - joined, - }; - Ok(((commands_send, responses_recv), ret)) - } -} diff --git a/src/rust/amadeus-rs/amadeus-lib/src/deamon/mod.rs b/src/rust/amadeus-rs/amadeus-lib/src/deamon/mod.rs deleted file mode 100644 index 77e56f24f979c843bf48429c9e9c3f7fc1744305..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-lib/src/deamon/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -mod command_deamon; -mod status_deamon; -pub use command_deamon::*; -pub use status_deamon::*; - -use lkt_lib::*; -use log::*; -use std::{ - cell::Cell, - io, - sync::{ - atomic::{self, AtomicBool}, - mpsc::{channel, Receiver, RecvTimeoutError, Sender}, - Arc, Mutex, - }, - thread, - time::Duration, -}; - -/// A common interface for all deamons. -pub trait Deamon: Sized { - type Channels; - - /// Quit the deamon - fn quit(&self); - - /// Tel whever the quit has been issued. - fn should_quit(&self) -> bool; - - /// Spawn a deamon - fn spawn(hostname: String, port: i16) -> io::Result<(Self::Channels, Self)>; - - /// Returns true when the thread has terminated. - fn joined(&self) -> bool; - - /// Join the deamon. - fn join(&self); -} - -#[macro_export] -macro_rules! break_when_flagged { - ($arc_atomic: expr) => { - if $arc_atomic.load(atomic::Ordering::SeqCst) { - break; - } - }; -} - -#[macro_export] -macro_rules! implement_deamon_quit { - () => { - fn quit(&self) { - debug!("asked to quit a deamon!"); - self.quit.store(true, atomic::Ordering::SeqCst); - } - - fn should_quit(&self) -> bool { - self.quit.load(atomic::Ordering::SeqCst) - } - }; -} - -#[macro_export] -macro_rules! implement_deamon_joined { - () => { - fn joined(&self) -> bool { - return self.joined.load(atomic::Ordering::SeqCst); - } - - fn join(&self) { - let locked = self.thread.lock(); - if locked.is_err() { - error!("Failed to lock the mutex that has the join handler",) - } - let locked = locked.unwrap(); - let thread = locked.replace(None); - match thread { - Some(thread) => { - let _ = thread.join(); - self.joined.store(true, atomic::Ordering::SeqCst); - } - None => error!("Nothing to join!"), - } - } - }; -} - -pub(self) use break_when_flagged; -pub(self) use implement_deamon_joined; -pub(self) use implement_deamon_quit; diff --git a/src/rust/amadeus-rs/amadeus-lib/src/deamon/status_deamon.rs b/src/rust/amadeus-rs/amadeus-lib/src/deamon/status_deamon.rs deleted file mode 100644 index b90b13087f373c60a37ca6c341915535a145aae1..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-lib/src/deamon/status_deamon.rs +++ /dev/null @@ -1,86 +0,0 @@ -use super::*; - -pub type StatusDeamonMessageType = ( - LektorPlaybackStatusResponse, - Option<LektorCurrentKaraResponse>, -); - -pub struct StatusDeamon { - thread: Arc<Mutex<Cell<Option<thread::JoinHandle<()>>>>>, - quit: Arc<AtomicBool>, - joined: Arc<AtomicBool>, -} - -impl Deamon for StatusDeamon { - type Channels = (Receiver<StatusDeamonMessageType>,); - implement_deamon_quit!(); - implement_deamon_joined!(); - - fn spawn(hostname: String, port: i16) -> io::Result<(Self::Channels, StatusDeamon)> { - let mut connexion = LektorConnexion::new(hostname, port)?; - - let (responses_send, responses_recv) = channel(); - let quit = Arc::<AtomicBool>::new(AtomicBool::default()); - let joined = Arc::<AtomicBool>::new(AtomicBool::default()); - quit.store(false, atomic::Ordering::SeqCst); - joined.store(false, atomic::Ordering::SeqCst); - let quit_deamon = quit.clone(); - - let thread = thread::spawn(move || { - loop { - break_when_flagged!(quit_deamon); - thread::sleep(Duration::from_secs(1)); - break_when_flagged!(quit_deamon); - - let status = match connexion.send_query(LektorQuery::PlaybackStatus) { - Ok(LektorResponse::PlaybackStatus(res)) => res, - Ok(_) => { - error!("got invalid response from lektor, not a status..."); - info!("quiting the status deamon"); - quit_deamon.store(false, atomic::Ordering::SeqCst); - break; - } - Err(e) => { - error!("failed to send the playback status command to lektor: {e}"); - info!("quiting the status deamon"); - quit_deamon.store(false, atomic::Ordering::SeqCst); - break; - } - }; - - let current = match status.state() { - LektorState::Stopped => None, - LektorState::Play(_) | LektorState::Pause(_) => { - match connexion.send_query(LektorQuery::CurrentKara) { - Ok(LektorResponse::CurrentKara(res)) => Some(res), - Ok(_) => { - error!("got invalid response from lektor, not a current kara..."); - None - } - Err(e) => { - error!("failed to send the current kara command to lektor: {e}"); - None - } - } - } - }; - - info!("send {status:?} and {current:?}"); - if let Err(e) = responses_send.send((status, current)) { - error!("Failed to send a status response to amadeus: {e}"); - info!("quiting the status deamon"); - quit_deamon.store(false, atomic::Ordering::SeqCst); - break; - } - } - info!("quiting the status deamon!"); - }); - - let ret = Self { - thread: Arc::new(Mutex::new(Cell::new(Some(thread)))), - quit, - joined, - }; - Ok(((responses_recv,), ret)) - } -} diff --git a/src/rust/amadeus-rs/amadeus-lib/src/lib.rs b/src/rust/amadeus-rs/amadeus-lib/src/lib.rs deleted file mode 100644 index 719c05fa492ad45e7bb0ea9bc612ffaeeb9961bd..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-lib/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod actions; -pub mod deamon; diff --git a/src/rust/amadeus-rs/amadeus-macro/Cargo.toml b/src/rust/amadeus-rs/amadeus-macro/Cargo.toml deleted file mode 100644 index 7671b6f7e25463f99d7aea99467a1542185a926e..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-macro/Cargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "amadeus_macro" -version = "0.1.0" -edition = "2021" -license = "MIT" \ No newline at end of file diff --git a/src/rust/amadeus-rs/amadeus-macro/src/lib.rs b/src/rust/amadeus-rs/amadeus-macro/src/lib.rs deleted file mode 100644 index 3be4abf86039feae4d636763113449e2336d058e..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus-macro/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -#[macro_export] -macro_rules! either { - ($test:expr => $true_expr:expr; $false_expr:expr) => { - if $test { - $true_expr - } else { - $false_expr - } - }; -} - -#[macro_export] -macro_rules! lkt_command_from_str { - ($lit:literal) => { - concat!($lit, '\n').to_owned() - }; -} - -#[macro_export] -macro_rules! then_some { - ($cond: expr => $some: expr) => { - if $cond { - Some($some) - } else { - None - } - }; -} diff --git a/src/rust/amadeus-rs/amadeus/Cargo.toml b/src/rust/amadeus-rs/amadeus/Cargo.toml deleted file mode 100644 index e60c07ea07810f86b22699518c5dc6fe437ed2e3..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "amadeus" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -lkt_lib = { path = "../lkt-lib" } -amadeus_lib = { path = "../amadeus-lib" } -amadeus_macro = { path = "../amadeus-macro" } -serde = { version = "1", default-features = false, features = [ "derive", "std" ] } -serde_json = { version = "1", default-features = false, features = [ "std" ] } -image = { version = "0.24", default-features = false, features = [ "jpeg", "ico", "png" ] } -eframe = { version = "0", features = [ "persistence" ] } -egui = { version = "0", features = [ "extra_debug_asserts", "extra_asserts", "serde", "persistence" ] } -epi = { version = "0", features = [ "persistence" ] } -log = { version = "0.4" } -lazy_static = "1" diff --git a/src/rust/amadeus-rs/amadeus/src/amadeus.rs b/src/rust/amadeus-rs/amadeus/src/amadeus.rs deleted file mode 100644 index 0ac96a3a9ea51c16d634b253b98d193a79781097..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/amadeus.rs +++ /dev/null @@ -1,560 +0,0 @@ -use crate::{cards::*, constants, utils, widgets}; -use amadeus_lib::{ - actions, - deamon::{self, CommandDeamon, Deamon, StatusDeamon}, -}; -use amadeus_macro::*; -use eframe::{egui, App}; -use lkt_lib::{ - Kara, LektorCurrentKaraInnerResponse, LektorPlaybackStatusResponse, LektorQuery, - LektorResponse, LektorState, Playlist, -}; -use log::*; -use std::{ - sync::mpsc::{Receiver, SendError, Sender}, - time, -}; - -type CommandDeamonData = ( - (Sender<LektorQuery>, Receiver<LektorResponse>), - deamon::CommandDeamon, -); - -type StatusDeamonData = ( - (Receiver<deamon::StatusDeamonMessageType>,), - deamon::StatusDeamon, -); - -pub struct Amadeus<'a> { - config: utils::AmadeusConfig, - has_config_changed: bool, - need_about_window: bool, - need_settings_window: bool, - actions: Vec<actions::Action>, - - last_render_instant: time::SystemTime, - begin_render_instant: time::SystemTime, - - deamon: Option<CommandDeamonData>, - status_deamon: Option<StatusDeamonData>, - - lektord_status: Option<LektorPlaybackStatusResponse>, - lektord_current: Option<LektorCurrentKaraInnerResponse>, - - lektord_queue: KaraCardCollection<'a>, - lektord_historic: KaraCardCollection<'a>, - - lektord_search_results: KaraCardCollection<'a>, - lektord_search_query: String, - lektord_updated_query: bool, - - playlist_store: widgets::PlaylistsStore, - - lektord_state: LektorState, - - amadeus_logo_texture: Option<egui::TextureHandle>, -} - -fn handle_sender_error(e: Result<(), SendError<LektorQuery>>) { - if let Err(e) = e { - let query = e.0; - error!("error while sending commands {query:?} to command deamon") - } -} - -impl Default for Amadeus<'_> { - fn default() -> Self { - Self { - last_render_instant: time::SystemTime::now(), - begin_render_instant: time::SystemTime::UNIX_EPOCH, - - lektord_queue: KaraCardCollection::new("Queue".to_owned()) - .add_action(actions::Action::PlayFromKara) - .add_action(actions::Action::DeleteKaraFromQueue) - .add_action(actions::Action::AddKaraToPlaylist), - lektord_historic: KaraCardCollection::new("Historic".to_owned()) - .add_action(actions::Action::AddKaraToQueue) - .add_action(actions::Action::InsertKaraInQueue) - .add_action(actions::Action::DeleteKaraFromQueue) - .add_action(actions::Action::AddKaraToPlaylist), - lektord_search_results: KaraCardCollection::new("Search results".to_owned()) - .add_action(actions::Action::AddKaraToQueue) - .add_action(actions::Action::InsertKaraInQueue) - .add_action(actions::Action::DeleteKaraFromQueue) - .add_action(actions::Action::AddKaraToPlaylist), - lektord_search_query: String::new(), - - actions: Vec::with_capacity(10), - playlist_store: Default::default(), - config: Default::default(), - has_config_changed: Default::default(), - need_about_window: Default::default(), - need_settings_window: Default::default(), - deamon: Default::default(), - status_deamon: Default::default(), - lektord_updated_query: Default::default(), - lektord_state: Default::default(), - amadeus_logo_texture: Default::default(), - - lektord_status: None, - lektord_current: None, - } - } -} - -impl Amadeus<'_> { - pub fn create(cc: &eframe::CreationContext<'_>) -> Box<Self> { - let mut ret = Amadeus::default(); - let ctx = &cc.egui_ctx; - if let Some(storage) = cc.storage { - ret.config = eframe::get_value(storage, "amadeus-rs-config").unwrap_or_default(); - } - - ctx.set_fonts(utils::font::get_font_definitions()); - ret.load_amadeus_logo(ctx); - - let mut style = (*ctx.style()).clone(); - style.text_styles = utils::font::get_font_styles(); - ctx.set_style(style); - - ret.apply_settings(); - - Box::new(ret) - } - - fn load_amadeus_logo(&mut self, ctx: &egui::Context) { - let (logo_buffer, [size_x, size_y]) = utils::get_icon_as_dynamic_image(); - let pixels = logo_buffer.as_flat_samples(); - let logo_texture = egui::ColorImage::from_rgba_unmultiplied( - [size_x as usize, size_y as usize], - pixels.as_slice(), - ); - self.amadeus_logo_texture = - Some(ctx.load_texture("amadeus-logo", logo_texture, egui::TextureOptions::LINEAR)); - } - - fn collect_fulfilled_actions(&mut self) -> Vec<(u64, actions::Action)> { - let mut ret = self.lektord_queue.fulfilled_actions(); - ret.extend(self.lektord_historic.fulfilled_actions()); - ret.extend(self.lektord_search_results.fulfilled_actions()); - ret.extend(self.playlist_store.fulfilled_actions()); - ret - } - - fn render_side_panel_main_view_button( - &mut self, - ui: &mut egui::Ui, - title: &str, - view: utils::AmadeusMainView, - ) { - ui.add_space(constants::PADDING); - let selected = view == self.config.main_panel_view; - if selected { - ui.colored_label(constants::get_text_color(self.config.dark_mode), title); - } else { - let the_btn = egui::Button::new(title).frame(false); - if ui.add(the_btn).clicked() { - self.config.main_panel_view = view; - } - } - } - - fn render_side_panel(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - if self.config.side_panel_show { - egui::SidePanel::left("LEFT_PANEL") - .resizable(true) - .show(ctx, |ui| { - ui.vertical(|ui| { - use utils::AmadeusMainView::*; - ui.style_mut().override_text_style = Some(utils::font::heading1()); - self.render_side_panel_main_view_button(ui, "🎵 Queue", Queue); - self.render_side_panel_main_view_button(ui, "📚 Historic", Historic); - self.render_side_panel_main_view_button(ui, "🔍 Search", SearchResults); - ui.add_space(constants::PADDING); - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.separator(); - }); - self.playlist_store.render(ui, self.config.dark_mode); - }); - } - if self.playlist_store.refresh_playlists() { - self.actions.push(actions::Action::RefreshPlaylists); - } - } - - fn render_central_panel(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { - use utils::AmadeusMainView::*; - match self.config.main_panel_view { - Queue => self.lektord_queue.render(ui, self.config.dark_mode), - Historic => self.lektord_historic.render(ui, self.config.dark_mode), - SearchResults => { - ui.style_mut().override_text_style = Some(utils::font::heading1()); - ui.label("Database search query"); - ui.add_space(constants::PADDING); - - let response = ui.add( - egui::TextEdit::singleline(&mut self.lektord_search_query) - .desired_width(f32::INFINITY), - ); - self.lektord_updated_query |= - response.lost_focus() || ui.input().key_pressed(egui::Key::Enter); - ui.add_space(constants::PADDING * 2.); - - self.lektord_search_results - .render(ui, self.config.dark_mode); - } - } - }); - } - - fn render_bottom_panel(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - if !(self.lektord_status.is_some() && self.lektord_current.is_some()) { - return; - } - - let status = self.lektord_status.as_ref().unwrap(); - let current = self.lektord_current.as_ref().unwrap(); - - let progress = (status.elapsed() as f64 / status.duration() as f64) as f32; - let text_duration = format!( - "{:02}:{:02}", - status.duration() / 60, - status.duration() % 60 - ); - - egui::TopBottomPanel::bottom("FOOTER") - .max_height(constants::BOTTOM_PANEL_MAX_SIZE) - .min_height(constants::BOTTOM_PANEL_MAX_SIZE) - .resizable(false) - .show(ctx, |ui| { - // The text... - ui.horizontal(|ui| { - KaraCard::new(Kara { - id: status.songid().unwrap() as u32, - source_name: current.source().to_string(), - song_type: current.song_type().to_string(), - language: current.language().to_string(), - category: current.category().to_string(), - title: current.title().to_string(), - song_number: current.song_number().map(|x| x as u32), - author: current.author().to_string(), - is_available: true, - }) - .render_compact(ui, self.config.dark_mode); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { - ui.style_mut().override_text_style = Some(utils::font::heading3()); - ui.add_space(constants::PADDING * 2.); - ui.label(text_duration); - }); - }); - // The progress bar... - let style = ui.style().clone(); - ui.style_mut().override_text_style = Some(utils::font::small()); - ui.style_mut().visuals.override_text_color = - Some(constants::get_text_color(self.config.dark_mode)); - ui.add(widgets::progress_bar(self.config.dark_mode, progress)); - ui.set_style(style); - }); - } - - fn render_top_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - egui::TopBottomPanel::top("MENU").show(ctx, |ui| { - ui.add_space(constants::TOP_PANEL_PADDING * 2.); - egui::menu::bar(ui, |ui| { - ui.with_layout(egui::Layout::left_to_right(egui::Align::Min), |ui| { - ui.style_mut().override_text_style = Some(utils::font::heading1()); - if ui.add(egui::Button::new("Amadeus").frame(false)).clicked() { - self.config.side_panel_show = !self.config.side_panel_show; - } - - ui.add(egui::Separator::default()); - egui::menu::menu_button(ui, "⚡", |ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.add(egui::Button::new("Connect lektord").wrap(false)) - .clicked() - .then(|| { - self.actions.push(actions::Action::ConnectToLektord); - ui.close_menu(); - }); - ui.add(egui::Button::new("Disconnect lektord").wrap(false)) - .clicked() - .then(|| { - self.actions.push(actions::Action::DisconnectFromLektord); - ui.close_menu(); - }); - }) - .response - .on_hover_text("Action menu"); - ui.add(egui::Separator::default()); - - ui.button("⚙") - .on_hover_text("Settings window") - .clicked() - .then(|| self.need_settings_window = true); - - ui.button("☕") - .on_hover_text("About window") - .clicked() - .then(|| self.need_about_window = true); - - ui.add(egui::Separator::default()); - ui.button("⏪") - .on_hover_text("Previous") - .clicked() - .then(|| self.actions.push(actions::Action::PlaybackPrevious)); - match self.lektord_state { - LektorState::Stopped | LektorState::Pause(_) => ui - .button("▶") - .on_hover_text("Play") - .clicked() - .then(|| self.actions.push(actions::Action::PlaybackPlay)), - LektorState::Play(_) => ui - .button("⏸") - .on_hover_text("Pause") - .clicked() - .then(|| self.actions.push(actions::Action::PlaybackPause)), - }; - ui.button("⏩") - .on_hover_text("Next") - .clicked() - .then(|| self.actions.push(actions::Action::PlaybackNext)); - }); - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - if ui.add(egui::Button::new("❌")).clicked() { - frame.close(); - } - - ui.style_mut().override_text_style = Some(utils::font::small_body()); - let frame_duration = self - .begin_render_instant - .duration_since(self.last_render_instant) - .unwrap_or_default() - .as_millis(); - let fps = 1000. / frame_duration as f64; - ui.label(format!("{fps:02.1}fps | {frame_duration:02}ms")); - let (drag_id, drag_space) = ui.allocate_space(ui.available_size()); - ui.interact(drag_space, drag_id, egui::Sense::drag()) - .dragged() - .then(|| frame.drag_window()); - }); - }); - ui.add_space(constants::TOP_PANEL_PADDING); - }); - } - - fn set_visuals(&mut self, ctx: &egui::Context) { - ctx.request_repaint(); - let mut visuals = - either!(self.config.dark_mode => egui::Visuals::dark(); egui::Visuals::light()); - - visuals.window_rounding = egui::Rounding::none(); - visuals.widgets.active.rounding = egui::Rounding::none(); - visuals.widgets.noninteractive.rounding = egui::Rounding::none(); - visuals.widgets.inactive.rounding = egui::Rounding::none(); - visuals.widgets.hovered.rounding = egui::Rounding::none(); - visuals.widgets.open.rounding = egui::Rounding::none(); - - visuals.hyperlink_color = constants::get_accent_color(self.config.dark_mode); - - ctx.set_visuals(visuals); - } - - /// Handle the events recieved from the deamons that connect to lektord. - fn handle_deamons_events(&mut self) { - if let Some(((_, deamon), _)) = self.deamon.as_mut() { - while let Ok(event) = deamon.try_recv() { - use LektorResponse::*; - match event { - PlaybackStatus(_) => todo!(), - CurrentKara(_) => todo!(), - PlaylistSet(set) => { - self.playlist_store.clear_playlists(); - set.into_iter() - .for_each(|plt| self.playlist_store.create(Playlist { name: plt })) - } - EmptyResponse(_) => todo!(), - KaraSet(_) => todo!(), - } - } - } - - if let Some(((deamon,), _)) = self.status_deamon.as_mut() { - while let Ok((status, current)) = deamon.try_recv() { - // Sanitize some values, if the time/elapsed/duration is not - // valid, we set everything to None/zero. - self.lektord_status = then_some!(status.verify() => status); - self.lektord_current = - then_some!(current.is_some() => current.unwrap().maybe_into_inner()).flatten(); - } - } - } - - /// Handle quits/exits of the deamons - fn handle_deamons_exit(&mut self) { - // Handle the deamon closing process. - if let Some((_, status_deamon)) = &mut self.status_deamon { - if status_deamon.should_quit() { - self.lektord_status = None; - self.lektord_current = None; - status_deamon.join(); - self.status_deamon = None; - } - }; - if let Some((_, deamon)) = &mut self.deamon { - if deamon.should_quit() { - deamon.join(); - self.deamon = None; - } - }; - } - - /// Handle actions recieved from the user. May communicate to the deamons - /// and send infos/commands to lektord if they are available. - fn handle_action(&mut self) { - use actions::Action::*; - - // Handle actions on lektor items - for (id, act) in self.collect_fulfilled_actions() { - match act { - OpenPlaylist => self.playlist_store.show_playlist(id), - ClearPlaylistContent => self.playlist_store.clear_playlist(id), - PlayFromKara - | DeleteKaraFromQueue - | InsertKaraInQueue - | AddKaraToQueue - | AddKaraToPlaylist - | DeleteKaraFromPlaylist(_) - | AddPlaylistToQueue - | InsertPlaylistToQueue => { - debug!("execute action {act:?} on lektor item with id {id}") - } - - // This should not occure on items because they are global - // actions. - RefreshPlaylists - | PlaybackPrevious - | PlaybackPlay - | PlaybackPause - | PlaybackNext - | ConnectToLektord - | DisconnectFromLektord => unreachable!(), - } - } - - // Handle top level actions - for act in self.actions.drain(..) { - match act { - ConnectToLektord => { - if self.status_deamon.is_none() { - let connexion = StatusDeamon::spawn( - self.config.lektord_hostname.clone(), - self.config.lektord_port.as_integer() as i16, - ); - if let Ok(connexion) = connexion { - self.status_deamon = Some(connexion); - } - } - if self.deamon.is_none() { - let connexion = CommandDeamon::spawn( - self.config.lektord_hostname.clone(), - self.config.lektord_port.as_integer() as i16, - ); - if let Ok(connexion) = connexion { - self.deamon = Some(connexion); - } - } - } - DisconnectFromLektord => { - debug!("asked to close the deamons"); - if let Some((_, status_deamon)) = &self.status_deamon { - status_deamon.quit(); - } - if let Some((_, deamon)) = &self.deamon { - deamon.quit(); - } - break; - } - - PlaybackPrevious | PlaybackPlay | PlaybackPause | PlaybackNext => { - debug!("Execute action {act:?} on lektor") - } - - RefreshPlaylists => { - if let Some(((sender, _), _)) = &self.deamon { - handle_sender_error(sender.send(LektorQuery::ListAllPlaylists)) - } - } - - _ => unreachable!(), - } - } - } - - fn apply_settings(&mut self) { - self.lektord_queue - .with_max_content(self.config.ui_max_queue_items.as_integer()); - self.lektord_historic - .with_max_content(self.config.ui_max_history_items.as_integer()); - self.lektord_search_results - .with_max_content(self.config.ui_max_search_items.as_integer()); - } -} - -impl App for Amadeus<'_> { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - self.begin_render_instant = time::SystemTime::now(); - ctx.request_repaint(); - self.set_visuals(ctx); - - self.render_top_panel(ctx, frame); - self.render_side_panel(ctx, frame); - self.render_central_panel(ctx, frame); - self.render_bottom_panel(ctx, frame); - - self.playlist_store - .render_playlist_contents(ctx, self.config.dark_mode); - widgets::render_about_window( - &mut self.need_about_window, - self.config.dark_mode, - ctx, - self.amadeus_logo_texture.as_ref().unwrap(), - ); - self.config.render_settings_window( - ctx, - &mut self.need_settings_window, - &mut self.has_config_changed, - ); - - self.last_render_instant = self.begin_render_instant; - - self.handle_deamons_events(); - self.handle_deamons_exit(); - self.handle_action(); - - if self.has_config_changed { - self.apply_settings(); - } - } - - fn save(&mut self, storage: &mut dyn eframe::Storage) { - if self.has_config_changed { - eframe::set_value(storage, "amadeus-rs-config", &self.config); - self.has_config_changed = false; - } - } - - fn persist_egui_memory(&self) -> bool { - true - } - - fn persist_native_window(&self) -> bool { - true - } - - fn auto_save_interval(&self) -> time::Duration { - time::Duration::from_secs(1) - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/cards/card.rs b/src/rust/amadeus-rs/amadeus/src/cards/card.rs deleted file mode 100644 index ee9ea5e4b788f1a973b17d76fd95d49a77cfd744..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/cards/card.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::{constants, utils, widgets::render_action_menu}; -use amadeus_lib::actions; -use amadeus_macro::either; -use lkt_lib::*; - -/// A simple card trait -pub trait Card<'a, LktType: LektorType<'a>>: ToString + Clone { - /// Create an instance of the card from the lektor type - fn new(lkt_type: LktType) -> Self; - - /// Render the card to the screen - fn render(&mut self, ui: &mut egui::Ui, dark_mode: bool, actions: &[actions::Action]); - - /// Render the card to the screen. Variant to try to render the element in a - /// more compact way. - fn render_compact(&self, ui: &mut egui::Ui, dark_mode: bool); - - /// Returns the Ids of the activated actions. - fn fulfilled_actions(&mut self) -> Vec<actions::Action>; - - /// Get the unique id from the inner lkt type. - fn unique_id(&self) -> u64; - - /// The card can indicate if it need to be separator by separators - const NEED_SEPARATOR: bool; - - /// Indicate if there is a need to have a separator between the header and - /// the first row. - const NEED_HEADER_SEPARATOR: bool; - - /// Set the spacing to insert after the last card is rendered. - const BOTTOM_SPACE: Option<f32>; -} - -#[derive(Clone)] -pub struct KaraCard { - pub inner: Kara, - actions: Vec<actions::Action>, -} - -#[derive(Clone)] -pub struct PlaylistCard { - pub inner: Playlist, - actions: Vec<actions::Action>, -} - -impl ToString for KaraCard { - fn to_string(&self) -> String { - format!( - "{} - {} / {} - {}{} - {} [{}]{}", - self.inner.category, - self.inner.language, - self.inner.source_name, - self.inner.song_type, - match self.inner.song_number { - Some(num) => num.to_string(), - None => "".to_string(), - }, - self.inner.title, - self.inner.author, - either!(self.inner.is_available => ""; " (unavailable)") - ) - } -} - -impl ToString for PlaylistCard { - fn to_string(&self) -> String { - self.inner.name.clone() - } -} - -impl AsRef<str> for PlaylistCard { - fn as_ref(&self) -> &str { - &self.inner.name - } -} - -impl Card<'_, Kara> for KaraCard { - const NEED_SEPARATOR: bool = true; - const NEED_HEADER_SEPARATOR: bool = true; - const BOTTOM_SPACE: Option<f32> = Some(constants::BOTTOM_PANEL_MAX_SIZE); - - fn new(inner: Kara) -> Self { - Self { - inner, - actions: Vec::with_capacity(5), - } - } - - fn render(&mut self, ui: &mut egui::Ui, dark_mode: bool, actions: &[actions::Action]) { - ui.add_space(constants::PADDING); - static MIN_WIDTH_FOR_ADDITIONAL_INFOS: f32 = 1024.; - let song = format!( - "{} - {}{} - {}", - self.inner.source_name, - self.inner.song_type, - match self.inner.song_number { - Some(num) => num.to_string(), - None => "".to_string(), - }, - self.inner.title - ); - ui.horizontal(|ui| { - ui.add_space(constants::PADDING * 3.); - let left_space = ui.available_width(); - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.colored_label( - constants::get_text_color(dark_mode), - format!("{} - {}", self.inner.category, self.inner.language), - ); - if left_space >= MIN_WIDTH_FOR_ADDITIONAL_INFOS { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { - ui.label(format!("kara by {}", self.inner.author)); - }); - } - }); - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::heading2()); - render_action_menu(ui, actions, "▶", &mut self.actions); - ui.colored_label(constants::get_text_color(dark_mode), song); - ui.style_mut().override_text_style = Some(utils::font::body()); - if left_space >= MIN_WIDTH_FOR_ADDITIONAL_INFOS { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { - if !self.inner.is_available { - ui.colored_label( - constants::get_accent_color(dark_mode), - " (unavailable)", - ); - } - ui.label(format!(" #{}", self.inner.id)); - }); - } - }); - }); - }); - ui.add_space(constants::PADDING); - } - - fn render_compact(&self, ui: &mut egui::Ui, dark_mode: bool) { - let header = format!( - "{} - {} by {}", - self.inner.category, self.inner.language, self.inner.author - ); - let song = format!( - "{} - {}{} - {}", - self.inner.source_name, - self.inner.song_type, - match self.inner.song_number { - Some(num) => num.to_string(), - None => "".to_string(), - }, - self.inner.title - ); - ui.horizontal(|ui| { - ui.add_space(constants::PADDING * 3.); - ui.vertical(|ui| { - ui.add_space(constants::PADDING * 2.); - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::heading3()); - ui.colored_label(constants::get_text_color(dark_mode), header); - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.label(egui::RichText::new(format!("#{}", self.inner.id))); - }); - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::heading1()); - ui.colored_label(constants::get_text_color(dark_mode), song); - if !self.inner.is_available { - ui.colored_label(constants::get_accent_color(dark_mode), " (unavailable)"); - } - }); - }); - }); - } - - fn fulfilled_actions(&mut self) -> Vec<actions::Action> { - let ret = self.actions.clone(); - self.actions.clear(); - ret - } - - fn unique_id(&self) -> u64 { - self.inner.unique_id() - } -} - -impl Card<'_, Playlist> for PlaylistCard { - const NEED_SEPARATOR: bool = false; - const NEED_HEADER_SEPARATOR: bool = false; - const BOTTOM_SPACE: Option<f32> = None; - - fn new(inner: Playlist) -> Self { - Self { - inner, - actions: Vec::with_capacity(5), - } - } - - fn render(&mut self, ui: &mut egui::Ui, dark_mode: bool, actions: &[actions::Action]) { - ui.horizontal(|ui| { - ui.add_space(constants::PADDING * 2.); - ui.style_mut().override_text_style = Some(utils::font::body()); - render_action_menu(ui, actions, "▶", &mut self.actions); - ui.colored_label( - constants::get_text_color(dark_mode), - self.inner.name.to_string(), - ); - }); - ui.add_space(constants::PADDING); - } - - fn render_compact(&self, ui: &mut egui::Ui, dark_mode: bool) { - ui.horizontal(|ui| { - ui.colored_label( - constants::get_text_color(dark_mode), - self.inner.name.to_string(), - ); - }); - } - - fn fulfilled_actions(&mut self) -> Vec<actions::Action> { - let ret = self.actions.clone(); - self.actions.clear(); - ret - } - - fn unique_id(&self) -> u64 { - self.inner.unique_id() - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/cards/collection.rs b/src/rust/amadeus-rs/amadeus/src/cards/collection.rs deleted file mode 100644 index 8dc93ee9be04ff1fa2145fc431eb21bb66ef7aee..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/cards/collection.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::{cards::card::*, constants, utils}; -use amadeus_lib::actions; -use amadeus_macro::either; -use lkt_lib::{Kara, LektorType}; -use std::{borrow::Borrow, cmp::min, marker::PhantomData}; - -pub type KaraCardCollection<'a> = CardCollection<'a, KaraCard, Kara>; - -/// Struct to hold a collection of cards -pub struct CardCollection<'a, CardType, LktType> -where - CardType: Card<'a, LktType>, - LktType: LektorType<'a>, -{ - name: String, - content: Vec<CardType>, - actions: Vec<actions::Action>, - max_content: Option<usize>, - phantom: PhantomData<&'a LktType>, -} - -impl<'a, CardType: Card<'a, LktType>, LktType: LektorType<'a>> ToString - for CardCollection<'a, CardType, LktType> -{ - fn to_string(&self) -> String { - format!( - "Collection {}: {}", - self.name, - self.content - .iter() - .map(|card| card.to_string()) - .fold(String::new(), |a, b| a + ", " + b.borrow()) - ) - } -} - -impl<'a, CardType: Card<'a, LktType>, LktType: LektorType<'a>> - CardCollection<'a, CardType, LktType> -{ - pub fn new(name: String) -> Self { - Self { - name, - max_content: None, - content: vec![], - actions: vec![], - phantom: PhantomData, - } - } - - pub fn with_max_content(&mut self, max_content: usize) -> &mut Self { - self.max_content = either!(max_content != 0 => Some(max_content); None); - self - } - - pub fn add_card(&mut self, card: CardType) -> &mut Self { - self.content.push(card); - self - } - - pub fn add_action(mut self, act: actions::Action) -> Self { - self.actions.push(act); - self - } - - pub fn empty(&self) -> bool { - self.content.is_empty() - } - - pub fn fulfilled_actions(&mut self) -> Vec<(u64, actions::Action)> { - let mut ret = Vec::new(); - for card in &mut self.content { - let actions = card.fulfilled_actions(); - let actions_count = actions.len(); - if actions_count != 0 { - let id = card.unique_id(); - ret.reserve(ret.len() + actions_count); - for action in actions { - ret.push((id, action)); - } - } - } - ret - } - - pub fn render(&mut self, ui: &mut egui::Ui, dark_mode: bool) { - ui.vertical(|ui| { - ui.add_space(constants::PADDING); - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::heading1()); - ui.colored_label(constants::get_text_color(dark_mode), self.name.to_string()); - ui.style_mut().override_text_style = Some(utils::font::small_body()); - ui.label(format!(" ({} elements)", self.content.len())); - }); - if CardType::NEED_HEADER_SEPARATOR { - ui.add_space(constants::PADDING); - ui.add(egui::Separator::default()); - } - if self.empty() { - ui.vertical_centered_justified(|ui| { - ui.add_space(constants::PADDING * 2.); - ui.add(egui::Spinner::new()); - ui.allocate_space(ui.available_size()) - }); - } else { - egui::ScrollArea::vertical() - .hscroll(false) - .always_show_scroll(false) - .max_width(f32::INFINITY) - .show(ui, |ui| { - ui.horizontal(|ui| ui.add_space(ui.available_width())); - let upper_bound = match self.max_content { - None => self.content.len(), - Some(x) => min(self.content.len(), x), - }; - for card in &mut self.content[0..upper_bound] { - card.render(ui, dark_mode, &self.actions); - if CardType::NEED_SEPARATOR { - ui.add(egui::Separator::default()); - } - } - if let Some(space) = CardType::BOTTOM_SPACE { - ui.add_space(space); - } - }); - ui.allocate_space(ui.available_size()); - } - }); - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/cards/mod.rs b/src/rust/amadeus-rs/amadeus/src/cards/mod.rs deleted file mode 100644 index 46282020633d111cf7b9702b37782456608d4027..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/cards/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod card; -mod collection; - -pub use card::Card; -pub use card::KaraCard; -pub use card::PlaylistCard; - -pub use collection::CardCollection; -pub use collection::KaraCardCollection; diff --git a/src/rust/amadeus-rs/amadeus/src/constants.rs b/src/rust/amadeus-rs/amadeus/src/constants.rs deleted file mode 100644 index 41a90eefb0d5579509bb7a4f90438d81e41bf129..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/constants.rs +++ /dev/null @@ -1,41 +0,0 @@ -use amadeus_macro::either; -use eframe::egui::Color32; - -pub const PADDING: f32 = 6.0; -pub const TOP_PANEL_PADDING: f32 = 0.5; -pub const BOTTOM_PANEL_MAX_SIZE: f32 = 90.; -const WHITE: Color32 = Color32::from_rgb(255, 255, 255); -const BLACK: Color32 = Color32::from_rgb(0, 0, 0); -const CYAN: Color32 = Color32::from_rgb(0, 255, 255); -const RED: Color32 = Color32::from_rgb(255, 0, 0); - -pub fn get_fill_color(dark_mode: bool) -> Color32 { - let base_color = get_accent_color(dark_mode); - if dark_mode { - base_color - } else { - let factor = 0.69_f32; - Color32::from_rgb( - (base_color.r() as f32 * factor) as u8, - (base_color.g() as f32 * factor) as u8, - (base_color.b() as f32 * factor) as u8, - ) - } -} - -pub fn get_text_color(dark_mode: bool) -> Color32 { - either!(dark_mode => WHITE; BLACK) -} - -pub fn get_accent_color(dark_mode: bool) -> Color32 { - either!(dark_mode => get_darker_color(CYAN); RED) -} - -fn get_darker_color(color: Color32) -> Color32 { - let factor = 0.69_f32; - Color32::from_rgb( - (color.r() as f32 * factor) as u8, - (color.g() as f32 * factor) as u8, - (color.b() as f32 * factor) as u8, - ) -} diff --git a/src/rust/amadeus-rs/amadeus/src/logger.rs b/src/rust/amadeus-rs/amadeus/src/logger.rs deleted file mode 100644 index dc9bc4e5eb6d075ea04fe23e6ac86b96e0001a66..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/logger.rs +++ /dev/null @@ -1,63 +0,0 @@ -use lazy_static::lazy_static; -use log::{Level, Metadata, Record, SetLoggerError}; -use std::sync::{Arc, Mutex}; - -struct SimpleLogger { - level: Arc<Mutex<Level>>, -} - -#[repr(transparent)] -struct SimpleLoggerRef { - pub inner: SimpleLogger, -} - -lazy_static! { - static ref LOGGER: SimpleLoggerRef = SimpleLoggerRef { - inner: SimpleLogger { - level: Arc::new(Mutex::new(Level::Debug)), - } - }; -} - -impl log::Log for SimpleLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - match self.level.lock() { - Ok(level) => metadata.level() <= *level, - Err(e) => panic!("Failed to lock the log level mutex: {e}"), - } - } - - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - eprintln!( - "{} - {} - {}", - record.target(), - record.level(), - record.args() - ); - } - } - - fn flush(&self) {} -} - -pub fn init() -> Result<(), SetLoggerError> { - let default_level = match LOGGER.inner.level.lock() { - Ok(lvl) => lvl, - Err(e) => panic!("Failed to lock the log level mutex: {e}"), - }; - - log::set_logger(&LOGGER.inner).map(|()| { - log::set_max_level(default_level.to_level_filter()); - }) -} - -pub fn set_level(level: Level) { - match LOGGER.inner.level.lock() { - Ok(mut inner_level) => { - *inner_level = level; - log::set_max_level(level.to_level_filter()); - } - Err(e) => panic!("Failed to lock the log level mutex: {e}"), - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/main.rs b/src/rust/amadeus-rs/amadeus/src/main.rs deleted file mode 100644 index a29cb0d438445fd1179774a3454831e9fba7e064..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/main.rs +++ /dev/null @@ -1,29 +0,0 @@ -mod amadeus; -mod cards; -mod constants; -mod logger; -mod utils; -mod widgets; - -use eframe::egui::Vec2; - -fn main() { - logger::set_level(log::Level::Debug); - if let Err(e) = logger::init() { - panic!("Failed to install logger: {e}") - } - - eframe::run_native( - "amadeus-rs", - eframe::NativeOptions { - maximized: false, - decorated: true, - drag_and_drop_support: true, - resizable: true, - initial_window_size: Some(Vec2::new(800., 600.)), - icon_data: Some(utils::get_icon_data()), - ..Default::default() - }, - Box::new(|cc| amadeus::Amadeus::create(cc)), - ); -} diff --git a/src/rust/amadeus-rs/amadeus/src/utils/config.rs b/src/rust/amadeus-rs/amadeus/src/utils/config.rs deleted file mode 100644 index 6f8189dd1760d03e73ee714b7515f465a4552fd7..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/utils/config.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::widgets; - -/// Indicate which view should be displayed in the central panel of Amadeus' -/// main window. -#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)] -pub enum AmadeusMainView { - /// The main panel should show the queue of lektord, i.e. the kara that will - /// be played. - Queue, - - /// The main panel should show the kara that where played by lektord. - Historic, - - /// The main panel should show the search view. - SearchResults, -} - -#[derive(serde::Serialize, serde::Deserialize)] -pub struct AmadeusConfig { - pub dark_mode: bool, - pub admin_password: String, - pub lektord_hostname: String, - pub lektord_port: super::NetworkPort, - pub lektord_auto_reconnect: bool, - pub main_panel_view: AmadeusMainView, - pub side_panel_show: bool, - pub ui_max_queue_items: super::IntegerTextBuffer<usize>, - pub ui_max_search_items: super::IntegerTextBuffer<usize>, - pub ui_max_history_items: super::IntegerTextBuffer<usize>, -} - -impl Default for AmadeusMainView { - fn default() -> Self { - Self::Queue - } -} - -impl Default for AmadeusConfig { - fn default() -> Self { - Self { - dark_mode: true, - admin_password: "".to_owned(), - lektord_hostname: "localhost".to_owned(), - lektord_port: super::NetworkPort::from("6600"), - lektord_auto_reconnect: false, - main_panel_view: Default::default(), - side_panel_show: true, - ui_max_queue_items: super::IntegerTextBuffer::from("100"), - ui_max_search_items: super::IntegerTextBuffer::from("100"), - ui_max_history_items: super::IntegerTextBuffer::from("100"), - } - } -} - -impl AmadeusConfig { - pub fn render_settings_window( - &mut self, - ctx: &egui::Context, - show: &mut bool, - changed: &mut bool, - ) { - widgets::WindowBuilder::new("Settings", self.dark_mode).show(show, ctx, |ui| { - ui.separator(); - widgets::add_heading2_label(ui, "☰ Lektord deamon"); - widgets::add_labelled_edit_line( - ui, - "Hostname ", - &mut self.lektord_hostname, - changed, - ); - widgets::add_labelled_edit_line( - ui, - "Port ", - &mut self.lektord_port, - changed, - ); - widgets::add_labelled_toggle_switch( - ui, - self.dark_mode, - "Auto-reconnect", - &mut self.lektord_auto_reconnect, - changed, - ); - - ui.separator(); - widgets::add_heading2_label(ui, "☰ Admin user"); - widgets::add_labelled_password( - ui, - "Admin pwd ", - &mut self.admin_password, - changed, - ); - - ui.separator(); - widgets::add_heading2_label(ui, "☰ UI"); - widgets::add_labelled_toggle_switch( - ui, - self.dark_mode, - "Dark theme", - &mut self.dark_mode, - changed, - ); - widgets::add_labelled_toggle_switch( - ui, - self.dark_mode, - "Show side panel", - &mut self.side_panel_show, - changed, - ); - widgets::add_labelled_edit_line( - ui, - "Max queue size ", - &mut self.ui_max_queue_items, - changed, - ); - widgets::add_labelled_edit_line( - ui, - "Max search size ", - &mut self.ui_max_search_items, - changed, - ); - widgets::add_labelled_edit_line( - ui, - "Max history size ", - &mut self.ui_max_history_items, - changed, - ); - }); - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/utils/font.rs b/src/rust/amadeus-rs/amadeus/src/utils/font.rs deleted file mode 100644 index c75eeebf28ad1f83e08dc6d04ea95467704ae80b..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/utils/font.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::collections::BTreeMap; - -use egui::{ - FontData, FontDefinitions, - FontFamily::{Monospace, Proportional}, - FontId, TextStyle, -}; - -#[inline] -pub fn heading1() -> TextStyle { - TextStyle::Heading -} - -#[inline] -pub fn heading2() -> TextStyle { - TextStyle::Name("Heading2".into()) -} - -#[inline] -pub fn heading3() -> TextStyle { - TextStyle::Name("ContextHeading".into()) -} - -#[inline] -pub fn body() -> TextStyle { - TextStyle::Body -} - -#[inline] -pub fn small_body() -> TextStyle { - TextStyle::Name("SmallBody".into()) -} - -#[inline] -pub fn monospace() -> TextStyle { - TextStyle::Monospace -} - -#[inline] -pub fn button() -> TextStyle { - TextStyle::Button -} - -#[inline] -pub fn small() -> TextStyle { - TextStyle::Small -} - -pub fn get_font_definitions() -> FontDefinitions { - static FONT_NAME: &str = "UbuntuMono"; - static FONT_DATA: &[u8] = include_bytes!("../../../rsc/UbuntuMono-Regular.ttf"); - static CJK_NAME: &str = "IPAMincho"; - static CJK_DATA: &[u8] = include_bytes!("../../../rsc/IPAMincho.ttf"); - let mut fonts = FontDefinitions::default(); - - fonts - .font_data - .insert(FONT_NAME.to_owned(), FontData::from_static(FONT_DATA)); - fonts - .font_data - .insert(CJK_NAME.to_owned(), FontData::from_static(CJK_DATA)); - - fonts - .families - .get_mut(&Proportional) - .unwrap() - .insert(0, FONT_NAME.to_owned()); - fonts - .families - .get_mut(&Proportional) - .unwrap() - .push(CJK_NAME.to_owned()); - - fonts - .families - .get_mut(&Monospace) - .unwrap() - .insert(0, FONT_NAME.to_owned()); - fonts - .families - .get_mut(&Monospace) - .unwrap() - .push(CJK_NAME.to_owned()); - - fonts -} - -pub fn get_font_styles() -> BTreeMap<TextStyle, FontId> { - let styles = [ - (heading1(), FontId::new(30.0, Proportional)), - (heading2(), FontId::new(25.0, Proportional)), - (heading3(), FontId::new(23.0, Proportional)), - (body(), FontId::new(18.0, Proportional)), - (monospace(), FontId::new(14.0, Proportional)), - (small_body(), FontId::new(14.0, Proportional)), - (button(), FontId::new(14.0, Proportional)), - (small(), FontId::new(10.0, Proportional)), - ]; - styles.into() -} diff --git a/src/rust/amadeus-rs/amadeus/src/utils/int_text_buffer.rs b/src/rust/amadeus-rs/amadeus/src/utils/int_text_buffer.rs deleted file mode 100644 index c53348eaed65002162508b2d128050c95bb1fbee..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/utils/int_text_buffer.rs +++ /dev/null @@ -1,132 +0,0 @@ -use egui::TextBuffer; -use serde::{Deserialize, Serialize}; -use std::{fmt, marker::PhantomData, str::FromStr}; - -#[derive(Serialize, Deserialize)] -pub struct IntegerTextBuffer<T: private::Unsigned + private::Zero<T>> { - buffer: String, - phantom: PhantomData<T>, -} - -impl<T: private::Unsigned + private::Zero<T>> IntegerTextBuffer<T> { - pub fn as_integer(&self) -> T - where - <T as FromStr>::Err: std::fmt::Display, - { - if self.buffer.is_empty() { - T::ZERO - } else { - match self.buffer.parse::<T>() { - Ok(int) => int, - Err(e) => panic!("Failed to parse integer: {}", e), - } - } - } - - fn remove_all_non_digit_chars(&mut self) { - self.buffer = self.buffer.chars().filter(|c| c.is_ascii_digit()).collect(); - } -} - -impl<T: private::Unsigned + private::Zero<T>> fmt::Display for IntegerTextBuffer<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.buffer) - } -} - -impl<T: private::Unsigned + private::Zero<T>> From<&str> for IntegerTextBuffer<T> { - fn from(text: &str) -> Self { - Self { - buffer: text.to_string(), - phantom: PhantomData, - } - } -} - -impl<T: private::Unsigned + private::Zero<T>> AsRef<str> for IntegerTextBuffer<T> { - fn as_ref(&self) -> &str { - self.buffer.as_str() - } -} - -impl<T: private::Unsigned + private::Zero<T>> TextBuffer for IntegerTextBuffer<T> { - fn is_mutable(&self) -> bool { - true - } - - fn as_str(&self) -> &str { - self.as_ref() - } - - fn char_range(&self, char_range: std::ops::Range<usize>) -> &str { - assert!(char_range.start <= char_range.end); - let start_byte = self.byte_index_from_char_index(char_range.start); - let end_byte = self.byte_index_from_char_index(char_range.end); - &self.as_str()[start_byte..end_byte] - } - - fn insert_text(&mut self, text: &str, char_index: usize) -> usize { - let ret = self.buffer.insert_text(text, char_index); - self.remove_all_non_digit_chars(); - ret - } - - fn delete_char_range(&mut self, char_range: std::ops::Range<usize>) { - self.buffer.delete_char_range(char_range); - } - - fn clear(&mut self) { - self.delete_char_range(0..self.as_ref().len()); - } - - fn replace(&mut self, text: &str) { - self.clear(); - self.insert_text(text, 0); - } - - fn take(&mut self) -> String { - let s = self.as_ref().to_owned(); - self.clear(); - s - } -} - -mod private { - use std::str::FromStr; - - pub trait Unsigned: FromStr {} - impl Unsigned for u8 {} - impl Unsigned for u16 {} - impl Unsigned for u32 {} - impl Unsigned for u64 {} - impl Unsigned for u128 {} - impl Unsigned for usize {} - - pub trait Zero<T: Unsigned> { - const ZERO: T; - } - - impl Zero<u8> for u8 { - const ZERO: u8 = 0u8; - } - - impl Zero<u16> for u16 { - const ZERO: u16 = 0u16; - } - - impl Zero<u32> for u32 { - const ZERO: u32 = 0u32; - } - - impl Zero<u64> for u64 { - const ZERO: u64 = 0u64; - } - - impl Zero<u128> for u128 { - const ZERO: u128 = 0u128; - } - - impl Zero<usize> for usize { - const ZERO: usize = 0usize; - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/utils/mod.rs b/src/rust/amadeus-rs/amadeus/src/utils/mod.rs deleted file mode 100644 index a7606af3abbee4305788870df6ce2e5d31de1571..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/utils/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub(crate) mod font; - -mod config; -pub(crate) use config::AmadeusConfig; -pub(crate) use config::AmadeusMainView; - -mod int_text_buffer; -pub(crate) use int_text_buffer::IntegerTextBuffer; -pub(crate) type NetworkPort = IntegerTextBuffer<u16>; - -pub(crate) fn get_icon_as_dynamic_image() -> (image::RgbaImage, [u32; 2]) { - static LOGO_SIDE: u32 = 48u32; - let logo_data = image::load_from_memory(include_bytes!("../../../rsc/AmadeusLogo.jpg")) - .expect("Failed to load Amadeus Logo") - .thumbnail(LOGO_SIDE, LOGO_SIDE); - let size = [logo_data.width(), logo_data.height()]; - let logo_buffer = logo_data.to_rgba8(); - (logo_buffer, size) -} - -pub(crate) fn get_icon_data() -> eframe::IconData { - let (logo_buffer, [size_x, size_y]) = get_icon_as_dynamic_image(); - eframe::IconData { - rgba: logo_buffer.to_vec(), - height: size_y, - width: size_x, - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/about_window.rs b/src/rust/amadeus-rs/amadeus/src/widgets/about_window.rs deleted file mode 100644 index 1a8d18a22776c57e06905f67caf75238fe7bfbc1..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/about_window.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::{constants, widgets}; - -static THIRD_PARTIE_FONTS: [(&str, &str, Option<&str>); 2] = [ - ( - "UbuntuMono", - "https://design.ubuntu.com/font/", - Some("Dalton Maag (under the Ubuntu font licence)"), - ), - ( - "IPAMincho", - "http://ossipedia.ipa.go.jp/ipafont/", - Some("Information-technology Promotion Agency, Japan (under IPA licence)"), - ), -]; - -static THIRD_PARTIE_LIBS: [(&str, &str, Option<&str>); 6] = [ - ( - "serde", - "https://github.com/serde-rs/serde", - Some("(under MIT or APACHE-2.0)"), - ), - ( - "serde_json", - "https://github.com/serde-rs/json", - Some("(under MIT or APACHE-2.0)"), - ), - ( - "egui", - "https://github.com/emilk/egui", - Some("(under MIT or APACHE-2.0)"), - ), - ( - "lazy_static", - "https://github.com/rust-lang-nursery/lazy-static.rs", - Some("(under MIT or APACHE-2.0)"), - ), - ( - "regex", - "https://github.com/rust-lang/regex", - Some("(under MIT or APACHE-2.0)"), - ), - ( - "image", - "https://github.com/image-rs/image", - Some("(under MIT)"), - ), -]; - -pub fn render_about_window( - show: &mut bool, - dark_mode: bool, - ctx: &egui::Context, - logo: &egui::TextureHandle, -) { - widgets::WindowBuilder::new("About", dark_mode) - .with_resizable(true) - .show(show, ctx, |ui| { - { - ui.separator(); - widgets::add_heading2_label(ui, "☰ Amadeus RS"); - ui.style_mut().override_text_style = Some(crate::utils::font::body()); - ui.horizontal_top(|ui| { - ui.image(logo, logo.size_vec2()); - ui.add_space(constants::PADDING); - ui.add( - egui::Label::new(concat!( - "Amadeus RS is a rewrite of the Amadeus front end to lektord.\n", - "Amadeus RS is under the MIT licence." - )) - .wrap(true), - ); - }); - ui.add_space(constants::PADDING); - } - - { - ui.separator(); - widgets::add_heading2_label(ui, "☰ Third party libraries and fonts"); - ui.style_mut().override_text_style = Some(crate::utils::font::body()); - ui.label("The followinf thrid party libraries where used:"); - for (label, url, after) in THIRD_PARTIE_LIBS { - widgets::add_bullet_hyperlink(ui, label, url, after); - } - ui.add_space(constants::PADDING); - ui.label("The following third party fonts where used:"); - for (label, url, after) in THIRD_PARTIE_FONTS { - widgets::add_bullet_hyperlink(ui, label, url, after); - } - ui.add_space(constants::PADDING); - } - }); -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/action_menu.rs b/src/rust/amadeus-rs/amadeus/src/widgets/action_menu.rs deleted file mode 100644 index 20a8cab1458aff11367809599ee68f486e63d630..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/action_menu.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::utils; -use amadeus_lib::actions::*; - -pub fn render_action_menu( - ui: &mut egui::Ui, - actions: &[Action], - menu_name: impl Into<egui::WidgetText>, - fullfilled: &mut Vec<Action>, -) { - ui.menu_button(menu_name, |ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - for act in actions { - let button = egui::Button::new(get_card_action_name(act)) - .wrap(false) - .frame(false); - if ui.add(button).clicked() { - fullfilled.push(act.clone()); - ui.close_menu(); - } - } - }); -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/mod.rs b/src/rust/amadeus-rs/amadeus/src/widgets/mod.rs deleted file mode 100644 index 07935c24d1eac8c48ddce66d71f19c7bace73b13..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -mod about_window; -mod action_menu; -mod playlists; -mod progress_bar; -mod toggle_switch; -mod window; - -pub use about_window::render_about_window; -pub use action_menu::render_action_menu; -use lkt_lib::Kara; -pub use playlists::PlaylistsStore; -pub use progress_bar::progress_bar; -pub use toggle_switch::toggle_switch; -pub use window::WindowBuilder; - -use crate::{constants, utils}; - -pub fn add_bullet_hyperlink(ui: &mut egui::Ui, label: &str, url: &str, after: Option<&str>) { - ui.horizontal(|ui| { - ui.label(" -"); - ui.hyperlink_to(label, url); - if let Some(text) = after { - ui.add(egui::Label::new(text).wrap(true)); - } - }); -} - -pub fn add_heading2_label(ui: &mut egui::Ui, text: &str) { - ui.style_mut().override_text_style = Some(utils::font::heading2()); - ui.add(egui::Label::new(text).wrap(false)); - ui.add_space(constants::PADDING); -} - -pub fn add_labelled_edit_line( - ui: &mut egui::Ui, - before: &str, - text: &mut dyn egui::TextBuffer, - changed: &mut bool, -) { - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.add(egui::Label::new(before)); - let response = ui.add(egui::TextEdit::singleline(text)); - *changed |= response.lost_focus(); - }); -} - -pub fn add_labelled_toggle_switch( - ui: &mut egui::Ui, - dark_mode: bool, - before: &str, - switch: &mut bool, - changed: &mut bool, -) { - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.add(egui::Label::new(before)); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { - let response = ui.add(toggle_switch(dark_mode, switch)); - *changed |= response.changed(); - }); - }); -} - -pub fn add_labelled_password( - ui: &mut egui::Ui, - before: &str, - text: &mut dyn egui::TextBuffer, - changed: &mut bool, -) { - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::body()); - ui.add(egui::Label::new(before)); - let response = ui.add(egui::TextEdit::singleline(text).password(true)); - *changed |= response.lost_focus(); - }); -} - -pub fn get_label_text_for_kara(kara: &Kara) -> String { - format!( - "{} - {} / {} - {} - {} [{}]", - kara.category, kara.language, kara.source_name, kara.song_type, kara.title, kara.author - ) -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/playlists.rs b/src/rust/amadeus-rs/amadeus/src/widgets/playlists.rs deleted file mode 100644 index 0376bb772395011a732f1cb6b45a81d303fe38ff..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/playlists.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::{ - constants, utils, - widgets::{self, render_action_menu}, -}; -use amadeus_lib::actions; -use lkt_lib::{Kara, LektorType, Playlist}; -use std::collections::{HashMap, HashSet}; - -static DEFAULT_HASHSET_SIZE: usize = 10; - -pub struct PlaylistsStore { - /// The list of all playlists - playlists: HashMap<String, u64>, - - /// The kara that are contained in the playlists. They are indexed by the - /// playlist's unique id. The not ordered and unique kara per playlist - /// aspects of playlists are enforced by the HashSet. - contents: HashMap<u64, HashSet<Kara>>, - - /// Store the playlists to show. - playlists_to_show: HashMap<u64, bool>, - - /// The fullfilled actions in this frame. - actions: Vec<(u64, actions::Action)>, - - /// A temp buffer for handling actions on a specific item, where we know the - /// id to put but not the render function. - temp_actions: Vec<actions::Action>, - - /// Whever to refresh all the playlists or not - refresh_playlists: bool, -} - -impl Default for PlaylistsStore { - fn default() -> Self { - Self { - playlists: HashMap::with_capacity(DEFAULT_HASHSET_SIZE), - contents: HashMap::with_capacity(DEFAULT_HASHSET_SIZE), - playlists_to_show: HashMap::with_capacity(DEFAULT_HASHSET_SIZE), - actions: Vec::new(), - temp_actions: Vec::new(), - refresh_playlists: false, - } - } -} - -impl PlaylistsStore { - pub fn fulfilled_actions(&mut self) -> Vec<(u64, actions::Action)> { - if !self.actions.is_empty() { - let ret = self.actions.clone(); - self.actions.clear(); - ret - } else { - vec![] - } - } - - /// Render the opened playlists. - pub fn render_playlist_contents(&mut self, ctx: &egui::Context, dark_mode: bool) { - let to_show = self - .playlists_to_show - .iter() - .filter(|(_, flag)| **flag) - .map(|(id, _)| *id) - .collect::<Vec<u64>>(); - for id in to_show { - let name = self - .playlists - .iter() - .filter(|(_, plt_id)| **plt_id == id) - .map(|(name, _)| name.as_str()) - .next() - .unwrap(); - let flag = self.playlists_to_show.get_mut(&id).unwrap(); - let karas = self.contents.get(&id).unwrap(); - let actions = widgets::WindowBuilder::new(name, dark_mode) - .with_resizable(false) - .with_default_size(egui::vec2(200., 500.)) - .with_actions(vec![ - actions::Action::AddPlaylistToQueue, - actions::Action::InsertPlaylistToQueue, - ]) - .with_warning_actions(vec![actions::Action::ClearPlaylistContent]) - .show(flag, ctx, |ui| { - egui::ScrollArea::vertical() - .hscroll(false) - .always_show_scroll(false) - .max_width(f32::INFINITY) - .show(ui, |ui| { - if karas.is_empty() { - ui.horizontal(|ui| { - ui.label("Empty playlist"); - ui.add_space(constants::PADDING); - ui.add(egui::Spinner::new()); - }); - } else { - for kara in karas { - ui.horizontal(|ui| { - ui.style_mut().override_text_style = - Some(utils::font::body()); - render_action_menu( - ui, - &[ - actions::Action::AddKaraToQueue, - actions::Action::InsertKaraInQueue, - actions::Action::AddKaraToPlaylist, - actions::Action::DeleteKaraFromPlaylist(id), - actions::Action::DeleteKaraFromQueue, - ], - widgets::get_label_text_for_kara(kara), - &mut self.temp_actions, - ); - self.temp_actions - .iter() - .map(|act| (id, act.clone())) - .for_each(|fullfilled| self.actions.push(fullfilled)); - self.temp_actions.clear(); - }); - } - } - }); - }); - if let Some(actions) = actions { - actions - .iter() - .map(|act| (id, act.clone())) - .for_each(|fullfilled| self.actions.push(fullfilled)); - } - } - } - - /// Render the list of playlists. Also register some of the user's actions - /// like opening a playlist, etc. - pub fn render(&mut self, ui: &mut egui::Ui, dark_mode: bool) { - ui.vertical(|ui| { - ui.add_space(constants::PADDING); - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(utils::font::heading1()); - ui.add(egui::widgets::Button::new("🗀 Playlists").frame(false)) - .clicked() - .then(|| self.refresh_playlists = true); - ui.style_mut().override_text_style = Some(utils::font::small_body()); - }); - ui.style_mut().override_text_style = Some(utils::font::body()); - if self.playlists.is_empty() { - ui.vertical_centered_justified(|ui| { - ui.add(egui::Spinner::new()); - ui.allocate_space(ui.available_size()) - }); - } else { - egui::ScrollArea::vertical() - .hscroll(false) - .always_show_scroll(false) - .max_width(f32::INFINITY) - .show(ui, |ui| { - ui.horizontal(|ui| ui.add_space(ui.available_width())); - for (name, id) in &self.playlists { - let plt_size = self.playlist_size(name).unwrap(); - ui.horizontal(|ui| { - ui.add_space(constants::PADDING); - render_action_menu( - ui, - &[ - actions::Action::OpenPlaylist, - actions::Action::AddPlaylistToQueue, - actions::Action::InsertPlaylistToQueue, - ], - "▶", - &mut self.temp_actions, - ); - self.temp_actions - .iter() - .map(|act| (*id, act.clone())) - .for_each(|fullfilled| self.actions.push(fullfilled)); - self.temp_actions.clear(); - ui.colored_label(constants::get_text_color(dark_mode), name); - ui.style_mut().override_text_style = - Some(utils::font::small_body()); - ui.label(format!("{plt_size} karas")); - }); - } - }); - ui.allocate_space(ui.available_size()); - } - }); - } - - fn get_playlist_by_name(&self, name: &str) -> Option<(&str, u64)> { - return self - .playlists - .iter() - .filter(|(plt_name, _)| *plt_name == name) - .map(|(name, id)| (name.as_str(), *id)) - .next(); - } - - pub fn show_playlist(&mut self, id: u64) { - if let Some(flag) = self.playlists_to_show.get_mut(&id) { - *flag = true; - } - } - - pub fn clear_playlist(&mut self, id: u64) { - if let Some(content) = self.contents.get_mut(&id) { - content.clear(); - } - } - - pub fn refresh_playlists(&mut self) -> bool { - let refresh = self.refresh_playlists; - self.refresh_playlists = false; - refresh - } - - pub fn clear_playlists(&mut self) { - self.contents.clear(); - self.playlists_to_show.clear(); - self.actions.clear(); - self.playlists.clear(); - } - - pub fn create(&mut self, plt: Playlist) { - self.contents.insert( - plt.unique_id(), - HashSet::with_capacity(DEFAULT_HASHSET_SIZE), - ); - let id = plt.unique_id(); - self.playlists.insert(plt.name, id); - self.playlists_to_show.insert(id, false); - } - - pub fn add(&mut self, name: &str, kara: Kara) { - if let Some((_, id)) = self.get_playlist_by_name(name) { - if let Some(plt_content) = self.contents.get_mut(&id) { - plt_content.insert(kara); - } else { - panic!("The playlist {name} has no content set, it should have been created when the playlist was created") - } - } - } - - pub fn playlist_size(&self, name: &str) -> Option<usize> { - let (_, id) = self.get_playlist_by_name(name)?; - let content = self.contents.get(&id)?; - Some(content.len()) - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/progress_bar.rs b/src/rust/amadeus-rs/amadeus/src/widgets/progress_bar.rs deleted file mode 100644 index 91b721748c56fe25be6ed234aeabdae5b5bcce47..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/progress_bar.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub fn progress_bar(dark_mode: bool, progress: f32) -> impl egui::Widget + 'static { - move |ui: &mut egui::Ui| { - let progress = progress.clamp(0.0, 1.0); - let fill_color = crate::constants::get_fill_color(dark_mode); - let desired_width = egui::NumExt::at_least(ui.available_size_before_wrap().x, 96.0); - let height = ui.spacing().interact_size.y; - let (outer_rect, response) = - ui.allocate_exact_size(egui::vec2(desired_width, height), egui::Sense::hover()); - - if ui.is_rect_visible(response.rect) { - let rect = egui::Rect::from_min_size( - outer_rect.min, - egui::vec2( - egui::NumExt::at_least(outer_rect.width() * progress, outer_rect.height()), - outer_rect.height(), - ), - ); - ui.painter().rect(rect, 0.0, fill_color, egui::Stroke::NONE); - } - - response - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/toggle_switch.rs b/src/rust/amadeus-rs/amadeus/src/widgets/toggle_switch.rs deleted file mode 100644 index 16d5b93ade1a9106d34ab5416b78a6af80ccb5b3..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/toggle_switch.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::constants; -use amadeus_macro::either; - -pub fn toggle_switch(dark_mode: bool, on: &mut bool) -> impl egui::Widget + '_ { - move |ui: &mut egui::Ui| { - let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0); - let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); - if response.clicked() { - *on = !*on; - response.mark_changed(); - } - response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, "")); - - if ui.is_rect_visible(rect) { - let (bg_fill, bg_stroke, fg_stroke, expansion) = { - let visuals = ui.style().interact_selectable(&response, *on); - ( - visuals.bg_fill, - visuals.bg_stroke, - visuals.fg_stroke, - visuals.expansion, - ) - }; - let fill_fore = constants::get_accent_color(dark_mode); - let fill_back = constants::get_fill_color(dark_mode); - let rect = rect.expand(expansion); - let radius = 0.5 * rect.height(); - - ui.painter() - .rect(rect, radius, either!(*on => fill_back; bg_fill), bg_stroke); - - let circle_x = egui::lerp( - (rect.left() + radius)..=(rect.right() - radius), - ui.ctx().animate_bool(response.id, *on), - ); - let center = egui::pos2(circle_x, rect.center().y); - - ui.painter() - .circle(center, 0.75 * radius, fill_fore, fg_stroke); - } - - response - } -} diff --git a/src/rust/amadeus-rs/amadeus/src/widgets/window.rs b/src/rust/amadeus-rs/amadeus/src/widgets/window.rs deleted file mode 100644 index afa682f70c25c5c771a6125e235d78e8d02059ef..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/amadeus/src/widgets/window.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::constants; -use amadeus_lib::actions::Action; -use amadeus_macro::*; - -use super::render_action_menu; - -/// Structure used to hole informations about the window to create, we use a -/// builder pattern here. -pub struct WindowBuilder<'a> { - name: &'a str, - dark_mode: bool, - resizable: Option<bool>, - default_size: Option<egui::Vec2>, - actions: Option<Vec<Action>>, - warning_actions: Option<Vec<Action>>, -} - -impl<'a> WindowBuilder<'a> { - pub fn new(name: &'a str, dark_mode: bool) -> Self { - Self { - name, - dark_mode, - resizable: None, - default_size: None, - actions: None, - warning_actions: None, - } - } - - pub fn with_resizable(self, resizable: bool) -> Self { - Self { - name: self.name, - dark_mode: self.dark_mode, - resizable: Some(resizable), - default_size: self.default_size, - actions: self.actions, - warning_actions: self.warning_actions, - } - } - - pub fn with_default_size(self, default_size: egui::Vec2) -> Self { - Self { - name: self.name, - dark_mode: self.dark_mode, - resizable: self.resizable, - default_size: Some(default_size), - actions: self.actions, - warning_actions: self.warning_actions, - } - } - - pub fn with_actions(self, actions: Vec<Action>) -> Self { - Self { - name: self.name, - dark_mode: self.dark_mode, - resizable: self.resizable, - default_size: self.default_size, - actions: Some(actions), - warning_actions: self.warning_actions, - } - } - - pub fn with_warning_actions(self, actions: Vec<Action>) -> Self { - Self { - name: self.name, - dark_mode: self.dark_mode, - resizable: self.resizable, - default_size: self.default_size, - actions: self.actions, - warning_actions: Some(actions), - } - } - - pub fn show<R>( - self, - flag: &mut bool, - ctx: &egui::Context, - add_contents: impl FnOnce(&mut egui::Ui) -> R, - ) -> Option<Vec<Action>> { - let mut flag_copy = *flag; - let mut temp_actions = Vec::<Action>::new(); - - // Force the mut borrow of the flag to be dropped - { - let win = egui::Window::new(self.name).open(flag).title_bar(false); - let (win, resizable) = match self.resizable { - Some(resizable) => (win.resizable(resizable), resizable), - None => (win, false), - }; - let win = match self.default_size { - None => win, - Some(sizes) => { - either!(resizable => win.default_size(sizes).fixed_size(sizes); win.default_size(sizes)) - } - }; - win.show(ctx, |ui| { - ui.horizontal(|ui| { - ui.style_mut().override_text_style = Some(super::utils::font::heading3()); - ui.add(egui::Button::new("❌")) - .on_hover_text("Close the window") - .clicked() - .then(|| flag_copy = false); - - if let Some(actions) = self.actions { - render_action_menu(ui, &actions, "▶", &mut temp_actions) - } - if let Some(actions) = self.warning_actions { - render_action_menu(ui, &actions, "⚠", &mut temp_actions) - } - - ui.colored_label(constants::get_text_color(self.dark_mode), self.name); - }); - ui.add_space(constants::PADDING / 2.); - add_contents(ui) - }); - } - - // See if we need to close the window - *flag = flag_copy; - - // Return the actions if needed - if temp_actions.is_empty() { - None - } else { - Some(temp_actions) - } - } -} diff --git a/src/rust/amadeus-rs/lkt-lib/Cargo.toml b/src/rust/amadeus-rs/lkt-lib/Cargo.toml deleted file mode 100644 index d28a58181390b0bbe7207150be48431dfcef0619..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "lkt_lib" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -amadeus_macro = { path = "../amadeus-macro" } -serde = { version = "1", default-features = false, features = [ "derive", "std" ] } -serde_json = { version = "1", default-features = false, features = [ "std" ] } -log = { version = "0.4" } -regex = "1" -lazy_static = "1" \ No newline at end of file diff --git a/src/rust/amadeus-rs/lkt-lib/src/connexion.rs b/src/rust/amadeus-rs/lkt-lib/src/connexion.rs deleted file mode 100644 index 243155f155925ca69769bbd6aba175023955127a..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/connexion.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Create a connexion to a lektord instande then send commands and recieve data -//! from the server. - -use crate::{ - constants, query::LektorQuery, query::LektorQueryLineType, response::LektorFormatedResponse, - LektorResponse, -}; -use log::*; -use std::{ - io::{self, BufRead, BufReader, Write}, - net::TcpStream, - str::FromStr, -}; - -pub enum LektorQueryError { - IO(io::Error), - Query(String), - Other(String), - ToTyped(String), -} - -impl std::fmt::Display for LektorQueryError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use LektorQueryError::*; - match self { - IO(io) => write!(f, "lektor query error io: {io}"), - Query(err) => write!(f, "lektor query logic error: {err}"), - Other(other) => write!(f, "lektor query error: {other}"), - ToTyped(msg) => write!(f, "lektor formated to typed error: {msg}"), - } - } -} - -pub struct LektorConnexion { - pub version: String, - stream: TcpStream, - reader: BufReader<TcpStream>, -} - -impl std::fmt::Debug for LektorConnexion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("LektorConnexion") - .field("stream", &self.stream) - .field("version", &self.version) - .field("reader", &self.reader) - .finish() - } -} - -impl LektorConnexion { - pub fn new(hostname: impl AsRef<str>, port: i16) -> io::Result<Self> { - match TcpStream::connect(format!("{}:{}", hostname.as_ref(), port)) { - Ok(lektord) => { - let mut ret: Self = Self { - stream: lektord.try_clone().unwrap(), - version: constants::MPD_VERSION.to_owned(), - reader: BufReader::new(lektord), - }; - let mut daemon_version: String = String::new(); - ret.reader.read_line(&mut daemon_version).unwrap(); - assert_eq!( - daemon_version.trim(), - format!("OK MPD {}", ret.version), - "Expected MPD server version {}, but got version {}, abort!", - ret.version, - daemon_version - ); - Ok(ret) - } - Err(e) => { - error!("Failed to connect to lektor: {e}"); - Err(e) - } - } - } - - pub fn send_query(&mut self, query: LektorQuery) -> Result<LektorResponse, LektorQueryError> { - let mut res: Vec<String> = Vec::new(); - query.verify().map_err(LektorQueryError::Query)?; - let builder = query.get_response_type(); - self.send_query_inner(query, &mut res) - .map_err(LektorQueryError::IO)?; - let formated = LektorFormatedResponse::try_from(res).map_err(LektorQueryError::Other)?; - builder(formated).map_err(LektorQueryError::ToTyped) - } - - fn send_query_inner( - &mut self, - query: LektorQuery, - previous_ret: &mut Vec<String>, - ) -> io::Result<()> { - self.write_string(query.to_string())?; - loop { - match self.read_replies() { - Err(e) => return Err(e), - Ok((res, _)) if res.is_empty() => return Ok(()), - Ok((res, None)) => { - return { - previous_ret.extend(res); - Ok(()) - } - } - Ok((res, Some(cont))) => { - previous_ret.extend(res); - self.write_string( - LektorQuery::create_continuation(query.clone(), cont).to_string(), - )?; - } - } - } - } - - fn read_replies(&mut self) -> io::Result<(Vec<String>, Option<usize>)> { - let error_return_value = io::Result::Err(io::Error::from(io::ErrorKind::Other)); - let mut ret: Vec<String> = Vec::new(); - let mut reply_line: String = String::new(); - let mut continuation = None; - loop { - reply_line.clear(); - match self.reader.read_line(&mut reply_line) { - Err(e) => return io::Result::Err(e), - Ok(size) => { - if size == 0 { - return io::Result::Err(io::Error::new( - io::ErrorKind::Other, - "recieved empty line", - )); - } - let msg = reply_line.trim(); - match LektorQueryLineType::from_str(msg) { - Ok(LektorQueryLineType::Ok) => return Ok((ret, continuation)), - Ok(LektorQueryLineType::Ack) => return error_return_value, - Ok(LektorQueryLineType::Data) => ret.push(msg.to_string()), - Ok(LektorQueryLineType::ListOk) => continue, - Ok(LektorQueryLineType::Continuation(cont)) => continuation = Some(cont), - Err(_) => { - return Err(io::Error::new( - io::ErrorKind::Other, - "unknown query line type", - )) - } - } - } - } - } - } - - fn write_string(&mut self, buffer: impl AsRef<str>) -> io::Result<()> { - let buffer = buffer.as_ref(); - if buffer.len() >= constants::LKT_MESSAGE_MAX { - Err(io::Error::new( - io::ErrorKind::InvalidData, - "buffer to send is to big!", - )) - } else if !buffer.ends_with('\n') { - Err(io::Error::new( - io::ErrorKind::InvalidData, - "buffer dosen't end with a new line", - )) - } else { - self.stream.write_all(buffer.as_bytes()) - } - } -} diff --git a/src/rust/amadeus-rs/lkt-lib/src/constants.rs b/src/rust/amadeus-rs/lkt-lib/src/constants.rs deleted file mode 100644 index 505f0af8e8e8bc5773341ca8fc6496988cb76508..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/constants.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Contains standard constants from lektord, must be updated manually if they -//! change in lektord... - -#![allow(dead_code)] - -/// MPD commands are at most 32 character long. -pub(crate) const LKT_COMMAND_LEN_MAX: usize = 32; - -/// At most 32 words in a command are supported. -pub(crate) const LKT_MESSAGE_ARGS_MAX: usize = 32; - -/// A message is at most <defined> chars long -pub(crate) const LKT_MESSAGE_MAX: usize = 2048; - -/// At most 64 commands per client. -pub(crate) const COMMAND_LIST_MAX: usize = 64; - -/// At most 16 messages per client. -pub(crate) const BUFFER_OUT_MAX: usize = 16; - -/// Expected version from the lektord daemin. -pub(crate) const MPD_VERSION: &str = "0.21.16"; diff --git a/src/rust/amadeus-rs/lkt-lib/src/lib.rs b/src/rust/amadeus-rs/lkt-lib/src/lib.rs deleted file mode 100644 index 331d354dd87a2a3762e3e6686bab89c9f66dcaaf..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Contains structurs and mechanisms to build, send and recieve commands -//! to/from lektord. - -mod connexion; -mod constants; -mod query; -mod response; -mod types; -mod uri; - -pub use connexion::*; -pub use query::*; -pub use response::*; -pub use types::*; -pub use uri::*; diff --git a/src/rust/amadeus-rs/lkt-lib/src/query.rs b/src/rust/amadeus-rs/lkt-lib/src/query.rs deleted file mode 100644 index 9abc3e277784cdab5719a19a02552a6b0ccf688a..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/query.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! Contains files to create and build queries to send latter to lektord. - -use crate::*; -use amadeus_macro::*; -use std::string::ToString; - -pub(crate) enum LektorQueryLineType { - Ok, - ListOk, - Ack, - Continuation(usize), - Data, -} - -#[derive(Debug, Clone)] -pub enum LektorQuery { - Ping, - Close, - KillServer, - ConnectAsUser(String, Box<LektorQuery>), - - CurrentKara, - PlaybackStatus, - - PlayNext, - PlayPrevious, - ShuffleQueue, - - ListAllPlaylists, - ListPlaylist(String), - SearchKara(LektorUri), - - FindAddKara(LektorUri), - InsertKara(LektorUri), - AddKara(LektorUri), - - Continuation(usize, Box<LektorQuery>), -} - -impl std::str::FromStr for LektorQueryLineType { - type Err = (); - - fn from_str(line: &str) -> Result<Self, Self::Err> { - if Self::is_line_ok(line) { - Ok(Self::Ok) - } else if Self::is_line_ack(line) { - Ok(Self::Ack) - } else if Self::is_line_list_ok(line) { - Ok(Self::ListOk) - } else if let Some(cont) = Self::is_line_continuation(line) { - Ok(Self::Continuation(cont)) - } else { - Ok(Self::Data) - } - } -} - -impl LektorQueryLineType { - fn is_line_continuation(line: &str) -> Option<usize> { - if !line.starts_with("continue:") { - return None; - } - match line.trim_start_matches("continue:").trim().parse::<usize>() { - Ok(cont) => Some(cont), - Err(_) => None, - } - } - - fn is_line_list_ok(line: &str) -> bool { - (line == "list_OK\n") || (line == "list_OK") - } - - fn is_line_ok(line: &str) -> bool { - (line == "OK\n") || (line == "OK") - } - - fn is_line_ack(line: &str) -> bool { - line.starts_with("ACK: ") - } -} - -/// The type of the function to use to produce a typed response from the -/// formated response. -type QueryToTypeResponseBuilder = fn(LektorFormatedResponse) -> Result<LektorResponse, String>; - -impl LektorQuery { - /// Get the function to use to produce the typed response from the formated - /// response, to automatically create the correct thing. - pub fn get_response_type(&self) -> QueryToTypeResponseBuilder { - use LektorQuery::*; - match self { - Ping | Close | KillServer | PlayNext | PlayPrevious | ShuffleQueue | InsertKara(_) - | AddKara(_) => LektorEmptyResponse::from_formated, - - ListAllPlaylists => LektorPlaylistSetResponse::from_formated, - PlaybackStatus => LektorPlaybackStatusResponse::from_formated, - CurrentKara => LektorCurrentKaraResponse::from_formated, - - ConnectAsUser(_, cmd) | Continuation(_, cmd) => cmd.get_response_type(), - - ListPlaylist(_) | SearchKara(_) | FindAddKara(_) => { - LektorKaraSetResponse::from_formated - } - } - } - - /// Create a continued query out of another one. If the query is already a - /// continuation query then the underlying query is reused. - pub fn create_continuation(query: Self, cont: usize) -> Self { - match query { - Self::Continuation(_, query) => Self::Continuation(cont, query), - _ => Self::Continuation(cont, Box::new(query)), - } - } - - /// Verify that a query is Ok. - pub fn verify(&self) -> Result<(), String> { - use LektorQuery::*; - match self { - // User commands - SearchKara(_) | FindAddKara(_) | InsertKara(_) | AddKara(_) | PlaybackStatus - | PlayNext | PlayPrevious | ShuffleQueue | ListAllPlaylists | ListPlaylist(_) - | CurrentKara | Ping => Ok(()), - - // Should be admin commands - Close => Err("close is an admin command".to_string()), - KillServer => Err("kill server is an admin command".to_string()), - - // Admin commands - ConnectAsUser(_, cmd) => match cmd.as_ref() { - Close | KillServer => Ok(()), - _ => Err(format!("not an admin command: {cmd:?}")), - }, - - // Continuation commands - Continuation(_, cmd) => match cmd.as_ref() { - ListAllPlaylists | FindAddKara(_) | SearchKara(_) | ListPlaylist(_) => Ok(()), - _ => Err(format!("not a continuable command: {cmd:?}")), - }, - } - } -} - -impl ToString for LektorQuery { - fn to_string(&self) -> String { - use LektorQuery::*; - match self { - Ping => lkt_command_from_str!("ping"), - Close => lkt_command_from_str!("close"), - KillServer => lkt_command_from_str!("kill"), - ConnectAsUser(password, cmd) => format!( - concat!( - "command_list_ok_begin\n", - "password {}\n", - "{}\n", - "command_list_end\n", - ), - password, - cmd.to_string() - ), - - CurrentKara => lkt_command_from_str!("currentsong"), - PlaybackStatus => lkt_command_from_str!("status"), - - PlayNext => lkt_command_from_str!("next"), - PlayPrevious => lkt_command_from_str!("previous"), - ShuffleQueue => lkt_command_from_str!("shuffle"), - - ListAllPlaylists => lkt_command_from_str!("listplaylists"), - ListPlaylist(plt_name) => format!("listplaylist {}\n", plt_name), - SearchKara(uri) => format!("find {}\n", uri.to_string()), - - FindAddKara(uri) => format!("findadd {}\n", uri.to_string()), - InsertKara(uri) => format!("__insert {}\n", uri.to_string()), - AddKara(uri) => format!("add {}\n", uri.to_string()), - - Continuation(cont, query) => format!("{cont} {}", query.to_owned().to_string()), - } - } -} diff --git a/src/rust/amadeus-rs/lkt-lib/src/response.rs b/src/rust/amadeus-rs/lkt-lib/src/response.rs deleted file mode 100644 index 127fa3037ba6c8782723b4ded5ec76d6af7e6513..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/response.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! Contains types for typed response. - -use crate::types::LektorState; -use amadeus_macro::*; - -/// A formated response is just a list of key/pairs. We get every line that is -/// not Ok/Ack/Continue (i.e. data lines) and split on the first ':' and trim -/// spaces from the keys and the values. The keys are always in lowercase. -#[derive(Debug)] -pub struct LektorFormatedResponse { - content: Vec<(String, String)>, - raw_content: Vec<String>, -} - -impl LektorFormatedResponse { - /// Pop the first key found in the response, get an error if the key is not - /// found. If multiple keys are found, only the first found is returned. - pub fn pop(&mut self, key: &str) -> Result<String, String> { - match self - .content - .iter() - .enumerate() - .find_map(|(index, (what, _))| then_some!(what == key => index)) - { - Some(index) => Ok(self.content.remove(index).1), - None => Err(format!("no key {key} was found in formated response")), - } - } - - /// Pop all the entries with the said key. If no key is found then the empty - /// vector is returned. This function can't fail. - /// - /// FIXME: Get ride of the clone in this function... - pub fn pop_all(&mut self, key: &str) -> Vec<String> { - let mut ret = Vec::new(); - self.content.retain(|(what, field)| { - if *what == key { - ret.push(field.clone()); - false - } else { - true - } - }); - ret - } - - /// Get the raw content of the response. - pub fn pop_raw(self) -> Vec<String> { - self.raw_content - } -} - -impl IntoIterator for LektorFormatedResponse { - type Item = (String, String); - type IntoIter = - <std::vec::Vec<(std::string::String, std::string::String)> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.content.into_iter() - } -} - -impl TryFrom<Vec<String>> for LektorFormatedResponse { - type Error = String; - - fn try_from(vec: Vec<String>) -> Result<Self, Self::Error> { - let (mut content, mut raw_content) = (Vec::new(), Vec::new()); - for line in vec { - match line.splitn(2, ':').collect::<Vec<&str>>()[..] { - [key, value] => content.push((key.trim().to_lowercase(), value.trim().to_string())), - _ => raw_content.push(line), - } - } - Ok(Self { - content, - raw_content, - }) - } -} - -#[derive(Debug)] -pub enum LektorResponse { - PlaybackStatus(LektorPlaybackStatusResponse), - CurrentKara(LektorCurrentKaraResponse), - PlaylistSet(LektorPlaylistSetResponse), - EmptyResponse(LektorEmptyResponse), - KaraSet(LektorKaraSetResponse), -} - -/// A trait for typed lektor responses. Such responses must be built by -/// consuming a formated response. We also protect from implemeting this trait -/// outside of this crate. -pub trait FromLektorResponse: std::fmt::Debug + private::Sealed { - /// Consume a formated response to produce the correctly typed response. May - /// got an error as a string that describes the problem. - fn from_formated(response: LektorFormatedResponse) -> Result<LektorResponse, String> - where - Self: Sized; -} - -mod private { - use super::*; - pub trait Sealed {} - impl Sealed for LektorPlaybackStatusResponse {} - impl Sealed for LektorCurrentKaraResponse {} - impl Sealed for LektorPlaylistSetResponse {} - impl Sealed for LektorKaraSetResponse {} - impl Sealed for LektorEmptyResponse {} -} - -macro_rules! getter { - ($name: ident: ref $type: ty) => { - pub fn $name(&self) -> &$type { - &self.$name - } - }; - - ($name: ident: $type: ty) => { - pub fn $name(&self) -> $type { - self.$name - } - }; -} - -#[derive(Debug)] -pub struct LektorPlaybackStatusResponse { - elapsed: usize, - songid: Option<usize>, - song: Option<usize>, - volume: usize, - state: LektorState, - duration: usize, - updating_db: usize, - playlistlength: usize, - random: bool, - consume: bool, - single: bool, - repeat: bool, -} - -#[derive(Debug)] -pub struct LektorPlaylistSetResponse { - playlists: Vec<String>, -} - -#[derive(Debug)] -pub struct LektorCurrentKaraInnerResponse { - title: String, - author: String, - source: String, - song_type: String, - song_number: Option<usize>, - category: String, - language: String, -} - -#[derive(Debug)] -pub struct LektorKaraSetResponse { - karas: Vec<String>, -} - -#[derive(Debug)] -pub struct LektorCurrentKaraResponse { - content: Option<LektorCurrentKaraInnerResponse>, -} - -#[derive(Debug)] -pub struct LektorEmptyResponse; - -impl LektorCurrentKaraResponse { - getter!(content: ref Option<LektorCurrentKaraInnerResponse>); - - pub fn maybe_into_inner(self) -> Option<LektorCurrentKaraInnerResponse> { - self.content - } - - pub fn into_inner(self) -> LektorCurrentKaraInnerResponse { - self.content.unwrap() - } -} - -impl LektorCurrentKaraInnerResponse { - getter!(title: ref String); - getter!(author: ref String); - getter!(source: ref String); - getter!(song_type: ref String); - getter!(song_number: Option<usize>); - getter!(category: ref String); - getter!(language: ref String); -} - -impl LektorPlaybackStatusResponse { - getter!(elapsed: usize); - getter!(songid: Option<usize>); - getter!(song: Option<usize>); - getter!(volume: usize); - getter!(state: LektorState); - getter!(duration: usize); - getter!(updating_db: usize); - getter!(playlistlength: usize); - getter!(random: bool); - getter!(consume: bool); - getter!(single: bool); - getter!(repeat: bool); - - pub fn verify(&self) -> bool { - self.elapsed() <= self.duration() - } -} - -impl LektorKaraSetResponse { - pub fn iter(&self) -> &[String] { - &self.karas[..] - } -} - -impl LektorPlaylistSetResponse { - pub fn iter(&self) -> &[String] { - &self.playlists[..] - } -} - -impl IntoIterator for LektorKaraSetResponse { - type Item = String; - type IntoIter = <std::vec::Vec<std::string::String> as IntoIterator>::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.karas.into_iter() - } -} - -impl IntoIterator for LektorPlaylistSetResponse { - type Item = String; - type IntoIter = <std::vec::Vec<std::string::String> as IntoIterator>::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.playlists.into_iter() - } -} - -impl LektorCurrentKaraInnerResponse { - /// If the response is partial we might want to return none (if we are not - /// playing anything for example...) - pub(self) fn is_partial(&self) -> bool { - self.title.is_empty() - || self.author.is_empty() - || self.source.is_empty() - || self.song_type.is_empty() - || self.category.is_empty() - || self.language.is_empty() - } -} - -impl FromLektorResponse for LektorEmptyResponse { - fn from_formated(_: LektorFormatedResponse) -> Result<LektorResponse, String> { - Ok(LektorResponse::EmptyResponse(Self {})) - } -} - -impl FromLektorResponse for LektorPlaylistSetResponse { - fn from_formated(mut response: LektorFormatedResponse) -> Result<LektorResponse, String> { - Ok(LektorResponse::PlaylistSet(Self { - playlists: response.pop_all("name"), - })) - } -} - -impl FromLektorResponse for LektorKaraSetResponse { - fn from_formated(response: LektorFormatedResponse) -> Result<LektorResponse, String> - where - Self: Sized, - { - Ok(LektorResponse::KaraSet(Self { - karas: response.pop_raw(), - })) - } -} - -impl FromLektorResponse for LektorPlaybackStatusResponse { - fn from_formated(mut response: LektorFormatedResponse) -> Result<LektorResponse, String> { - let mut ret = Self { - elapsed: response.pop("elapsed")?.parse::<usize>().unwrap_or(0), - songid: match response.pop("songid")?.parse::<isize>() { - Ok(x) if x <= 0 => None, - Ok(x) => Some(x as usize), - Err(_) => None, - }, - song: match response.pop("song")?.parse::<usize>() { - Ok(x) => Some(x), - Err(_) => None, - }, - volume: response - .pop("volume")? - .parse::<usize>() - .unwrap() - .clamp(0, 100), - duration: response.pop("duration")?.parse::<usize>().unwrap(), - updating_db: response.pop("updating_db")?.parse::<usize>().unwrap(), - playlistlength: response.pop("playlistlength")?.parse::<usize>().unwrap(), - random: response.pop("random")?.parse::<usize>().unwrap() != 0, - consume: response.pop("consume")?.parse::<usize>().unwrap() != 0, - single: response.pop("single")?.parse::<usize>().unwrap() != 0, - repeat: response.pop("repeat")?.parse::<usize>().unwrap() != 0, - state: LektorState::Stopped, - }; - ret.state = match &response.pop("state")?[..] { - "play" => LektorState::Play(ret.songid.unwrap()), - "pause" => LektorState::Pause(ret.songid.unwrap()), - _ => LektorState::Stopped, - }; - Ok(LektorResponse::PlaybackStatus(ret)) - } -} - -impl FromLektorResponse for LektorCurrentKaraResponse { - fn from_formated(mut response: LektorFormatedResponse) -> Result<LektorResponse, String> { - let song_type_number = response.pop("type")?; - let (song_type, song_number) = match song_type_number.find(char::is_numeric) { - Some(index) => ( - song_type_number[..index].to_owned(), - match song_type_number[index..].parse::<usize>() { - Ok(x) => Some(x), - Err(_) => None, - }, - ), - None => panic!("Oupsy"), - }; - - let inner = LektorCurrentKaraInnerResponse { - title: response.pop("title")?, - author: response.pop("author")?, - source: response.pop("source")?, - category: response.pop("category")?, - language: response.pop("language")?, - song_type, - song_number, - }; - - Ok(LektorResponse::CurrentKara(Self { - content: then_some!(!inner.is_partial() => inner), - })) - } -} diff --git a/src/rust/amadeus-rs/lkt-lib/src/types.rs b/src/rust/amadeus-rs/lkt-lib/src/types.rs deleted file mode 100644 index cab41589de820bd5f50b231de7888480848eacf2..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/types.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Common types for lektord. - -use std::hash::{Hash, Hasher}; - -/// Lektor Types are the Kara and the Playlist types. You should not implement -/// this type yourself. -pub trait LektorType<'a>: - Clone + serde::Serialize + serde::Deserialize<'a> + Hash + private::Sealed -{ - fn unique_id(&self) -> u64; -} - -#[derive(Clone, Eq, serde::Serialize, serde::Deserialize)] -pub struct Kara { - pub id: u32, - pub source_name: String, - - #[serde(rename = "type")] - pub song_type: String, - - pub language: String, - pub category: String, - pub title: String, - pub song_number: Option<u32>, - pub author: String, - pub is_available: bool, -} - -#[derive(Clone, Eq, serde::Serialize, serde::Deserialize, PartialEq, Hash)] -pub struct Playlist { - pub name: String, -} - -impl PartialEq for Kara { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl LektorType<'_> for Kara { - fn unique_id(&self) -> u64 { - private::unique_id(self) - } -} - -impl LektorType<'_> for Playlist { - fn unique_id(&self) -> u64 { - private::unique_id(self) - } -} - -#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Copy)] -pub enum LektorState { - Stopped, - Play(usize), - Pause(usize), -} - -impl Default for LektorState { - fn default() -> Self { - Self::Stopped - } -} - -/// We seal the implementation only for supported types. -mod private { - use super::{Kara, Playlist}; - use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, - }; - - pub trait Sealed {} - - impl Sealed for Kara {} - impl Sealed for Playlist {} - - pub(crate) fn unique_id<T: Hash + Sealed>(object: &T) -> u64 { - let mut s = DefaultHasher::new(); - object.hash(&mut s); - s.finish() - } -} - -/// Sealed implementation of Hash for Kara. -impl Hash for Kara { - fn hash<H: Hasher>(&self, state: &mut H) { - ((self.id as usize) * 2).hash(state); - } -} diff --git a/src/rust/amadeus-rs/lkt-lib/src/uri.rs b/src/rust/amadeus-rs/lkt-lib/src/uri.rs deleted file mode 100644 index 7c1e8308540a95ae32976828bfb6483a9e664e1f..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-lib/src/uri.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Build lektord queries. - -#[derive(Debug, Clone)] -pub enum LektorUri { - Id(i32), - Author(String), - Playlist(String), - Query(String), -} - -impl ToString for LektorUri { - fn to_string(&self) -> String { - match self { - Self::Id(id) => format!("id://{}", id), - Self::Author(author) => format!("author://{}", author), - Self::Playlist(plt_name) => format!("playlist://{}", plt_name), - Self::Query(sql_query) => format!("query://%{}%", sql_query), - } - } -} diff --git a/src/rust/amadeus-rs/lkt-rs/Cargo.toml b/src/rust/amadeus-rs/lkt-rs/Cargo.toml deleted file mode 100644 index 9909296afe0d25253ac3135d17e9fa2fdbff75af..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-rs/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "lkt" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -lkt_lib = { path = "../lkt-lib" } -amadeus_macro = { path = "../amadeus-macro" } -serde = { version = "1", features = [ "derive" ] } -serde_json = { version = "1", default-features = false, features = [ "std" ] } -log = { version = "0.4" } -lazy_static = "1" diff --git a/src/rust/amadeus-rs/lkt-rs/src/main.rs b/src/rust/amadeus-rs/lkt-rs/src/main.rs deleted file mode 100644 index f50969b3de81a74ab32d1247b7aca91ed3f0ee14..0000000000000000000000000000000000000000 --- a/src/rust/amadeus-rs/lkt-rs/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use lkt_lib::*; - -fn main() { - let mut lektor = LektorConnexion::new("localhost", 6600).unwrap(); - if lektor.send_query(LektorQuery::Ping).is_ok() {} - - if let Ok(LektorResponse::CurrentKara(current)) = lektor.send_query(LektorQuery::CurrentKara) { - println!("CURRENT {current:?}"); - } - - if let Ok(LektorResponse::PlaybackStatus(status)) = - lektor.send_query(LektorQuery::PlaybackStatus) - { - println!("STATUS {status:?}"); - } - - if let Ok(LektorResponse::PlaylistSet(plts)) = lektor.send_query(LektorQuery::ListAllPlaylists) - { - println!("PLTS {plts:?}"); - } -} 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 d58c370c2bf216e895d52e615f6d9ef2a9ed0592..9c77af5f18a1f4dfdb3e4d882745f12db951cc0f 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 @@ -34,7 +34,7 @@ pub enum LktUriField { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum LktUriValue<'a> { String(&'a str), - Integer(i32), + Integer(i64), } impl Default for LktUriField { @@ -82,11 +82,11 @@ impl LktCUri { /// Get the URI value as an int. If the contained thing is not an integer /// returns None. - fn get_value_as_int(&self) -> Option<i32> { + fn get_value_as_int(&self) -> Option<i64> { use LktUriValueType::*; match self.get_value_type() { Null | String => None, - Integer => Some(unsafe { lkt_uri_get_value_as_int(self.ptr) }), + Integer => Some(unsafe { lkt_uri_get_value_as_int(self.ptr) } as i64), } } diff --git a/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql b/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql index dcd18261383281758e597fdb21df33ba3ac4f844..b75c9f725b1df1e12fe17457a66bfdbb6b00b326 100644 --- a/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql +++ b/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql @@ -1,23 +1,23 @@ -- A list of repos karas where downloaded from. All repos should have different -- names! Their IDs are local to the client. CREATE TABLE repo - ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT - , name TEXT NOT NULL UNIQUE + ( id BIGINT NOT NULL PRIMARY KEY + , name TEXT NOT NULL UNIQUE ); -- Link a kara in the local referencial to its reference in the distant repo. -- Local IDs are all uniques and every client should be expected to have -- different ones. CREATE TABLE repo_kara - ( repo_id INTEGER NOT NULL REFERENCES repo(id) - , repo_kara_id INTEGER NOT NULL - , local_kara_id INTEGER NOT NULL REFERENCES kara(id) + ( repo_id BIGINT NOT NULL REFERENCES repo(id) + , repo_kara_id BIGINT NOT NULL + , local_kara_id BIGINT NOT NULL REFERENCES kara(id) , PRIMARY KEY (repo_id, repo_kara_id, local_kara_id) ); -- A kara entry in the databse. CREATE TABLE kara - ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT + ( id BIGINT NOT NULL PRIMARY KEY , is_dl BOOLEAN NOT NULL DEFAULT false , song_title TEXT NOT NULL , song_type TEXT NOT NULL @@ -27,23 +27,23 @@ CREATE TABLE kara ); -- We can have multiple kara makers for one kara. -CREATE TABLE kara_makers - ( id INTEGER NOT NULL REFERENCES kara ON DELETE CASCADE - , name TEXT NOT NULL +CREATE TABLE kara_maker + ( id BIGINT NOT NULL REFERENCES kara ON DELETE CASCADE + , name TEXT NOT NULL , PRIMARY KEY (id, name) ); -- Tags are informations used to be able to make queries in a easier way. CREATE TABLE tag - ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT - , name TEXT NOT NULL UNIQUE + ( id BIGINT NOT NULL PRIMARY KEY + , name TEXT NOT NULL UNIQUE ); -- The content of a tag for kara. Multiple tags for a single kara with the same -- tag id means that there a list of values. -CREATE TABLE kara_tags - ( kara_id INTEGER NOT NULL REFERENCES kara(id) ON DELETE CASCADE - , tag_id INTEGER NOT NULL REFERENCES tag(id) ON DELETE CASCADE +CREATE TABLE kara_tag + ( kara_id BIGINT NOT NULL REFERENCES kara(id) ON DELETE CASCADE + , tag_id BIGINT NOT NULL REFERENCES tag(id) ON DELETE CASCADE , value TEXT , PRIMARY KEY (kara_id, tag_id, value) ); @@ -51,8 +51,8 @@ CREATE TABLE kara_tags -- Store the history of played karas from the queue. For now we allow a kara to -- appear multiple times in the history. CREATE TABLE history - ( id INTEGER NOT NULL REFERENCES kara(id) ON DELETE CASCADE - , epoch INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT + ( id BIGINT NOT NULL REFERENCES kara(id) ON DELETE CASCADE + , epoch BIGINT NOT NULL PRIMARY KEY ); -- The list of ISO 639-1 languages with their associated codes. @@ -64,43 +64,8 @@ CREATE TABLE iso_639_1 , is_macro BOOLEAN NOT NULL DEFAULT false ); -CREATE TABLE kara_langs - ( id INTEGER NOT NULL REFERENCES kara ON DELETE CASCADE - , code TEXT NOT NULL REFERENCES iso_639_1 ON DELETE CASCADE +CREATE TABLE kara_lang + ( id BIGINT NOT NULL REFERENCES kara ON DELETE CASCADE + , code TEXT NOT NULL REFERENCES iso_639_1 ON DELETE CASCADE , PRIMARY KEY (id, code) ); - --- As defined in ISO 639-1: --- https://archive.wikiwix.com/cache/index2.php?url=http%3A%2F%2Fwww.sil.org%2Fiso639-3%2Fcodes.asp%3Forder%3D639_1%26letter%3D%2525#federation=archive.wikiwix.com&tab=url -INSERT OR REPLACE INTO iso_639_1 (is_macro, is_iso, code, name_en) VALUES - ( true, true, "ar", "Arabic" ), - ( false, true, "br", "Breton" ), - ( false, true, "ca", "Catalan" ), - ( false, true, "de", "German" ), - ( false, true, "el", "Greek" ), - ( false, true, "en", "English" ), - ( false, true, "eo", "Esperanto" ), - ( false, true, "es", "Spanish" ), - ( false, true, "eu", "Basque" ), - ( true, true, "fa", "Persian" ), - ( false, true, "fr", "French" ), - ( false, true, "he", "Hebrew" ), - ( true, true, "ie", "Interlingue" ), - ( false, true, "it", "Italian" ), - ( false, true, "ja", "Japanese" ), - ( false, true, "ko", "Korean" ), - ( false, true, "la", "Latin" ), - ( false, true, "nl", "Dutch" ), - ( false, true, "no", "Norwegian" ), - ( false, true, "oc", "Occitan" ), - ( false, true, "pl", "Polish" ), - ( false, true, "pt", "Portuguese" ), - ( false, true, "ru", "Russian" ), - ( false, true, "sv", "Swedish" ), - ( false, true, "vi", "Vietnamese" ), - ( false, true, "zh", "Chinese" ), - ( false, true, "zu", "Zulu" ), - -- The things we added because they were not defined in the ISO 639-1 and - -- because we need them to distinguish some karas. - ( true, false, "fx", "Fictional" ), - ( true, false, "ot", "Joker" ); diff --git a/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093114_iso-639-1/down.sql b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093114_iso-639-1/down.sql new file mode 100644 index 0000000000000000000000000000000000000000..7cb99c19129b81d7dd7db57b0b68533dbb2abace --- /dev/null +++ b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093114_iso-639-1/down.sql @@ -0,0 +1 @@ +DELETE FROM iso_639_1; \ No newline at end of file diff --git a/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093114_iso-639-1/up.sql b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093114_iso-639-1/up.sql new file mode 100644 index 0000000000000000000000000000000000000000..775b44c089840d483155887c1dfb113419f55a21 --- /dev/null +++ b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093114_iso-639-1/up.sql @@ -0,0 +1,34 @@ +-- As defined in ISO 639-1: +-- https://archive.wikiwix.com/cache/index2.php?url=http%3A%2F%2Fwww.sil.org%2Fiso639-3%2Fcodes.asp%3Forder%3D639_1%26letter%3D%2525#federation=archive.wikiwix.com&tab=url +INSERT OR REPLACE INTO iso_639_1 (is_macro, is_iso, code, name_en) VALUES + ( true, true, "ar", "Arabic" ), + ( false, true, "br", "Breton" ), + ( false, true, "ca", "Catalan" ), + ( false, true, "de", "German" ), + ( false, true, "el", "Greek" ), + ( false, true, "en", "English" ), + ( false, true, "eo", "Esperanto" ), + ( false, true, "es", "Spanish" ), + ( false, true, "eu", "Basque" ), + ( true, true, "fa", "Persian" ), + ( false, true, "fr", "French" ), + ( false, true, "he", "Hebrew" ), + ( true, true, "ie", "Interlingue" ), + ( false, true, "it", "Italian" ), + ( false, true, "ja", "Japanese" ), + ( false, true, "ko", "Korean" ), + ( false, true, "la", "Latin" ), + ( false, true, "nl", "Dutch" ), + ( false, true, "no", "Norwegian" ), + ( false, true, "oc", "Occitan" ), + ( false, true, "pl", "Polish" ), + ( false, true, "pt", "Portuguese" ), + ( false, true, "ru", "Russian" ), + ( false, true, "sv", "Swedish" ), + ( false, true, "vi", "Vietnamese" ), + ( false, true, "zh", "Chinese" ), + ( false, true, "zu", "Zulu" ), + -- The things we added because they were not defined in the ISO 639-1 and + -- because we need them to distinguish some karas. + ( true, false, "fx", "Fictional" ), + ( true, false, "ot", "Joker" ); \ No newline at end of file diff --git a/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093422_playlists/down.sql b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093422_playlists/down.sql new file mode 100644 index 0000000000000000000000000000000000000000..837d192c96d1822cf0e640aa052b9d1805b70d48 --- /dev/null +++ b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093422_playlists/down.sql @@ -0,0 +1,2 @@ +DELETE TABLE playlist; +DELETE TABLE playlist_kara; \ No newline at end of file diff --git a/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093422_playlists/up.sql b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093422_playlists/up.sql new file mode 100644 index 0000000000000000000000000000000000000000..b9a1a4a064ddac86deb5493912da8a240724b90b --- /dev/null +++ b/src/rust/liblektor-rs/lektor_db/migrations/2023-01-24-093422_playlists/up.sql @@ -0,0 +1,18 @@ +-- A record of all the playlists. They should have all different names. We also +-- store the creation time and the name of the creator for statistics. +CREATE TABLE playlist + ( id BIGINT NOT NULL PRIMARY KEY + , name TEXT NOT NULL UNIQUE + , creator TEXT NOT NULL + , ctime BIGINT NOT NULL DEFAULT (unixepoch()) + ); + +-- Create a link between the karas and the playlists. Here we store the date at +-- which the kara was added to the playlist. Without taking into account the +-- deletions in playlists the mtime of a playlist should be the `MAX(atime)`. +CREATE TABLE playlist_kara + ( playlist_id BIGINT NOT NULL REFERENCES playlist(id) ON DELETE CASCADE + , kara_id BIGINT NOT NULL REFERENCES kara(id) ON DELETE CASCADE + , atime BIGINT NOT NULL DEFAULT (unixepoch()) + , PRIMARY KEY (playlist_id, kara_id) + ); diff --git a/src/rust/liblektor-rs/lektor_db/src/connexion.rs b/src/rust/liblektor-rs/lektor_db/src/connexion.rs index 493584ebb0d8d8d9c4f20c12007c65dcb047f501..fdd7a0c719d28caca482078692b14e25af4308ad 100644 --- a/src/rust/liblektor-rs/lektor_db/src/connexion.rs +++ b/src/rust/liblektor-rs/lektor_db/src/connexion.rs @@ -57,10 +57,10 @@ macro_rules! uri_as_int { match uri_val!($what :/ $uri) { LktUriValue::Integer(int) => int, LktUriValue::String(str) => { - let str = str.parse::<u64>().map_err(|err| { + let str = str.parse::<i64>().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))? + i64::try_from(str).map_err(|err| format!("the {} `{str}` {err}", $what))? } } }; @@ -92,32 +92,38 @@ impl LktDatabaseConnection { }) } - /// Get a tag id by its name. - 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 - )) + /// Get a tag id by its name. If the tag doesn't exists, create it. + fn get_tag_id_by_name(&mut self, tag_name: impl AsRef<str>) -> LktDatabaseResult<i64> { + with_dsl!(tag => self.sqlite.exclusive_transaction(|conn| { + let getter = tag.filter(name.is(tag_name.as_ref())); + match getter.first::<Tag>(conn) { + Ok(Tag { id: tag_id, .. }) => Ok(tag_id), + Err(diesel::result::Error::NotFound) => { + let new_id = tag.select(max(id)).first::<Option<i64>>(conn)?.unwrap_or_default() + 1; + diesel::insert_into(tag).values(NewTag { id: new_id, name: tag_name.as_ref() }).execute(conn)?; + Ok(getter.first::<Tag>(conn)?.id) + } + Err(err) => Err(LktDatabaseError::DieselResult(err)), + }})) } /// Get a free local id for all karas. Note that using this function might /// be unsafe because there is no guarenties that the returned ID will be /// free by the time a kara is inserted with the said id... - pub fn get_kara_new_local_id(&mut self) -> LktDatabaseResult<i32> { - let max = with_dsl!(kara => kara.select(max(id)) - .first::<Option<i32>>(&mut self.sqlite)? - .unwrap_or(0) - ); - Ok(max + 1) + #[deprecated(note = "We must create a new id in a given database to avoid race conditions")] + fn get_kara_new_local_id(&mut self) -> LktDatabaseResult<i64> { + Ok(with_dsl!(kara => kara.select(max(id)) + .first::<Option<i64>>(&mut self.sqlite)? + .unwrap_or_default() + 1 + )) } /// Delete a kara with its id in a repo. pub fn delete_kara_by_repo( &mut self, - arg_repo_id: u64, - arg_kara_id: u64, + arg_repo_id: i64, + arg_kara_id: i64, ) -> LktDatabaseResult<()> { - let arg_repo_id = i32::try_from(arg_repo_id)?; - let arg_kara_id = i32::try_from(arg_kara_id)?; self.sqlite.exclusive_transaction(|c| { let local_id = with_dsl!(repo_kara => repo_kara .filter(repo_id.eq(arg_repo_id)) @@ -132,8 +138,7 @@ impl LktDatabaseConnection { } /// Delete a kara by its local ID. - pub fn delete_kara_by_local_id(&mut self, kara_id: u64) -> LktDatabaseResult<()> { - let local_id = i32::try_from(kara_id)?; + pub fn delete_kara_by_local_id(&mut self, local_id: i64) -> LktDatabaseResult<()> { self.sqlite.exclusive_transaction(|c| { with_dsl!(repo_kara => diesel::delete(repo_kara.filter(local_kara_id.eq(local_id))).execute(c)?); with_dsl!(kara => diesel::delete(kara.filter(id.eq(local_id))).execute(c)?); @@ -143,7 +148,10 @@ impl LktDatabaseConnection { /// Ensure that a given language is present in the database. If it's not /// insert it. Existence test is done on the code of the language. - pub fn ensure_languages_exist<'a>(&mut self, langs: &[Language<'a>]) -> LktDatabaseResult<()> { + pub fn ensure_languages_exist<'a>( + &mut self, + langs: &[NewLanguage<'a>], + ) -> LktDatabaseResult<()> { self.sqlite.exclusive_transaction(|c| { for lang in langs { with_dsl!(iso_639_1 => match iso_639_1.filter(code.eq(lang.code)).count().get_result(c)? { @@ -161,18 +169,17 @@ impl LktDatabaseConnection { let (id, new_kara, karamakers, langs, tags) = kara; self.ensure_languages_exist(&langs)?; self.sqlite.exclusive_transaction(|c| { - with_dsl!(kara => diesel::insert_into(kara).values(&new_kara).execute(c)?); - with_dsl!(repo_kara => diesel::insert_into(repo_kara).values(id).execute(c)?); - with_dsl!(kara_makers => diesel::insert_or_ignore_into(kara_makers).values(karamakers).execute(c)?); - with_dsl!(kara_langs => { - use models::{Language, KaraLanguage}; - let langs = langs.into_iter().map(|Language { code: lang, .. }| KaraLanguage { id: new_kara.id, code: lang } ); - diesel::delete(kara_langs.filter(id.eq(new_kara.id))).execute(c)?; - diesel::insert_into(kara_langs).values(langs.collect::<Vec<_>>()).execute(c)?; + with_dsl!(kara => diesel::insert_into(kara).values(&new_kara).execute(c)?); + with_dsl!(repo_kara => diesel::insert_into(repo_kara).values(id).execute(c)?); + with_dsl!(kara_maker => diesel::insert_or_ignore_into(kara_maker).values(karamakers).execute(c)?); + with_dsl!(kara_lang => { + let langs = langs.into_iter().map(|NewLanguage { code: lang, .. }| NewKaraLanguage { id: new_kara.id, code: lang } ); + diesel::delete(kara_lang.filter(id.eq(new_kara.id))).execute(c)?; + diesel::insert_into(kara_lang).values(langs.collect::<Vec<_>>()).execute(c)?; }); - with_dsl!(kara_tags => { - diesel::delete(kara_tags.filter(kara_id.eq(new_kara.id))).execute(c)?; - diesel::insert_into(kara_tags).values(tags).execute(c)?; + with_dsl!(kara_tag => { + diesel::delete(kara_tag.filter(kara_id.eq(new_kara.id))).execute(c)?; + diesel::insert_into(kara_tag).values(tags).execute(c)?; }); Ok(()) }) @@ -181,25 +188,24 @@ impl LktDatabaseConnection { /// Create a series of models from a kara signature from Kurisu's V1 API. pub fn new_kara_v1<'a>( &mut self, - repo_id: u64, + repo_id: i64, kara: api_v1::Kara<'a>, ) -> LktDatabaseResult<NewKaraRequest<'a>> { let local_id = self.get_kara_new_local_id()?; - let repo_id = i32::try_from(repo_id)?; let id = KaraId { repo_id, local_kara_id: local_id, - repo_kara_id: i32::try_from(kara.id)?, + repo_kara_id: i64::try_from(kara.id)?, }; - let lang = Language::from(kara.get_language()); - let kara_makers = vec![KaraMaker { + let lang = NewLanguage::from(kara.get_language()); + let kara_maker = vec![NewKaraMaker { id: local_id, name: kara.author_name, }]; let tags = vec![AddKaraTag { kara_id: local_id, tag_id: self.get_tag_id_by_name("number")?, - value: Some(format!("{}", kara.song_number)), + value: KaraTagValue::Integer(i64::try_from(kara.song_number)?), }]; let kara = NewKara { id: local_id, @@ -209,16 +215,16 @@ impl LktDatabaseConnection { source_name: kara.source_name, file_hash: format!("{}", kara.unix_timestamp), }; - Ok((id, kara, kara_makers, vec![lang], tags)) + Ok((id, kara, kara_maker, vec![lang], tags)) } /// Peek the next kara to play - pub fn peek_next_kara_in_queue(&self) -> Option<u64> { + pub fn peek_next_kara_in_queue(&self) -> Option<i64> { self.queue.peek_next().copied() } /// Pop the next kara to play - pub fn pop_next_kara_in_queue(&mut self) -> Option<u64> { + pub fn pop_next_kara_in_queue(&mut self) -> Option<i64> { self.queue.pop_next() } @@ -228,27 +234,41 @@ impl LktDatabaseConnection { } /// Add a kara at the end of the queue. - pub fn enqueue_kara(&mut self, local_id: u64) { + pub fn enqueue_kara(&mut self, local_id: i64) { self.queue.enqueue_kara(local_id) } /// Add a kara at the end of a priority in the queue. - pub fn enqueue_kara_with_priority(&mut self, local_id: u64, priority: LktDatabasePriority) { + pub fn enqueue_kara_with_priority(&mut self, local_id: i64, priority: LktDatabasePriority) { self.queue.enqueue_kara_with_priority(local_id, priority) } /// Add a kara at the top of the queue. - pub fn insert_kara(&mut self, local_id: u64) { + pub fn insert_kara(&mut self, local_id: i64) { self.queue.insert_kara(local_id) } /// Add a kara at the top of a priority in the queue. - pub fn insert_kara_with_priority(&mut self, local_id: u64, priority: LktDatabasePriority) { + pub fn insert_kara_with_priority(&mut self, local_id: i64, 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> { + /// Search the queue by URIs. We return the local ids. + pub fn queue_search(&mut self, _uri: LktCUri) -> Result<Vec<i64>, String> { + todo!() + } + + /// Search the given playlist by URIs. We return the local ids. + pub fn playlist_search<S: AsRef<str>>( + &mut self, + _playlist: S, + _uri: LktCUri, + ) -> Result<Vec<i64>, String> { + todo!() + } + + /// Search karas by URIs. We return the local ids. + pub fn database_search(&mut self, uri: LktCUri) -> Result<Vec<i64>, String> { use lektor_c_compat::rs::{LktUriField, LktUriValue}; let uri_type = uri .get_type() @@ -258,35 +278,35 @@ impl LktDatabaseConnection { LktUriField::Id => Ok(vec![with_dsl!(kara => kara .filter(id.is(uri_as_int!("id" :/ uri))) .select(id) - .first::<i32>(&mut self.sqlite) + .first::<i64>(&mut self.sqlite) .map_err(|err| format!("{err}"))? )]), - LktUriField::KaraMaker => Ok(with_dsl!(kara_makers => kara_makers + LktUriField::KaraMaker => Ok(with_dsl!(kara_maker => kara_maker .filter(name.like(uri_str!("author" :/ uri))) - .inner_join(with_dsl!(kara => kara)) + .inner_join(schema::kara::table) .select(id) - .load::<i32>(&mut self.sqlite) + .load::<i64>(&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) + .select(id).load::<i64>(&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) + .select(id).load::<i64>(&mut self.sqlite) .map_err(|err| format!("{err}"))? )), - LktUriField::Language => Ok(with_dsl!(kara_langs => kara_langs + LktUriField::Language => Ok(with_dsl!(kara_lang => kara_lang .filter(code.like(uri_str!("language" :/ uri))) - .inner_join(with_dsl!(kara => kara)) + .inner_join(schema::kara::table) .select(id) - .load::<i32>(&mut self.sqlite) + .load::<i64>(&mut self.sqlite) .map_err(|err| format!("{err}"))? )), @@ -296,28 +316,28 @@ impl LktDatabaseConnection { } LktUriField::Playlist => { - let _ = uri_str!("playlist" :/ uri); - todo!() + let playlist = uri_str!("playlist" :/ uri); + Err(format!( + "need to implement playlists, can't filter for {playlist}" + )) } } } /// 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}"))?; + pub fn get_info(&mut self, local_id: i64) -> Result<(), String> { 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)))) + .inner_join(schema::repo::table) .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 + let _kara_maker: Vec<String> = with_dsl!(kara => kara .filter(id.is(local_id)) - .inner_join(schema::kara_makers::table) - .select(schema::kara_makers::name) + .inner_join(schema::kara_maker::table) + .select(schema::kara_maker::name) .load::<String>(&mut self.sqlite) .map_err(|err| format!("failed to get makers for kara {local_id}: {err}"))? ); diff --git a/src/rust/liblektor-rs/lektor_db/src/lib.rs b/src/rust/liblektor-rs/lektor_db/src/lib.rs index 785bdea0603eb0488abdda0960840643f4c5631d..b29f7c4f2e3a1620c1feea45c53915f7ec755ceb 100644 --- a/src/rust/liblektor-rs/lektor_db/src/lib.rs +++ b/src/rust/liblektor-rs/lektor_db/src/lib.rs @@ -16,7 +16,7 @@ pub(self) use std::{collections::VecDeque, ops::Range, path::Path}; pub type NewKaraRequest<'a> = ( models::KaraId, models::NewKara<'a>, - Vec<models::KaraMaker<'a>>, - Vec<models::Language<'a>>, - Vec<models::AddKaraTag>, + Vec<models::NewKaraMaker<'a>>, + Vec<models::NewLanguage<'a>>, + Vec<models::AddKaraTag<'a>>, ); diff --git a/src/rust/liblektor-rs/lektor_db/src/models.rs b/src/rust/liblektor-rs/lektor_db/src/models.rs index 9cbf5321b415bbf74946b222bb83e470278981fd..fb58d92f8ad99a3c14984ca8da55acd358aefd1a 100644 --- a/src/rust/liblektor-rs/lektor_db/src/models.rs +++ b/src/rust/liblektor-rs/lektor_db/src/models.rs @@ -1,6 +1,7 @@ //! Models used for querying, inserting or updating the database. use crate::{schema::*, *}; +use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types}; use kurisu_api::v1 as api_v1; // First the insertable things @@ -8,22 +9,22 @@ use kurisu_api::v1 as api_v1; #[derive(Insertable)] #[diesel(table_name = repo)] pub struct NewRepo<'a> { - pub id: i32, + pub id: i64, pub name: &'a str, } #[derive(Debug, Insertable, Queryable, Selectable)] #[diesel(table_name = repo_kara)] pub struct KaraId { - pub repo_id: i32, - pub repo_kara_id: i32, - pub local_kara_id: i32, + pub repo_id: i64, + pub repo_kara_id: i64, + pub local_kara_id: i64, } #[derive(Debug, Insertable)] #[diesel(table_name = kara)] pub struct NewKara<'a> { - pub id: i32, + pub id: i64, pub song_title: &'a str, pub song_type: &'a str, pub song_origin: &'a str, @@ -31,38 +32,81 @@ pub struct NewKara<'a> { pub file_hash: String, } -#[derive(Debug, Insertable, Queryable, Selectable)] -#[diesel(table_name = kara_makers)] -pub struct KaraMaker<'a> { - pub id: i32, +#[derive(Debug, Insertable)] +#[diesel(table_name = kara_maker)] +pub struct NewKaraMaker<'a> { + pub id: i64, pub name: &'a str, } -#[derive(Debug, Insertable, Queryable, Selectable)] -#[diesel(table_name = kara_langs)] -pub struct KaraLanguage<'a> { - pub id: i32, +/// Add a new language to a kara. The language must exists in the database. +#[derive(Debug, Insertable)] +#[diesel(table_name = kara_lang)] +pub struct NewKaraLanguage<'a> { + pub id: i64, pub code: &'a str, } -#[derive(Debug, Insertable, Queryable, Selectable)] +/// Add a new language to the database. Will be deprecated. +#[derive(Debug, Insertable)] #[diesel(table_name = iso_639_1)] -pub struct Language<'a> { +pub struct NewLanguage<'a> { pub code: &'a str, pub name_en: &'a str, pub is_iso: bool, pub is_macro: bool, } -#[derive(Insertable)] -#[diesel(table_name = kara_tags)] -pub struct AddKaraTag { - pub kara_id: i32, - pub tag_id: i32, - pub value: Option<String>, +/// Represent possible values for a tag. +#[derive(Debug, FromSqlRow, AsExpression)] +#[diesel(sql_type = sql_types::Nullable<sql_types::Text>)] +pub enum KaraTagValue<'a> { + /// The tag has no value, it's juste present.? + None, + + /// The tag has a string slice value. + Str(&'a str), + + /// The tag has an owned string value. + String(String), + + /// The tag has an integer value. + Integer(i64), +} + +#[derive(Debug, Insertable)] +#[diesel(table_name = kara_tag)] +pub struct AddKaraTag<'a> { + pub kara_id: i64, + pub tag_id: i64, + pub value: KaraTagValue<'a>, +} + +#[derive(Debug, Insertable)] +#[diesel(table_name = tag)] +pub struct NewTag<'a> { + pub id: i64, + pub name: &'a str, +} + +impl<'a> diesel::serialize::ToSql<sql_types::Nullable<sql_types::Text>, diesel::sqlite::Sqlite> + for KaraTagValue<'a> +{ + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, diesel::sqlite::Sqlite>, + ) -> diesel::serialize::Result { + match self { + KaraTagValue::None => return Ok(diesel::serialize::IsNull::Yes), + KaraTagValue::Str(str) => out.set_value(*str), + KaraTagValue::String(str) => out.set_value(str.as_str()), + KaraTagValue::Integer(int) => out.set_value(format!("{int}")), + }; + Ok(diesel::serialize::IsNull::No) + } } -impl<'a> From<api_v1::Language<'a>> for Language<'a> { +impl<'a> From<api_v1::Language<'a>> for NewLanguage<'a> { fn from(lang: api_v1::Language<'a>) -> Self { Self { code: lang.code, @@ -78,14 +122,14 @@ impl<'a> From<api_v1::Language<'a>> for Language<'a> { #[derive(Queryable, Selectable)] #[diesel(table_name = tag)] pub struct Tag { - pub id: i32, + pub id: i64, pub name: String, } #[derive(Queryable, Selectable)] #[diesel(table_name = kara)] pub struct Kara { - pub id: i32, + pub id: i64, pub is_dl: bool, pub song_title: String, pub song_type: String, diff --git a/src/rust/liblektor-rs/lektor_db/src/queue.rs b/src/rust/liblektor-rs/lektor_db/src/queue.rs index f41050a281fa2c54e77adbbab7ebaa6054457c69..54e3f1434ddec7bafabb3ecb26071711f9b72bd4 100644 --- a/src/rust/liblektor-rs/lektor_db/src/queue.rs +++ b/src/rust/liblektor-rs/lektor_db/src/queue.rs @@ -43,7 +43,6 @@ impl_from_for_proprity!(i16); impl_from_for_proprity!(i32); impl_from_for_proprity!(i64); -impl_into_for_priority!(i32); impl_into_for_priority!(usize); /// The iterator for the database queue. The iterator returns references to @@ -64,7 +63,7 @@ pub struct LktDatabaseQueueRangeIter<'a> { } impl<'a> Iterator for LktDatabaseQueueIter<'a> { - type Item = &'a u64; + type Item = &'a i64; fn next(&mut self) -> Option<Self::Item> { let priority = self.priority?.clamp(0, LKT_DATABASE_QUEUES_COUNT - 1); @@ -84,7 +83,7 @@ impl<'a> Iterator for LktDatabaseQueueIter<'a> { } impl<'a> Iterator for LktDatabaseQueueRangeIter<'a> { - type Item = &'a u64; + type Item = &'a i64; fn next(&mut self) -> Option<Self::Item> { let new_remaining = self.remaining.saturating_sub(1); @@ -100,24 +99,24 @@ impl<'a> Iterator for LktDatabaseQueueRangeIter<'a> { /// The queue datastructure used for storing karas in the queue. #[derive(Debug, Default)] pub struct LktDatabaseQueue { - levels: [VecDeque<u64>; LKT_DATABASE_QUEUES_COUNT], + levels: [VecDeque<i64>; LKT_DATABASE_QUEUES_COUNT], } impl LktDatabaseQueue { - pub fn enqueue_kara(&mut self, local_id: u64) { + pub fn enqueue_kara(&mut self, local_id: i64) { self.levels[0].push_back(local_id); } - pub fn enqueue_kara_with_priority(&mut self, local_id: u64, priority: LktDatabasePriority) { + pub fn enqueue_kara_with_priority(&mut self, local_id: i64, priority: LktDatabasePriority) { let priority: usize = priority.into(); self.levels[priority].push_back(local_id); } - pub fn insert_kara(&mut self, local_id: u64) { + pub fn insert_kara(&mut self, local_id: i64) { self.levels[LKT_DATABASE_QUEUES_COUNT - 1].push_front(local_id); } - pub fn insert_kara_with_priority(&mut self, local_id: u64, priority: LktDatabasePriority) { + pub fn insert_kara_with_priority(&mut self, local_id: i64, priority: LktDatabasePriority) { let priority: usize = priority.into(); self.levels[priority].push_front(local_id); } @@ -136,11 +135,11 @@ impl LktDatabaseQueue { } } - pub fn peek_next(&self) -> Option<&u64> { + pub fn peek_next(&self) -> Option<&i64> { self.iter().next() } - pub fn pop_next(&mut self) -> Option<u64> { + pub fn pop_next(&mut self) -> Option<i64> { let level = self .levels .iter_mut() diff --git a/src/rust/liblektor-rs/lektor_db/src/schema.rs b/src/rust/liblektor-rs/lektor_db/src/schema.rs index 48a94b8b58a6fbb8635865c97b91e6a3fbe91e65..977a01e8f5327e4c0fa83e3cb619d2c4663b39ba 100644 --- a/src/rust/liblektor-rs/lektor_db/src/schema.rs +++ b/src/rust/liblektor-rs/lektor_db/src/schema.rs @@ -2,8 +2,8 @@ diesel::table! { history (epoch) { - id -> Integer, - epoch -> Integer, + id -> BigInt, + epoch -> BigInt, } } @@ -18,7 +18,7 @@ diesel::table! { diesel::table! { kara (id) { - id -> Integer, + id -> BigInt, is_dl -> Bool, song_title -> Text, song_type -> Text, @@ -29,55 +29,74 @@ diesel::table! { } diesel::table! { - kara_langs (id, code) { - id -> Integer, + kara_lang (id, code) { + id -> BigInt, code -> Text, } } diesel::table! { - kara_makers (id, name) { - id -> Integer, + kara_maker (id, name) { + id -> BigInt, name -> Text, } } diesel::table! { - kara_tags (kara_id, tag_id, value) { - kara_id -> Integer, - tag_id -> Integer, + kara_tag (kara_id, tag_id, value) { + kara_id -> BigInt, + tag_id -> BigInt, value -> Nullable<Text>, } } +diesel::table! { + playlist (id) { + id -> BigInt, + name -> Text, + creator -> Text, + ctime -> BigInt, + } +} + +diesel::table! { + playlist_kara (playlist_id, kara_id) { + playlist_id -> BigInt, + kara_id -> BigInt, + atime -> BigInt, + } +} + diesel::table! { repo (id) { - id -> Integer, + id -> BigInt, name -> Text, } } diesel::table! { repo_kara (repo_id, repo_kara_id, local_kara_id) { - repo_id -> Integer, - repo_kara_id -> Integer, - local_kara_id -> Integer, + repo_id -> BigInt, + repo_kara_id -> BigInt, + local_kara_id -> BigInt, } } diesel::table! { tag (id) { - id -> Integer, + id -> BigInt, name -> Text, } } diesel::joinable!(history -> kara (id)); -diesel::joinable!(kara_langs -> iso_639_1 (code)); -diesel::joinable!(kara_langs -> kara (id)); -diesel::joinable!(kara_makers -> kara (id)); -diesel::joinable!(kara_tags -> kara (kara_id)); -diesel::joinable!(kara_tags -> tag (tag_id)); +diesel::joinable!(kara_lang -> iso_639_1 (code)); +diesel::joinable!(kara_lang -> kara (id)); +diesel::joinable!(kara_maker -> kara (id)); +diesel::joinable!(kara_tag -> kara (kara_id)); +diesel::joinable!(kara_tag -> tag (tag_id)); +diesel::joinable!(playlist_kara -> kara (kara_id)); +diesel::joinable!(playlist_kara -> playlist (playlist_id)); diesel::joinable!(repo_kara -> kara (local_kara_id)); diesel::joinable!(repo_kara -> repo (repo_id)); @@ -85,9 +104,11 @@ diesel::allow_tables_to_appear_in_same_query!( history, iso_639_1, kara, - kara_langs, - kara_makers, - kara_tags, + kara_lang, + kara_maker, + kara_tag, + playlist, + playlist_kara, repo, repo_kara, tag, diff --git a/src/rust/liblektor-rs/lektor_unsafe/src/db.rs b/src/rust/liblektor-rs/lektor_unsafe/src/db.rs index 2468c6b55b5aa3a38dea03a2bff10032c4139a8f..a17a4fe57014f00724296483cd1e700182d5a663 100644 --- a/src/rust/liblektor-rs/lektor_unsafe/src/db.rs +++ b/src/rust/liblektor-rs/lektor_unsafe/src/db.rs @@ -57,8 +57,8 @@ pub unsafe extern "C" fn lkt_database_close_connection(db: *mut LktDatabaseConne #[no_mangle] pub unsafe extern "C" fn lkt_database_delete_kara_by_repo( db: *mut LktDatabaseConnection, - repo: u64, - kara: u64, + repo: i64, + kara: i64, ) -> bool { let db = db.as_mut().expect("passing a nullptr as db handle"); db.delete_kara_by_repo(repo, kara) @@ -75,7 +75,7 @@ pub unsafe extern "C" fn lkt_database_delete_kara_by_repo( #[no_mangle] pub unsafe extern "C" fn lkt_database_delete_kara_by_local_id( db: *mut LktDatabaseConnection, - local_id: u64, + local_id: i64, ) -> bool { let db = db.as_mut().expect("passing a nullptr as db handle"); db.delete_kara_by_local_id(local_id) @@ -90,7 +90,7 @@ pub unsafe extern "C" fn lkt_database_delete_kara_by_local_id( #[no_mangle] pub unsafe extern "C" fn lkt_database_get_kara_info( db: *mut LktDatabaseConnection, - local_id: u64, + local_id: i64, cb: extern "C" fn(*const c_char, *const c_char, *mut c_void), user: *mut c_void, ) -> bool { @@ -104,7 +104,7 @@ pub unsafe extern "C" fn lkt_database_get_kara_info( #[no_mangle] pub unsafe extern "C" fn lkt_database_get_kara_tags( db: *mut LktDatabaseConnection, - local_id: u64, + local_id: i64, cb: extern "C" fn(*const c_char, *const c_char, *mut c_void), user: *mut c_void, ) -> bool {