diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs index af869c13c7d22c42b3085b94f2c0778654f0a7f7..fde30c99b9cbde913aefff656a87af02fb23fd06 100644 --- a/amadeus/src/app.rs +++ b/amadeus/src/app.rs @@ -650,7 +650,7 @@ impl Application for Amadeus { Message::RefreshRequest(RefreshRequest::Playlists), Message::ConfigMessage(config::Message::Infos(Some(infos))), ]), - Err(_) => Message::ConnectionStatus(true), + Err(_) => Message::ConnectionStatus(false), }) } Message::SmollTick(instant) => { @@ -858,8 +858,8 @@ impl Application for Amadeus { fn subscription(&self) -> iced::Subscription<Self::Message> { use iced::time::{every, Duration}; let (smoll_time, big_time) = either!(self.is_connected => - (Some(Duration::new(1, 0)), Duration::new(20, 0)); - (None, Duration::new(30, 0)) + (Some(Duration::new(1, 0)), self.config.amadeus.retry_time_interval()); + (None, self.config.amadeus.retry_time_interval()) ); let keycodes = iced::subscription::events().map(|event| match event { Event::Keyboard(KbdEvent::KeyReleased { diff --git a/amadeus/src/components/config/mod.rs b/amadeus/src/components/config/mod.rs index 9b21e81bf7a707436d64ee06e3b3876e0227a649..e0ef98f6b7e434d97f7d25ff8622dee2a7e6e769 100644 --- a/amadeus/src/components/config/mod.rs +++ b/amadeus/src/components/config/mod.rs @@ -18,6 +18,7 @@ use iced::{ use lektor_payloads::Infos; use lektor_utils::{ config::{SocketScheme, UserConfig}, + either, log::{self, Level as LogLevel}, logger, }; @@ -29,6 +30,7 @@ pub struct State { scroll_id: scrollable::Id, current_scroll_offset: scrollable::RelativeOffset, + tmp_retry_time: u16, tmp_scheme: String, tmp_host: String, tmp_port: u16, @@ -38,6 +40,14 @@ pub struct State { system_informations: Option<iced::system::Information>, } +fn clamp_scale_factor(scale: impl Into<f64>) -> f64 { + scale.into().clamp(0.5, 1.5) +} + +fn clamp_time_interval(time: impl Into<i64>) -> u16 { + time.into().clamp(10, u16::MAX as i64) as u16 +} + /// Amadeus configuration. #[derive(Debug, Deserialize, Serialize, Clone)] pub struct AmadeusConfig { @@ -58,8 +68,16 @@ pub struct UIConfig { pub theme: theme::Message, #[serde(default = "serde_utils::get_1_f64")] + #[serde(serialize_with = "serde_utils::serialize_scale_factor")] + #[serde(deserialize_with = "serde_utils::deserialize_scale_factor")] scale_factor: f64, + /// Retry interval in seconds. + #[serde(default = "serde_utils::get_20_u16")] + #[serde(serialize_with = "serde_utils::serialize_interval")] + #[serde(deserialize_with = "serde_utils::deserialize_interval")] + retry_interval_sec: u16, + #[serde(default = "serde_utils::get_true")] pub open_config_if_init_ping_failed: bool, } @@ -70,6 +88,7 @@ pub struct RemoteConfig { pub scheme: SocketScheme, pub host: SocketAddr, pub user: UserConfig, + pub kurisu_token: Option<String>, } /// Messages used to update the config file @@ -121,12 +140,19 @@ pub enum Message { /// holded a connection we now need to reconnect for sure. TokenChanged(String), + /// The token for kurisu changed. We do this so that we can override the token from lektord + /// config with one supplied by amadeus. + KurisuTokenChanged(String), + /// Open the settings page if lektord was offline when we launched amadeus. OpenConfigIfInitPingFailed(bool), /// The scale factor changed. ScaleFactorChanged(f64), + /// The retry interval time changed. + RetryIntervalChanged(u16), + /// The config was scrolled. Scrolled(Viewport), @@ -158,15 +184,61 @@ pub enum Request { impl UIConfig { /// Get the scale factor, should only be between 0.5 and 2.0 pub fn scale_factor(&self) -> f64 { - self.scale_factor.clamp(0.5, 2.0) + clamp_scale_factor(self.scale_factor) + } + + /// Get the retry time for when we are offline. + pub fn retry_time_interval(&self) -> iced::time::Duration { + iced::time::Duration::new(clamp_time_interval(self.retry_interval_sec).into(), 0) } } #[rustfmt::skip] mod serde_utils { - pub(super) fn get_1_f64() -> f64 { 1.0 } - pub(super) fn get_true() -> bool { true } - pub(super) fn get_false() -> bool { true } + pub(super) fn get_1_f64() -> f64 { 1.0 } + pub(super) fn get_20_u16() -> u16 { 20 } + pub(super) fn get_true() -> bool { true } + pub(super) fn get_false() -> bool { true } + + pub(super) fn serialize_scale_factor<S: serde::Serializer>(v: &f64, ser: S) -> Result<S::Ok, S::Error> { ser.serialize_f64(*v) } + pub(super) fn deserialize_scale_factor<'de, D: serde::Deserializer<'de>>(de: D) -> Result<f64, D::Error> { + struct FloadVisitor; + impl<'de> serde::de::Visitor<'de> for FloadVisitor { + type Value = f64; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("f32 or f64") } + fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_f32<E: serde::de::Error>(self, v: f32) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_i16<E: serde::de::Error>(self, v: i16) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_u16<E: serde::de::Error>(self, v: u16) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_i8 <E: serde::de::Error>(self, v: i8 ) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + fn visit_u8 <E: serde::de::Error>(self, v: u8 ) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } + } + de.deserialize_any(FloadVisitor) + } + + pub(super) fn serialize_interval<S: serde::Serializer>(v: &u16, ser: S) -> Result<S::Ok, S::Error> { ser.serialize_u16(*v) } + pub(super) fn deserialize_interval<'de, D: serde::Deserializer<'de>>(de: D) -> Result<u16, D::Error> { + struct FloadVisitor; + impl<'de> serde::de::Visitor<'de> for FloadVisitor { + type Value = u16; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("u16") } + fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_f32<E: serde::de::Error>(self, v: f32) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_i16<E: serde::de::Error>(self, v: i16) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_u16<E: serde::de::Error>(self, v: u16) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_i8 <E: serde::de::Error>(self, v: i8 ) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_u8 <E: serde::de::Error>(self, v: u8 ) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } + fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> { + Ok(super::clamp_time_interval(v.clamp(0, i64::MAX as u64) as i64)) + } + } + de.deserialize_any(FloadVisitor) + } } impl Default for State { @@ -175,6 +247,7 @@ impl Default for State { scroll_id: scrollable::Id::unique(), current_scroll_offset: scrollable::RelativeOffset::START, config: Default::default(), + tmp_retry_time: serde_utils::get_20_u16(), tmp_scheme: Default::default(), tmp_host: Default::default(), tmp_port: Default::default(), @@ -198,6 +271,7 @@ impl Default for RemoteConfig { Self { scheme: SocketScheme::Http, host: SocketAddr::new([127, 0, 0, 1].into(), 6600), + kurisu_token: None, user: UserConfig { admin: false, ..Default::default() @@ -243,7 +317,8 @@ impl State { }); }; macro_rules! connect_to { - () => { + ($expr: expr) => {{ + $expr; match self.tmp_host.parse() { Ok(host) => { config.connect.host = SocketAddr::new(host, self.tmp_port); @@ -251,7 +326,13 @@ impl State { } _ => return Command::none(), } - }; + }}; + } + macro_rules! write_config { + ($reload: expr, $expr: expr) => {{ + $expr; + self.write_config($reload) + }}; } match message { Message::None | Message::OpenLinkInBrowser(_) => { @@ -270,6 +351,7 @@ impl State { SocketAddr::V6(v6) => (v6.ip().to_string(), v6.port()), }; self.tmp_scheme = new.connect.scheme.to_string(); + self.tmp_retry_time = clamp_time_interval(new.amadeus.retry_interval_sec); *config = new; // Some times we add things to the config, we need to re-write the config when we // load it completly to apply the new defaults. @@ -282,53 +364,50 @@ impl State { Command::none() } - Message::OpenConfigIfInitPingFailed(flag) => { - config.amadeus.open_config_if_init_ping_failed = flag; - self.write_config(Request::None) - } - Message::ScaleFactorChanged(scale) => { - config.amadeus.scale_factor = scale.clamp(0.5, 2.0); - self.write_config(Request::None) - } + Message::OpenConfigIfInitPingFailed(flag) => write_config!( + Request::None, + config.amadeus.open_config_if_init_ping_failed = flag + ), + Message::RetryIntervalChanged(interval) => write_config!(Request::None, { + config.amadeus.retry_interval_sec = interval; + self.tmp_retry_time = interval; + }), + Message::ScaleFactorChanged(scale) => write_config!( + Request::None, + config.amadeus.scale_factor = clamp_scale_factor(scale) + ), Message::ThemeChanged(theme) => { - config.amadeus.theme = theme; - self.write_config(Request::None) + write_config!(Request::None, config.amadeus.theme = theme) } Message::UserChanged(user) => { - config.connect.user.user = user; - self.write_config(Request::Reconnect) + write_config!(Request::Reconnect, config.connect.user.user = user) } Message::TokenChanged(token) => { - config.connect.user.token = token; - self.write_config(Request::Reconnect) - } - Message::LogLevelChanged(level) => { - config.log = level; - logger::level(level); - self.write_config(Request::None) + write_config!(Request::Reconnect, config.connect.user.token = token) } + Message::KurisuTokenChanged(token) => write_config!( + Request::Reconnect, + config.connect.kurisu_token = either!(token.is_empty() => None; Some(token)) + ), Message::MprisFlagChanged(flag) => { - config.mpris = flag; - self.write_config(Request::ToggleMprisServer(flag)) + write_config!(Request::ToggleMprisServer(flag), config.mpris = flag) } + Message::LogLevelChanged(level) => write_config!(Request::None, { + config.log = level; + logger::level(level); + }), Message::SchemeChanged(scheme) => { self.tmp_scheme = scheme.to_string(); if let Ok(scheme) = scheme.parse() { - config.connect.scheme = scheme; - return self.write_config(Request::None); + write_config!(Request::None, config.connect.scheme = scheme) + } else { + Command::none() } - Command::none() } - Message::PortChanged(port) => { - self.tmp_port = port; - connect_to!() - } - Message::HostChanged(host) => { - self.tmp_host = host; - connect_to!() - } + Message::PortChanged(port) => connect_to!(self.tmp_port = port), + Message::HostChanged(host) => connect_to!(self.tmp_host = host), Message::Scrolled(viewport) => { self.current_scroll_offset = viewport.relative_offset(); @@ -342,10 +421,16 @@ impl State { ($name: literal => [ $($content: expr),+ $(,)? ]) => { Some(container(column![ text(concat!("# ", $name)).size(SIZE_FONT_MEDIUM), - vertical_space(10), + vertical_space(5), $($content),+, - vertical_space(20) - ]).width(Length::Fixed(500.0))) + vertical_space(15) + ].spacing(5)).width(Length::Fixed(500.0))) + }; + } + + macro_rules! elem_row { + ($title: literal, $what: expr) => { + row![text($title), horizontal_space(Length::Fill), $what] }; } @@ -358,20 +443,21 @@ impl State { ] .align_items(Alignment::Start), input!(@$what $($portion)?; $($args),+ => $event), - vertical_space(10), + vertical_space(5), ] }; - (@number $($portion: literal)?; $tip: expr, $value: expr, $min: expr, $max: expr => $event: ident) => { + (@number $($portion: literal)?; $tip: expr, $value: expr => $event: ident) => { text_input($tip, &$value.to_string()) $(.width(Length::Fixed($portion)))? .size(SIZE_FONT_NORMAL) .padding(5) - .on_input(|str| { - match str.parse() { - Ok(int) if ($min..=$max).contains(&int) => Message::$event(int), - _ => Message::None, - } + .on_input(|str| if str.trim().is_empty() { + Message::$event(Default::default()) + } else { + str.trim().parse() + .map(Message::$event) + .unwrap_or(Message::None) }) }; @@ -393,10 +479,6 @@ impl State { }; } - let token_link = - icon!(SIZE_FONT_SMALL | Link -> Message::OpenLinkInBrowser(KURISU_TOKEN_LINK)) - .padding(0); - macro_rules! scale_factor { ($factor: literal, $($factors: literal),+) => { row![scale_factor!($factor), $(scale_factor!($factors)),+] @@ -416,18 +498,19 @@ impl State { }; } + let token_link = + icon!(SIZE_FONT_SMALL | Link -> Message::OpenLinkInBrowser(KURISU_TOKEN_LINK)) + .padding(0); + let content = container([ section![ "Misc" => [ self.loglevel.view(self.config.log).map(Message::LogLevelChanged), - vertical_space(5), toggler(Some("MPRIS server".into()), self.config.mpris, Message::MprisFlagChanged) - .spacing(15) .text_size(SIZE_FONT_NORMAL) .size(SIZE_FONT_NORMAL), ]], section![ "Amadeus" => [ self.theme.view(&self.config.amadeus.theme).map(Message::ThemeChanged), - vertical_space(5), toggler( Some("Open settings when lektord is offline on launch".into()), self.config.amadeus.open_config_if_init_ping_failed, @@ -435,21 +518,14 @@ impl State { ) .text_size(SIZE_FONT_NORMAL) .size(SIZE_FONT_NORMAL), - vertical_space(5), - row![ - text("Scale factor").size(SIZE_FONT_NORMAL), - horizontal_space(Length::Fill), - scale_factor! { 0.5, 1.0, 1.5, 2.0 } - ], + elem_row!["Scale factor", scale_factor! { 0.5, 0.75, 1.0, 1.25, 1.5 }], + input!(number! "Retry interval (sec)", "seconds", self.tmp_retry_time => RetryIntervalChanged), ]], section![ "Remote" => [ row![ - input!(text! (100.0) "Scheme", "http", &self.tmp_scheme => SchemeChanged), - horizontal_space(Length::Fixed(1.0)), - input!(text! (270.0) "Host", "host", &self.tmp_host => HostChanged), - horizontal_space(Length::Fixed(1.0)), - input!(number! (100.0) "Port", "6600", self.tmp_port, 1024, u16::max_value() => PortChanged), - horizontal_space(Length::Fill), + input!(text! (100.0) "Scheme", "http", &self.tmp_scheme => SchemeChanged), + input!(text! (270.0) "Host", "host", &self.tmp_host => HostChanged), + input!(number! (100.0) "Port", "6600", self.tmp_port => PortChanged), column![ tip!(button(text(iced_aw::graphics::icons::icon_to_char(iced_aw::graphics::icons::Icon::Check)) .horizontal_alignment(Horizontal::Center) @@ -463,66 +539,34 @@ impl State { None => Box::<SquareButtonStyleSheet>::default(), } )).on_press(Message::TryConnect) => Right | " Try to connect"), - vertical_space(10) + vertical_space(5) ] ] + .spacing(1) .align_items(Alignment::End) .width(Length::Fixed(500.0)), - input!(text! "User", "user", &self.config.connect.user.user => UserChanged), - input!(password! "Token" token_link, KURISU_TOKEN_LINK, &self.config.connect.user.token => TokenChanged), + input!(text! "User", "user", &self.config.connect.user.user => UserChanged), + input!(password! "Token", "token", &self.config.connect.user.token => TokenChanged), + input!(password! "Kurisu Token" token_link, KURISU_TOKEN_LINK, + self.config.connect.kurisu_token.as_ref().map(|token| token.as_str()).unwrap_or_default() + => KurisuTokenChanged + ), ]], match &self.remote_infos { Some(Some(infos)) => section![ "Remote Infos" => [ - row![ - text("Server version").size(SIZE_FONT_NORMAL), - horizontal_space(Length::Fill), - text(&infos.version).size(SIZE_FONT_NORMAL), - ], - row![ - text("Database epoch").size(SIZE_FONT_NORMAL), - horizontal_space(Length::Fill), - text(infos.last_epoch.map(|x| x.to_string().into()).unwrap_or(Cow::Borrowed("None"))).size(SIZE_FONT_NORMAL), - ], + elem_row!["Server version", text(&infos.version)], + elem_row!["Database epoch", text(infos.last_epoch.map(|x| x.to_string().into()).unwrap_or(Cow::Borrowed("None")))], ]], _ => None, }, match &self.system_informations { Some(sys) => section! [ "System Infos" => [ - row![ - text("System name"), - horizontal_space(Length::Fill), - text(sys.system_name.as_deref().unwrap_or("...")), - ], - vertical_space(5), - row![ - text("Kernel name"), - horizontal_space(Length::Fill), - text(sys.system_kernel.as_deref().unwrap_or("..")), - ], - vertical_space(5), - row![ - text("System version"), - horizontal_space(Length::Fill), - text(sys.system_version.as_deref().unwrap_or("..")), - ], - vertical_space(5), - row![ - text("CPU"), - horizontal_space(Length::Fill), - text(format!("{}, {} cores", sys.cpu_brand.as_str(), sys.cpu_cores.unwrap_or(1))), - ], - vertical_space(5), - row![ - text("Total memory"), - horizontal_space(Length::Fill), - text(format!("{}GiB", sys.memory_total / (1024 * 1024 * 1024))), - ], - vertical_space(5), - row![ - text("Graphics backend and adapter"), - horizontal_space(Length::Fill), - text(format!("{}, {}", sys.graphics_backend, sys.graphics_adapter)), - ], + elem_row!["System name", text(sys.system_name.as_deref().unwrap_or("..."))], + elem_row!["Kernel name", text(sys.system_kernel.as_deref().unwrap_or("..."))], + elem_row!["System version", text(sys.system_version.as_deref().unwrap_or("..."))], + elem_row!["CPU", text(format!("{}, {} cores", sys.cpu_brand.as_str(), sys.cpu_cores.unwrap_or(1)))], + elem_row!["Total memory", text(format!("{}GiB", sys.memory_total / (1024 * 1024 * 1024)))], + elem_row!["Graphics backend and adapter", text(format!("{}, {}", sys.graphics_backend, sys.graphics_adapter))], ]], _ => None, }