From 6d73cd0bf027e2fc0eeda219a6287a355ffe62bf Mon Sep 17 00:00:00 2001 From: TicClick Date: Mon, 30 Sep 2024 22:20:54 +0200 Subject: [PATCH 1/6] implement chat logging facility --- src/core/logging.rs | 206 ++++++++++++++++++++++++++++++++++++++++++++ src/core/mod.rs | 1 + 2 files changed, 207 insertions(+) create mode 100644 src/core/logging.rs diff --git a/src/core/logging.rs b/src/core/logging.rs new file mode 100644 index 0000000..e2275be --- /dev/null +++ b/src/core/logging.rs @@ -0,0 +1,206 @@ +use std::collections::{hash_map::Entry, HashMap}; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; + +use steel_core::chat::Message; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + +use crate::actor::ActorHandle; + +pub enum LoggingRequest { + LogMessage { chat_name: String, message: Message }, + CloseLog { chat_name: String }, + ChangeLogFormat { log_line_format: String }, + ChangeLoggingDirectory { logging_directory: String }, + ShutdownLogger, +} + +pub struct ChatLoggerHandle { + channel: UnboundedSender, +} + +impl ActorHandle for ChatLoggerHandle {} + +impl ChatLoggerHandle { + pub fn new(log_directory: &str, log_line_format: &str) -> Self { + let (tx, rx) = unbounded_channel(); + let mut actor = ChatLoggerBackend::new(log_directory, log_line_format, rx); + std::thread::spawn(move || { + actor.run(); + }); + Self { channel: tx } + } + + pub fn log(&self, chat_name: String, message: Message) { + let _ = self + .channel + .send(LoggingRequest::LogMessage { chat_name, message }); + } + + pub fn close_log(&self, chat_name: String) { + let _ = self.channel.send(LoggingRequest::CloseLog { chat_name }); + } + + pub fn change_log_format(&self, log_line_format: String) { + let _ = self + .channel + .send(LoggingRequest::ChangeLogFormat { log_line_format }); + } + + pub fn change_logging_directory(&self, logging_directory: String) { + let _ = self + .channel + .send(LoggingRequest::ChangeLoggingDirectory { logging_directory }); + } + + pub fn shutdown(&self) { + let _ = self.channel.send(LoggingRequest::ShutdownLogger); + } +} + +struct ChatLoggerBackend { + log_directory: PathBuf, + log_line_format: String, + channel: UnboundedReceiver, + files: HashMap, +} + +impl ChatLoggerBackend { + fn new( + log_directory: &str, + log_line_format: &str, + channel: UnboundedReceiver, + ) -> Self { + Self { + log_directory: Path::new(&log_directory).to_path_buf(), + log_line_format: log_line_format.to_owned(), + channel, + files: HashMap::new(), + } + } + + fn run(&mut self) { + while let Some(evt) = self.channel.blocking_recv() { + match evt { + LoggingRequest::LogMessage { chat_name, message } => { + if self.log(chat_name, message).is_err() { + return; + } + } + LoggingRequest::ChangeLogFormat { log_line_format } => { + self.log_line_format = log_line_format; + } + LoggingRequest::ChangeLoggingDirectory { logging_directory } => { + log::info!( + "Chat logging directory has been changed: {:?} -> {}", + self.log_directory, + logging_directory + ); + self.log_directory = Path::new(&logging_directory).to_path_buf(); + self.files.clear(); + } + LoggingRequest::CloseLog { chat_name } => self.close(chat_name), + LoggingRequest::ShutdownLogger => return, + } + } + } + + fn chat_path(&self, chat_name: &str) -> PathBuf { + self.log_directory.join(chat_name.to_lowercase()).with_extension("log") + } + + fn format_message(log_line_format: &str, message: &Message) -> String { + let mut result = String::new(); + let mut placeholder = String::new(); + let mut in_placeholder = false; + + for c in log_line_format.chars() { + match c { + '{' => { + in_placeholder = true; + placeholder.clear(); + } + '}' => { + if in_placeholder { + result.push_str(&Self::resolve_placeholder(&placeholder, message)); + in_placeholder = false; + } else { + result.push(c); + } + } + _ => { + if in_placeholder { + placeholder.push(c); + } else { + result.push(c); + } + } + } + } + + result + } + + fn resolve_placeholder(placeholder: &str, message: &Message) -> String { + if let Some(format) = placeholder.strip_prefix("date:") { + message.time.format(format).to_string() + } else { + match placeholder { + "username" => message.username.clone(), + "text" => message.text.clone(), + _ => String::from("{unknown}"), + } + } + } + + fn log(&mut self, chat_name: String, message: Message) -> std::io::Result<()> { + if self.files.is_empty() { + if let Err(e) = std::fs::create_dir_all(&self.log_directory) { + log::error!( + "Failed to create the directory for storing chat logs: {}", + e + ); + return Err(e); + } + } + + let target_path = self.chat_path(&chat_name); + let mut f = match self.files.entry(target_path.clone()) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => { + match std::fs::OpenOptions::new() + .read(true) + .create(true) + .append(true) + .open(target_path) + { + Ok(handle) => e.insert(handle), + Err(e) => { + log::error!( + "Failed to open or create the chat log for {}: {}", + chat_name, + e + ); + return Err(e); + } + } + } + }; + + let formatted_message = Self::format_message(&self.log_line_format, &message); + if let Err(e) = writeln!(&mut f, "{}", formatted_message) { + log::error!("Failed to append a chat log line for {}: {}", chat_name, e); + return Err(e); + } + + Ok(()) + } + + fn close(&mut self, chat_name: String) { + let target_path = self.chat_path(&chat_name); + if let Entry::Occupied(e) = self.files.entry(target_path) { + e.remove_entry(); + } + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index d0c938a..04fd456 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ pub mod irc; +pub mod logging; pub mod os; pub mod sound; pub mod updater; From 96f0908df2a485b70ca8a3cee6cddf5c55cbc64b Mon Sep 17 00:00:00 2001 From: TicClick Date: Thu, 3 Oct 2024 22:05:56 +0200 Subject: [PATCH 2/6] log chat messages from the app's backend --- crates/steel_core/src/settings/journal.rs | 21 +++++++++++ src/app/mod.rs | 46 +++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/crates/steel_core/src/settings/journal.rs b/crates/steel_core/src/settings/journal.rs index de82c3c..d86b1d3 100644 --- a/crates/steel_core/src/settings/journal.rs +++ b/crates/steel_core/src/settings/journal.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; #[serde(default)] pub struct Journal { pub app_events: AppEvents, + pub chat_events: ChatEvents, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -33,3 +34,23 @@ enum LevelFilterDef { Debug, Trace, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct ChatEvents { + pub enabled: bool, + pub directory: String, + pub format: String, + pub with_system_events: bool, +} + +impl Default for ChatEvents { + fn default() -> Self { + Self { + enabled: true, + directory: "./chat-logs".to_owned(), + format: "[{date:%Y-%m-%d %H:%M:%S}] <{username}> {text}".to_owned(), + with_system_events: true, + } + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 5c67f60..e32f427 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -9,6 +9,7 @@ use steel_core::chat::irc::IRCError; use steel_core::chat::{ChatLike, ChatState, ConnectionStatus, Message}; use crate::core::irc::IRCActorHandle; +use crate::core::logging::ChatLoggerHandle; use crate::core::updater::Updater; use crate::core::{settings, updater}; use steel_core::ipc::{server::AppMessageIn, ui::UIMessageIn}; @@ -27,6 +28,7 @@ pub struct Application { events: UnboundedReceiver, irc: IRCActorHandle, + chat_logger: Option, updater: Option, ui_queue: UnboundedSender, pub app_queue: UnboundedSender, @@ -40,6 +42,7 @@ impl Application { events, updater: None, irc: IRCActorHandle::new(app_queue.clone()), + chat_logger: None, ui_queue, app_queue, } @@ -177,6 +180,8 @@ impl Application { self.load_settings(true); log::set_max_level(self.state.settings.journal.app_events.level); + self.enable_chat_logger(&self.state.settings.journal.clone()); + self.start_updater(); if self.state.settings.chat.autoconnect { self.connect(); @@ -194,6 +199,41 @@ impl Application { self.ui_handle_settings_requested(); } + fn enable_chat_logger(&mut self, logging_settings: &settings::Journal) { + self.chat_logger = Some(ChatLoggerHandle::new( + &logging_settings.chat_events.directory, + &logging_settings.chat_events.format, + )); + } + + fn handle_logging_settings_change(&mut self, new_settings: &settings::Journal) { + let old_settings = self.state.settings.journal.clone(); + if old_settings.app_events.level != new_settings.app_events.level { + log::set_max_level(new_settings.app_events.level); + } + + if old_settings.chat_events.enabled != new_settings.chat_events.enabled { + match new_settings.chat_events.enabled { + true => self.enable_chat_logger(new_settings), + false => { + if let Some(cl) = self.chat_logger.as_ref() { + cl.shutdown() + } + } + } + } + + if let Some(chat_logger) = &self.chat_logger { + if old_settings.chat_events.directory != new_settings.chat_events.directory { + chat_logger.change_logging_directory(new_settings.chat_events.directory.clone()); + } + + if old_settings.chat_events.format != new_settings.chat_events.format { + chat_logger.change_log_format(new_settings.chat_events.format.clone()); + } + } + } + pub fn ui_handle_settings_requested(&self) { self.ui_queue .send(UIMessageIn::SettingsChanged(self.state.settings.clone())) @@ -201,6 +241,8 @@ impl Application { } pub fn ui_handle_settings_updated(&mut self, settings: settings::Settings) { + self.handle_logging_settings_change(&settings.journal); + self.state.settings = settings; self.state.settings.to_file(DEFAULT_SETTINGS_PATH); } @@ -286,6 +328,10 @@ impl Application { message: Message, switch_if_missing: bool, ) { + if let Some(chat_logger) = &self.chat_logger { + chat_logger.log(target.clone(), message.clone()); + } + self.maybe_remember_chat(&target, switch_if_missing); self.ui_queue .send(UIMessageIn::NewMessageReceived { target, message }) From 96b8e7751e34217ee6e4a8c4bcaa68416673fa53 Mon Sep 17 00:00:00 2001 From: TicClick Date: Sun, 6 Oct 2024 19:56:24 +0200 Subject: [PATCH 3/6] ui: unify chat update logic --- src/gui/state/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/gui/state/mod.rs b/src/gui/state/mod.rs index 3e96fa2..552ec74 100644 --- a/src/gui/state/mod.rs +++ b/src/gui/state/mod.rs @@ -286,8 +286,10 @@ impl UIState { } pub fn mark_all_as_disconnected(&mut self) { - for chat in self.chats.iter_mut() { - chat.set_state( + let open_chats: Vec = self.chats.iter().map(|ch| ch.name.clone()).collect(); + for chat_name in open_chats { + self.set_chat_state( + &chat_name, ChatState::Left, Some("You have left the chat (disconnected)"), ); @@ -295,13 +297,14 @@ impl UIState { } pub fn mark_all_as_connected(&mut self) { - for chat in self.chats.iter_mut() { - let (new_state, reason) = match chat.name.is_channel() { + let open_chats: Vec = self.chats.iter().map(|ch| ch.name.clone()).collect(); + for chat_name in open_chats { + let (new_state, reason) = match chat_name.is_channel() { // Joins are handled by the app server true => (ChatState::JoinInProgress, None), false => (ChatState::Joined, Some("You are online")), }; - chat.set_state(new_state, reason); + self.set_chat_state(&chat_name, new_state, reason); } } } From 89cc798b1af5c38fe27e34aaa7b4f07486757aab Mon Sep 17 00:00:00 2001 From: TicClick Date: Sun, 6 Oct 2024 21:08:40 +0200 Subject: [PATCH 4/6] add logging settings in UI --- src/core/logging.rs | 101 +++++++++++++++++++----------------- src/core/os.rs | 67 ++++++++++++++++-------- src/gui/settings/journal.rs | 84 ++++++++++++++++++++++++++++++ src/gui/settings/mod.rs | 6 +++ 4 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 src/gui/settings/journal.rs diff --git a/src/core/logging.rs b/src/core/logging.rs index e2275be..9af3290 100644 --- a/src/core/logging.rs +++ b/src/core/logging.rs @@ -1,6 +1,7 @@ use std::collections::{hash_map::Entry, HashMap}; +use std::fmt::Write as FmtWrite; use std::fs::File; -use std::io::Write; +use std::io::Write as IOWrite; use std::path::{Path, PathBuf}; use steel_core::chat::Message; @@ -107,51 +108,9 @@ impl ChatLoggerBackend { } fn chat_path(&self, chat_name: &str) -> PathBuf { - self.log_directory.join(chat_name.to_lowercase()).with_extension("log") - } - - fn format_message(log_line_format: &str, message: &Message) -> String { - let mut result = String::new(); - let mut placeholder = String::new(); - let mut in_placeholder = false; - - for c in log_line_format.chars() { - match c { - '{' => { - in_placeholder = true; - placeholder.clear(); - } - '}' => { - if in_placeholder { - result.push_str(&Self::resolve_placeholder(&placeholder, message)); - in_placeholder = false; - } else { - result.push(c); - } - } - _ => { - if in_placeholder { - placeholder.push(c); - } else { - result.push(c); - } - } - } - } - - result - } - - fn resolve_placeholder(placeholder: &str, message: &Message) -> String { - if let Some(format) = placeholder.strip_prefix("date:") { - message.time.format(format).to_string() - } else { - match placeholder { - "username" => message.username.clone(), - "text" => message.text.clone(), - _ => String::from("{unknown}"), - } - } + self.log_directory + .join(chat_name.to_lowercase()) + .with_extension("log") } fn log(&mut self, chat_name: String, message: Message) -> std::io::Result<()> { @@ -188,7 +147,7 @@ impl ChatLoggerBackend { } }; - let formatted_message = Self::format_message(&self.log_line_format, &message); + let formatted_message = format_message_for_logging(&self.log_line_format, &message); if let Err(e) = writeln!(&mut f, "{}", formatted_message) { log::error!("Failed to append a chat log line for {}: {}", chat_name, e); return Err(e); @@ -204,3 +163,51 @@ impl ChatLoggerBackend { } } } + +pub fn format_message_for_logging(log_line_format: &str, message: &Message) -> String { + let mut result = String::new(); + let mut placeholder = String::new(); + let mut in_placeholder = false; + + for c in log_line_format.chars() { + match c { + '{' => { + in_placeholder = true; + placeholder.clear(); + } + '}' => { + if in_placeholder { + result.push_str(&resolve_placeholder(&placeholder, message)); + in_placeholder = false; + } else { + result.push(c); + } + } + _ => { + if in_placeholder { + placeholder.push(c); + } else { + result.push(c); + } + } + } + } + + result +} + +fn resolve_placeholder(placeholder: &str, message: &Message) -> String { + if let Some(date_format) = placeholder.strip_prefix("date:") { + let mut buf = String::new(); + match write!(&mut buf, "{}", message.time.format(date_format)) { + Ok(_) => buf, + Err(_) => format!("{{date:{}}}", date_format), + } + } else { + match placeholder { + "username" => message.username.clone(), + "text" => message.text.clone(), + _ => String::from("{unknown}"), + } + } +} diff --git a/src/core/os.rs b/src/core/os.rs index d5363ee..36dcc9f 100644 --- a/src/core/os.rs +++ b/src/core/os.rs @@ -1,42 +1,65 @@ +use std::path::Path; + #[derive(Debug)] -enum OpenTarget<'opener> { - AppFile(&'opener str), - AppDirectory, +enum RuntimeDirectoryPath<'opener> { + File(&'opener str), + RootDirectory, + Subdirectory(&'opener str), +} + +fn open_fs_path_in_explorer(path: &Path, target: &str) { + if let Some(path) = path.to_str() { + let path = path.to_owned(); + let (executable, args) = if cfg!(target_os = "windows") { + // let file_arg = format!("/select,{}", log_path); + ("explorer.exe", vec![path]) + } else if cfg!(target_os = "macos") { + ("open", vec![path]) + } else { + ("xdg-open", vec![path]) + }; + if let Err(e) = std::process::Command::new(executable).args(&args).spawn() { + log::error!( + "failed to open {target:?} from UI: {e:?} (command line: \"{executable} {args:?})" + ); + } + } } -fn open_app_path(target: OpenTarget) { +fn open_app_path(target: RuntimeDirectoryPath) { if let Ok(mut path) = std::env::current_exe() { match target { - OpenTarget::AppFile(file_name) => path.set_file_name(file_name), - OpenTarget::AppDirectory => { + RuntimeDirectoryPath::File(file_name) => path.set_file_name(file_name), + RuntimeDirectoryPath::RootDirectory => { path = path.parent().unwrap().to_path_buf(); } - } - if let Some(path) = path.to_str() { - let path = path.to_owned(); - let (executable, args) = if cfg!(target_os = "windows") { - // let file_arg = format!("/select,{}", log_path); - ("explorer.exe", vec![path]) - } else if cfg!(target_os = "macos") { - ("open", vec![path]) - } else { - ("xdg-open", vec![path]) - }; - if let Err(e) = std::process::Command::new(executable).args(&args).spawn() { - log::error!("failed to open {target:?} from UI: {e:?} (command line: \"{executable} {args:?})"); + RuntimeDirectoryPath::Subdirectory(subdir) => { + path = path.parent().unwrap().to_path_buf().join(subdir) } } + open_fs_path_in_explorer(&path, &format!("{:?}", target)); } } pub fn open_runtime_log() { - open_app_path(OpenTarget::AppFile("runtime.log")) + open_app_path(RuntimeDirectoryPath::File("runtime.log")) } + pub fn open_settings_file() { - open_app_path(OpenTarget::AppFile("settings.yaml")) + open_app_path(RuntimeDirectoryPath::File("settings.yaml")) } + pub fn open_own_directory() { - open_app_path(OpenTarget::AppDirectory) + open_app_path(RuntimeDirectoryPath::RootDirectory) +} + +pub fn open_external_directory(path: &str) { + let d = std::path::Path::new(path); + if d.is_relative() { + open_app_path(RuntimeDirectoryPath::Subdirectory(path)); + } else { + open_fs_path_in_explorer(d, path); + } } pub fn cleanup_after_update() { diff --git a/src/gui/settings/journal.rs b/src/gui/settings/journal.rs new file mode 100644 index 0000000..5db327b --- /dev/null +++ b/src/gui/settings/journal.rs @@ -0,0 +1,84 @@ +use eframe::egui::RichText; +use steel_core::chat; + +use super::SettingsWindow; +use crate::{ + core::{self, logging::format_message_for_logging}, + gui::state::UIState, +}; + +impl SettingsWindow { + pub(super) fn show_logging_tab(&mut self, ui: &mut eframe::egui::Ui, state: &mut UIState) { + ui.vertical(|ui| { + ui.heading("chat logging"); + ui.checkbox( + &mut state.settings.journal.chat_events.enabled, + "enable chat logging", + ); + + ui.horizontal(|ui| { + ui.label("directory with logs"); + ui.text_edit_singleline(&mut state.settings.journal.chat_events.directory) + .on_hover_text_at_pointer("location of all the log files"); + + if ui.button("open").clicked() + && std::path::Path::new(&state.settings.journal.chat_events.directory).exists() + { + core::os::open_external_directory( + &mut state.settings.journal.chat_events.directory, + ); + } + }); + + ui.label("format of a single line"); + ui.text_edit_multiline(&mut state.settings.journal.chat_events.format); + + ui.horizontal_wrapped(|ui| { + ui.label(RichText::new("preview: →").color(ui.visuals().warn_fg_color)); + let message = + chat::Message::new_text("WilliamGibson", "I think I left my cyberdeck on"); + let formatted_message = format_message_for_logging( + &state.settings.journal.chat_events.format, + &message, + ); + ui.label(formatted_message); + ui.label(RichText::new("←").color(ui.visuals().warn_fg_color)); + }); + + ui.collapsing("click to show help", |ui| { + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + + ui.label("allowed placeholders:\n"); + + ui.label("- "); + ui.label(RichText::new("{username}").color(ui.style().visuals.warn_fg_color)); + ui.label(" - author of the message\n"); + + ui.label("- "); + ui.label(RichText::new("{text}").color(ui.style().visuals.warn_fg_color)); + ui.label(" - message text\n"); + + ui.label("- "); + ui.label(RichText::new("{date:").color(ui.style().visuals.warn_fg_color)); + ui.label(RichText::new("dateformat").color(ui.style().visuals.error_fg_color)); + ui.label(RichText::new("}").color(ui.style().visuals.warn_fg_color)); + ui.label(" - message date/time, where "); + ui.label(RichText::new("dateformat").color(ui.style().visuals.error_fg_color)); + ui.label(" is replaced by a format string. example: "); + + ui.label(RichText::new("{date:").color(ui.style().visuals.warn_fg_color)); + ui.label( + RichText::new("%Y-%m-%d %H:%M:%S").color(ui.style().visuals.error_fg_color), + ); + ui.label(RichText::new("}").color(ui.style().visuals.warn_fg_color)); + ui.label(" ("); + ui.hyperlink_to("click for more examples", "https://strftime.net"); + ui.label(")"); + }); + }); + }); + + // TODO(logging): Add a setting for logging system events. + } +} diff --git a/src/gui/settings/mod.rs b/src/gui/settings/mod.rs index dbdb0d5..51f6f13 100644 --- a/src/gui/settings/mod.rs +++ b/src/gui/settings/mod.rs @@ -2,6 +2,7 @@ mod application; mod chat; +mod journal; mod notifications; mod ui; @@ -22,6 +23,7 @@ pub enum Tab { Notifications, #[cfg(feature = "glass")] Moderation, + Logging, } #[derive(Default)] @@ -60,6 +62,8 @@ impl SettingsWindow { #[cfg(feature = "glass")] ui.selectable_value(&mut self.active_tab, Tab::Moderation, "moderation"); + + ui.selectable_value(&mut self.active_tab, Tab::Logging, "logging"); }); ui.separator(); @@ -72,6 +76,8 @@ impl SettingsWindow { #[cfg(feature = "glass")] Tab::Moderation => state.glass.show_ui(ui, &state.settings.ui.theme), + + Tab::Logging => self.show_logging_tab(ui, state), } }); From 1e41f62fcda413ed8dfe220edda6e2adfff1623c Mon Sep 17 00:00:00 2001 From: TicClick Date: Sun, 6 Oct 2024 22:49:31 +0200 Subject: [PATCH 5/6] pad new logging sessions with \n --- src/core/logging.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/core/logging.rs b/src/core/logging.rs index 9af3290..94a6029 100644 --- a/src/core/logging.rs +++ b/src/core/logging.rs @@ -125,8 +125,8 @@ impl ChatLoggerBackend { } let target_path = self.chat_path(&chat_name); - let mut f = match self.files.entry(target_path.clone()) { - Entry::Occupied(e) => e.into_mut(), + let (is_new_file, mut f) = match self.files.entry(target_path.clone()) { + Entry::Occupied(e) => (false, e.into_mut()), Entry::Vacant(e) => { match std::fs::OpenOptions::new() .read(true) @@ -134,7 +134,7 @@ impl ChatLoggerBackend { .append(true) .open(target_path) { - Ok(handle) => e.insert(handle), + Ok(handle) => (true, e.insert(handle)), Err(e) => { log::error!( "Failed to open or create the chat log for {}: {}", @@ -147,6 +147,17 @@ impl ChatLoggerBackend { } }; + if is_new_file { + if let Err(e) = writeln!(&mut f, "\n") { + log::error!( + "Failed to start a new logging session for {}: {}", + chat_name, + e + ); + return Err(e); + } + } + let formatted_message = format_message_for_logging(&self.log_line_format, &message); if let Err(e) = writeln!(&mut f, "{}", formatted_message) { log::error!("Failed to append a chat log line for {}: {}", chat_name, e); From 880cfc2d364f471071d5e4266388f3a78764f861 Mon Sep 17 00:00:00 2001 From: TicClick Date: Sun, 6 Oct 2024 22:54:25 +0200 Subject: [PATCH 6/6] close log file when the corresponding tab closes --- src/app/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index e32f427..050fbf5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -416,6 +416,11 @@ impl Application { if name.is_channel() { self.leave_channel(name); } + + if let Some(chat_logger) = &self.chat_logger { + chat_logger.close_log(normalized); + } + self.ui_queue .send(UIMessageIn::ChatClosed(name.to_owned())) .unwrap();