diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs index fde30c99b9cbde913aefff656a87af02fb23fd06..262a7dd4b4398625359a7fb0b83e762ce4c4477f 100644 --- a/amadeus/src/app.rs +++ b/amadeus/src/app.rs @@ -37,6 +37,9 @@ pub struct Amadeus { /// The last recieved instant, to calculate the delta and maybe time some animations. last_instant: iced::time::Instant, + /// The last database epoch found. + last_epoch: Option<u64>, + /// The configuration, can be edited with the settings modal. config: config::State, @@ -97,6 +100,7 @@ impl Default for Amadeus { show_main_panel: false, is_connected: false, is_init: false, + last_epoch: None, // Has default. connect_config: Default::default(), @@ -120,12 +124,27 @@ impl Amadeus { id: KId, cb: impl FnOnce(Arc<Kara>) -> Message + 'static + Send, ) -> Command<Message> { - let store = self.kara_store.clone(); - Command::perform(async move { store.get(id).await }, |kara| { + Command::perform(KaraStore::into_get(self.kara_store.clone(), id), |kara| { kara.map(cb).into() }) } + /// Commands to issue to clear data from the [Amadeus] application. + fn clear() -> Command<Message> { + Message::from_iter([ + Message::ConfigMessage(config::Message::Infos(None)), + Message::MainPanelMessage(mainpanel::Message::Queue(mainpanel::queue::Message::Clear)), + Message::MainPanelMessage(mainpanel::Message::History( + mainpanel::history::Message::Clear, + )), + Message::MainPanelMessage(mainpanel::Message::Playlists( + mainpanel::playlists::Message::Clear, + )), + Message::SidebarMessage(sidebar::Message::ClearPlaylists), + ]) + .perform() + } + fn get_history(&self) -> impl Future<Output = Option<Vec<Arc<Kara>>>> { let cfg = self.connect_config.clone(); let store = self.kara_store.clone(); @@ -298,8 +317,8 @@ impl Amadeus { send(remove_from_playlist(cfg, name, KaraFilter::KId(id))) } playlists::Request::ChangeView => { - let show = either!(self.is_connected => MainPanelDisplay::MainPanel(mainpanel::Show::Queue); MainPanelDisplay::Config); - Command::perform(async {}, |_| Message::MainPanelDisplay(show)) + Message::MainPanelDisplay(either!(self.is_connected => MainPanelDisplay::MainPanel(mainpanel::Show::Queue); MainPanelDisplay::Config)) + .perform() } }, @@ -370,8 +389,7 @@ impl Amadeus { playlists::Message::RemoveKaraFromPlaylist(name, id.clone()).into(), ]) }); - let action = |_| ShowModal::ChoosePlaylist(plts, callback).into(); - Command::perform(async {}, action) + Message::from(ShowModal::ChoosePlaylist(plts, callback)).perform() } (Some(plt), AddToPlaylist(id)) => { let plts = self.playlist_list().to_vec(); @@ -421,7 +439,12 @@ impl Amadeus { } /// Perform the init ping if needed - fn command_init_ping(&self) -> Command<<Self as Application>::Message> { + fn command_init_ping(&mut self) -> Command<<Self as Application>::Message> { + if self.is_init { + log::error!("try to send init ping multiple times"); + return Command::none(); + } + self.is_init = true; let flag = self.config.amadeus.open_config_if_init_ping_failed; Command::perform(get_infos(self.connect_config.clone()), move |res| { Message::from_err_or_elses( @@ -508,9 +531,7 @@ impl Application for Amadeus { res.map_err(|err| anyhow!("load icon font err: {err:?}")) .into() }), - Command::perform(async {}, move |()| { - config::Message::LoadConfig(config).into() - }), + Message::from(config::Message::LoadConfig(config)).perform(), iced::system::fetch_information(config::Message::SystemInformations).map(Message::from), ]); (Default::default(), init_events) @@ -583,11 +604,10 @@ impl Application for Amadeus { } // Open a link. - Message::OpenLinkInBrowser(link) => Command::perform(async {}, move |_| { + Message::OpenLinkInBrowser(link) => Message::into_perform( lektor_utils::open::that(link) - .map_err(|err| anyhow!("failed to open {link}: {err:?}")) - .into() - }), + .map_err(|err| anyhow!("failed to open {link}: {err:?}")), + ), // Kill lektord & exit the application Message::ExitApplication => iced::window::close(), @@ -610,21 +630,7 @@ impl Application for Amadeus { Command::batch([ flag.then(|| self.update(Message::MainPanelDisplay(MainPanelDisplay::Config))) .unwrap_or_else(Command::none), - Command::perform(async {}, |_| { - Message::from_iter([ - Message::ConfigMessage(config::Message::Infos(None)), - Message::MainPanelMessage(mainpanel::Message::Queue( - mainpanel::queue::Message::Clear, - )), - Message::MainPanelMessage(mainpanel::Message::History( - mainpanel::history::Message::Clear, - )), - Message::MainPanelMessage(mainpanel::Message::Playlists( - mainpanel::playlists::Message::Clear, - )), - Message::SidebarMessage(sidebar::Message::ClearPlaylists), - ]) - }), + Amadeus::clear(), ]) } Message::ConnectionStatus(_) => Command::none(), @@ -655,7 +661,6 @@ impl Application for Amadeus { } Message::SmollTick(instant) => { self.last_instant = self.last_instant.max(instant); - let cfg = self.connect_config.clone(); let queue = Command::perform(self.get_queue(), |res| { res.map(|queue| { @@ -675,44 +680,47 @@ impl Application for Amadeus { .unwrap_or(Message::ConnectionStatus(false)) }); - let status = Command::perform(get_status(cfg), |res| match res { - Ok(PlayStateWithCurrent { - state: s @ PlayState::Play | s @ PlayState::Pause, - current: Some((id, elapsed, duration)), - }) => Message::from_iter([ - Message::ChangedPlayback(s), - Message::ChangedKaraId(id), - Message::TimeUpdate(elapsed, duration), - ]), - Ok(PlayStateWithCurrent { - state: PlayState::Stop, - current: None, - }) => Message::ChangedPlayback(PlayState::Stop), - Ok(state) => { - log::error!("got incoherent state from the server: {state:?}"); - Message::ChangedPlayback(PlayState::Stop) - } - Err(_) => Message::ConnectionStatus(false), - }); + let status = + Command::perform(get_status(self.connect_config.clone()), |res| match res { + Ok(PlayStateWithCurrent { + state: s @ PlayState::Play | s @ PlayState::Pause, + current: Some((id, elapsed, duration)), + }) => Message::from_iter([ + Message::ChangedPlayback(s), + Message::ChangedKaraId(id), + Message::TimeUpdate(elapsed, duration), + ]), + Ok(PlayStateWithCurrent { + state: PlayState::Stop, + current: None, + }) => Message::ChangedPlayback(PlayState::Stop), + Ok(state) => { + log::error!("got incoherent state from the server: {state:?}"); + Message::ChangedPlayback(PlayState::Stop) + } + Err(_) => Message::ConnectionStatus(false), + }); Command::batch([queue, history, status]) } // Config changed Message::ConfigMessage(config) => self.handle_config_message(config), - Message::ConfigLoaded => { - log::info!("config was loaded, start rendering the application"); - self.is_init = true; - self.command_init_ping() - } - Message::ReconnectToLektord(flush) => { - log::error!("need to handle the reconnect message with flush status: {flush}"); - Command::none() - } - Message::DatabaseEpoch(epoch) => { - log::error!("need to handle epoch {epoch}"); - Command::none() - } + Message::ConfigLoaded => self.command_init_ping(), + Message::ReconnectToLektord => Command::batch([ + send(KaraStore::into_clear(self.kara_store.clone())), + Amadeus::clear(), + ]), + Message::DatabaseEpoch(epoch) => match self.last_epoch.take() { + Some(last_epoch) if last_epoch != epoch => { + self.last_epoch = Some(last_epoch.max(epoch)); + Message::ReconnectToLektord.perform() + } + _ => { + self.last_epoch = Some(epoch); + Command::none() + } + }, // Change what the main panel displays + We have informations to pass to it Message::MainPanelMessage(message) => self.mainpanel.update(message).map(Message::from), diff --git a/amadeus/src/message.rs b/amadeus/src/message.rs index 6147027ce917a28163202a405a88a884717123f2..53bf7889c271dd01b1b8289357fd7a18b06e8ce0 100644 --- a/amadeus/src/message.rs +++ b/amadeus/src/message.rs @@ -4,6 +4,7 @@ use crate::components::{ modal::ShowModal, sidebar, topbar, }; +use iced::Command; use lektor_payloads::{KId, Kara, PlayState, Priority}; use lektor_utils::log; use std::sync::Arc; @@ -71,9 +72,8 @@ pub enum Message { /// The config was loaded, can start rendering the application. ConfigLoaded, - /// Need to reconnect to the lektord instance. If the flag is true then we need to flush the - /// caches and reload things... - ReconnectToLektord(bool), + /// Need to reconnect to the lektord instance. We need to flush the caches and reload things... + ReconnectToLektord, /// The current kara changed and we got the kara struct. ChangedKara(Arc<Kara>), @@ -169,6 +169,16 @@ impl Message { res.map_err(|err| log::error!("{err}")) .unwrap_or_else(|()| Message::from_iter(err())) } + + /// Wrapper to avoid calling [Command::perform] every time with an empty future... + pub fn perform(self) -> Command<Message> { + Command::perform(async {}, |_| self) + } + + pub fn into_perform(this: impl Into<Self>) -> Command<Message> { + let this = this.into(); + Command::perform(async {}, |_| this) + } } impl std::fmt::Display for MainPanelDisplay { @@ -319,9 +329,8 @@ impl From<bottombar::Message> for Message { impl From<config::Request> for Message { fn from(value: config::Request) -> Self { match value { - config::Request::None => Message::None, - config::Request::Reconnect => Message::ReconnectToLektord(false), - config::Request::FlushCacheAndReconnect => Message::ReconnectToLektord(true), + config::Request::Reconnect | config::Request::None => Message::None, + config::Request::FlushCacheAndReconnect => Message::ReconnectToLektord, config::Request::Loaded => Message::ConfigLoaded, config::Request::ToggleMprisServer(flag) => Message::ToggleMprisServer(flag), } diff --git a/amadeus/src/store.rs b/amadeus/src/store.rs index 76b78dbe2247fd08b4d3a7eade67d9af5e7fa1a5..9cb0bc763ea3e7cb84150a4307d178a92632a3ca 100644 --- a/amadeus/src/store.rs +++ b/amadeus/src/store.rs @@ -39,6 +39,12 @@ impl KaraStore { let _ = self.sender.send_replace(config); } + /// Clear the cache. + pub async fn clear(&self) -> Result<()> { + self.store.write().await.clear(); + Ok(()) + } + /// Get a kara from lektord by its [KId]. If the config was updated, we apply the update. /// We prefer to get the kara from the internal hashmap, if the kara is not present, we request /// it from lektord. @@ -51,16 +57,20 @@ impl KaraStore { return Ok(kara.clone()); } let kara = Arc::new(get_kara_by_kid(self.config.read().await.clone(), id).await?); - self.store - .write() - .await - .insert(kara.id.clone(), kara.clone()); + let mut store = self.store.write().await; + store.insert(kara.id.clone(), kara.clone()); Ok(kara) } /// Same as the [KaraStore::get], but we consume a cloned pointer to have to write less /// boiler-plate code. - pub async fn into_get(this: Arc<Self>, id: KId) -> Result<Arc<Kara>> { - this.as_ref().get(id).await + pub async fn into_get(self: Arc<Self>, id: KId) -> Result<Arc<Kara>> { + self.as_ref().get(id).await + } + + /// Same as [KaraStore::clear], but we consume a cloned pointer to have to write less + /// boiler-plate code. + pub async fn into_clear(self: Arc<Self>) -> Result<()> { + self.clear().await } }