diff --git a/data/src/client.rs b/data/src/client.rs index b175f6a1..846368c9 100644 --- a/data/src/client.rs +++ b/data/src/client.rs @@ -1485,6 +1485,20 @@ impl Map { } } + pub fn exit(&mut self) -> HashSet { + self.0 + .iter_mut() + .filter_map(|(server, state)| { + if let State::Ready(client) = state { + client.quit(None); + Some(server.clone()) + } else { + None + } + }) + .collect() + } + pub fn resolve_user_attributes<'a>( &'a self, server: &Server, @@ -1560,10 +1574,6 @@ impl Map { } }) } - - pub fn take(&mut self) -> Self { - Self(std::mem::take(&mut self.0)) - } } #[derive(Debug, Clone)] diff --git a/data/src/history.rs b/data/src/history.rs index d82828f2..be1a0776 100644 --- a/data/src/history.rs +++ b/data/src/history.rs @@ -9,11 +9,12 @@ use irc::proto; use tokio::fs; use tokio::time::Instant; -pub use self::manager::{Manager, Resource}; -pub use self::metadata::{Metadata, ReadMarker}; use crate::user::Nick; use crate::{compression, environment, message, server, Message}; +pub use self::manager::{Manager, Resource}; +pub use self::metadata::{Metadata, ReadMarker}; + pub mod manager; pub mod metadata; diff --git a/data/src/history/manager.rs b/data/src/history/manager.rs index 7c358d0b..209842b3 100644 --- a/data/src/history/manager.rs +++ b/data/src/history/manager.rs @@ -42,10 +42,18 @@ pub enum Message { Result, history::Error>, ), Flushed(server::Server, history::Kind, Result<(), history::Error>), + Exited( + Vec<( + Server, + history::Kind, + Result, history::Error>, + )>, + ), } pub enum Event { Closed(server::Server, history::Kind, Option), + Exited(Vec<(Server, history::Kind, Option)>), } #[derive(Debug, Default)] @@ -109,12 +117,12 @@ impl Manager { Message::Flushed(server, kind, Err(error)) => { log::warn!("failed to flush history for {kind} on {server}: {error}") } - Message::UpdatePartial(server, kind, Ok(loaded)) => { - log::debug!("updating metadata for {kind} on {server}"); - self.data.update_partial(server, kind, loaded); + Message::UpdatePartial(server, kind, Ok(metadata)) => { + log::debug!("loaded metadata for {kind} on {server}"); + self.data.update_partial(server, kind, metadata); } Message::UpdatePartial(server, kind, Err(error)) => { - log::warn!("failed to load history metadata for {kind} on {server}: {error}"); + log::warn!("failed to load metadata for {kind} on {server}: {error}"); } Message::UpdateReadMarker(server, kind, read_marker, Ok(_)) => { log::debug!("updated read marker for {kind} on {server} to {read_marker}"); @@ -124,6 +132,24 @@ impl Manager { "failed to update read marker for {kind} on {server} to {read_marker}: {error}" ); } + Message::Exited(results) => { + let mut output = vec![]; + + for (server, kind, result) in results { + match result { + Ok(marker) => { + log::debug!("closed history for {kind} on {server}",); + output.push((server, kind, marker)); + } + Err(error) => { + log::warn!("failed to close history for {kind} on {server}: {error}"); + output.push((server, kind, None)); + } + } + } + + return Some(Event::Exited(output)); + } } None @@ -137,26 +163,17 @@ impl Manager { &mut self, server: Server, kind: history::Kind, - ) -> Option)>> { + ) -> Option> { let history = self.data.map.get_mut(&server)?.remove(&kind)?; - Some(async move { - match history.close().await { - Ok(marker) => { - log::debug!("closed history for {kind} on {server}",); - (server, kind, marker) - } - Err(error) => { - log::warn!("failed to close history for {kind} on {server}: {error}"); - (server, kind, None) - } - } - }) + Some( + history + .close() + .map(|result| Message::Closed(server, kind, result)), + ) } - pub fn close_all( - &mut self, - ) -> impl Future)>> { + pub fn exit(&mut self) -> impl Future { let map = std::mem::take(&mut self.data).map; async move { @@ -167,24 +184,7 @@ impl Manager { }) }); - let results = future::join_all(tasks).await; - - let mut output = vec![]; - - for (server, kind, result) in results { - match result { - Ok(marker) => { - log::debug!("closed history for {kind} on {server}",); - output.push((server, kind, marker)); - } - Err(error) => { - log::warn!("failed to close history for {kind} on {server}: {error}"); - output.push((server, kind, None)); - } - } - } - - output + Message::Exited(future::join_all(tasks).await) } } @@ -228,7 +228,7 @@ impl Manager { pub fn update_read_marker( &mut self, server: Server, - kind: history::Kind, + kind: impl Into, read_marker: history::ReadMarker, ) -> Option> { self.data.update_read_marker(server, kind, read_marker) @@ -717,11 +717,13 @@ impl Data { fn update_read_marker( &mut self, server: server::Server, - kind: history::Kind, + kind: impl Into, read_marker: history::ReadMarker, ) -> Option> { use std::collections::hash_map; + let kind = kind.into(); + match self .map .entry(server.clone()) diff --git a/data/src/history/metadata.rs b/data/src/history/metadata.rs index e4b3e29f..89273682 100644 --- a/data/src/history/metadata.rs +++ b/data/src/history/metadata.rs @@ -1,8 +1,8 @@ -use chrono::{format::SecondsFormat, DateTime, Utc}; use std::fmt; use std::path::PathBuf; use std::str::FromStr; +use chrono::{format::SecondsFormat, DateTime, Utc}; use serde::{Deserialize, Serialize}; use tokio::fs; diff --git a/src/main.rs b/src/main.rs index 5441ec45..b9bf5dd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,15 +16,16 @@ mod url; mod widget; mod window; +use std::collections::HashSet; use std::env; use std::time::{Duration, Instant}; use chrono::Utc; use data::config::{self, Config}; -use data::history; use data::history::manager::Broadcast; use data::version::Version; use data::{environment, server, version, Url, User}; +use data::{history, Server}; use iced::widget::{column, container}; use iced::{padding, Length, Subscription, Task}; use screen::{dashboard, help, migration, welcome}; @@ -201,6 +202,7 @@ pub enum Screen { Help(screen::Help), Welcome(screen::Welcome), Migration(screen::Migration), + Exit { pending_exit: HashSet }, } #[derive(Debug)] @@ -339,6 +341,16 @@ impl Halloy { self.clients.quit(&server, None); Task::none() } + Some(dashboard::Event::Exit) => { + let pending_exit = self.clients.exit(); + + if pending_exit.is_empty() { + iced::exit() + } else { + self.screen = Screen::Exit { pending_exit }; + Task::none() + } + } None => Task::none(), }; @@ -695,13 +707,11 @@ impl Halloy { } } data::client::Event::UpdateReadMarker(target, read_marker) => { - let kind = history::Kind::from(target); - commands.push( dashboard .update_read_marker( server.clone(), - kind, + target, read_marker, ) .map(Message::Dashboard), @@ -727,34 +737,42 @@ impl Halloy { Task::batch(commands) } - stream::Update::Quit(server, reason) => { - let Screen::Dashboard(dashboard) = &mut self.screen else { - return Task::none(); - }; - - self.servers.remove(&server); - - if let Some(client) = self.clients.remove(&server) { - let user = client.nickname().to_owned().into(); - - let channels = client.channels().to_vec(); + stream::Update::Quit(server, reason) => match &mut self.screen { + Screen::Dashboard(dashboard) => { + self.servers.remove(&server); + + if let Some(client) = self.clients.remove(&server) { + let user = client.nickname().to_owned().into(); + + let channels = client.channels().to_vec(); + + dashboard + .broadcast( + &server, + &self.config, + Utc::now(), + Broadcast::Quit { + user, + comment: reason, + user_channels: channels, + }, + ) + .map(Message::Dashboard) + } else { + Task::none() + } + } + Screen::Exit { pending_exit } => { + pending_exit.remove(&server); - dashboard - .broadcast( - &server, - &self.config, - Utc::now(), - Broadcast::Quit { - user, - comment: reason, - user_channels: channels, - }, - ) - .map(Message::Dashboard) - } else { - Task::none() + if pending_exit.is_empty() { + iced::exit() + } else { + Task::none() + } } - } + _ => Task::none(), + }, }, Message::Event(window, event) => { // Events only enabled for main window @@ -838,7 +856,7 @@ impl Halloy { } window::Event::CloseRequested => { if let Screen::Dashboard(dashboard) = &mut self.screen { - return dashboard.exit(self.clients.take()).then(|_| iced::exit()); + return dashboard.exit().map(Message::Dashboard); } else { return iced::exit(); } @@ -882,6 +900,7 @@ impl Halloy { Screen::Help(help) => help.view().map(Message::Help), Screen::Welcome(welcome) => welcome.view().map(Message::Welcome), Screen::Migration(migration) => migration.view().map(Message::Migration), + Screen::Exit { .. } => column![].into(), }; let content = container(screen) diff --git a/src/screen/dashboard.rs b/src/screen/dashboard.rs index b5851c90..c80c4fb2 100644 --- a/src/screen/dashboard.rs +++ b/src/screen/dashboard.rs @@ -52,7 +52,6 @@ pub enum Message { SelectedText(Vec<(f32, String)>), History(history::manager::Message), DashboardSaved(Result<(), data::dashboard::Error>), - CloseHistory(Server, history::Kind, Option), Task(command_bar::Message), Shortcut(shortcut::Command), FileTransfer(file_transfer::task::Update), @@ -67,6 +66,7 @@ pub enum Event { ConfigReloaded(Result), ReloadThemes, QuitServer(Server), + Exit, } impl Dashboard { @@ -519,6 +519,16 @@ impl Dashboard { clients.send_markread(&server, target, read_marker); } } + history::manager::Event::Exited(results) => { + for (server, kind, read_marker) in results { + if let Some((target, read_marker)) = kind.target().zip(read_marker) + { + clients.send_markread(&server, target, read_marker); + } + } + + return (Task::none(), Some(Event::Exit)); + } } } } @@ -528,11 +538,6 @@ impl Dashboard { Message::DashboardSaved(Err(error)) => { log::warn!("error saving dashboard: {error}"); } - Message::CloseHistory(server, kind, marker) => { - if let Some((target, marker)) = kind.target().zip(marker) { - clients.send_markread(&server, target, marker); - } - } Message::Task(message) => { let Some(command_bar) = &mut self.command_bar else { return (Task::none(), None); @@ -1183,11 +1188,7 @@ impl Dashboard { tasks.push( self.history .close(server, history::Kind::Channel(channel)) - .map(|task| { - Task::perform(task, |(server, kind, marker)| { - Message::CloseHistory(server, kind, marker) - }) - }) + .map(|task| Task::perform(task, Message::History)) .unwrap_or_else(Task::none), ); @@ -1197,11 +1198,7 @@ impl Dashboard { tasks.push( self.history .close(server, history::Kind::Query(nick)) - .map(|task| { - Task::perform(task, |(server, kind, marker)| { - Message::CloseHistory(server, kind, marker) - }) - }) + .map(|task| Task::perform(task, Message::History)) .unwrap_or_else(Task::none), ); @@ -1237,7 +1234,7 @@ impl Dashboard { pub fn update_read_marker( &mut self, server: Server, - kind: history::Kind, + kind: impl Into + 'static, read_marker: ReadMarker, ) -> Task { if let Some(task) = self.history.update_read_marker(server, kind, read_marker) { @@ -1704,32 +1701,28 @@ impl Dashboard { } } - pub fn exit(&mut self, mut clients: client::Map) -> Task<()> { - let history = self.history.close_all(); - let last_changed = self.last_changed; + pub fn exit(&mut self) -> Task { + let history = self.history.exit(); + let last_changed = self.last_changed.take(); let dashboard = data::Dashboard::from(&*self); - Task::future(async move { - for (server, kind, marker) in history.await { - if let Some((target, marker)) = kind.target().zip(marker) { - clients.send_markread(&server, target, marker); - } - } - - // Give markread messages a chance to send - tokio::time::sleep(Duration::from_millis(500)).await; - - if last_changed.is_some() { - match dashboard.save().await { - Ok(_) => { - log::info!("dashboard saved"); - } - Err(error) => { - log::warn!("error saving dashboard: {error}"); + Task::perform( + async move { + if last_changed.is_some() { + match dashboard.save().await { + Ok(_) => { + log::info!("dashboard saved"); + } + Err(error) => { + log::warn!("error saving dashboard: {error}"); + } } } - } - }) + + history.await + }, + Message::History, + ) } fn open_popout_window(&mut self, main_window: &Window, pane: Pane) -> Task {