From f997d19ab1857ff7cb3f0c39de6fcf92bc36b87a Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Mon, 11 Aug 2025 23:49:34 +0300 Subject: [PATCH] feat: improve event loop --- Cargo.toml | 1 + src/config/mod.rs | 17 +++++++++++-- src/main.rs | 65 ++++++++++++++++++++++++++++++----------------- src/ui/table.rs | 8 +++--- 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 843408a..81e847d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" [dependencies] filecaster = { version = "0.2", features = [ + "derive", "merge", ], path = "../filecaster/filecaster/" } color-eyre = "0.6" diff --git a/src/config/mod.rs b/src/config/mod.rs index 7d63787..d025f35 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -17,6 +17,8 @@ use std::{ }; use tracing::{debug, info, warn}; +const DEFAULT_CONFIG_STR: &str = include_str!("../../config/default.toml"); + #[derive(Debug, Clone, FromFile)] pub struct Config { pub keybinds: KeybindsConfig, @@ -25,12 +27,23 @@ pub struct Config { } impl Config { + /// Load configuration with fallback to embedded defaults. + /// + /// Merge order: + /// 1. Embedded defaults + /// 2. System-wide config (`/etc/xdg/traxor/config.toml`) + /// 3. User config (`~/.config/traxor/config.toml`) + /// /// # Errors /// - /// TODO: add error types + /// Returns an error if: + /// - The embedded default config cannot be parsed (should never happen unless corrupted at build time). + /// - The system-wide or user config file cannot be read due to I/O errors. + /// - The TOML in any config file is invalid and cannot be parsed. #[tracing::instrument(name = "Loading configuration")] pub fn load() -> Result { - let mut cfg_file = ConfigFile::default(); + let mut cfg_file = toml::from_str::(DEFAULT_CONFIG_STR) + .context("Failed to parse embedded default config")?; let candidates = [ ("system-wide", PathBuf::from("/etc/xdg/traxor/config.toml")), diff --git a/src/main.rs b/src/main.rs index 72bf5ae..a8eb997 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,11 @@ use color_eyre::Result; use ratatui::{Terminal, backend::CrosstermBackend}; -use std::io; -use tracing::{debug, trace}; +use std::{io, sync::Arc}; +use tokio::{ + sync::Mutex, + time::{self, Duration}, +}; +use tracing::{trace, warn}; use traxor::{ app::App, config::Config, @@ -15,38 +19,54 @@ use traxor::{ async fn main() -> Result<()> { color_eyre::install()?; - debug!("Loading configuration..."); let config = Config::load()?; - debug!("Configuration loaded."); - - // Setup the logger. setup_logger(&config)?; - // Create an application. - let mut app = App::new(config)?; + // Wrap App in Arc> so we can share it between UI and updater + let app = Arc::new(Mutex::new(App::new(config)?)); - // Initialize the terminal user interface. + // Clone for updater task + let app_clone = app.clone(); + + tokio::spawn(async move { + let mut interval = time::interval(Duration::from_secs(2)); + loop { + interval.tick().await; + + let mut app = app_clone.lock().await; + if let Err(e) = app.torrents.update().await { + warn!("Failed to update torrents: {e}"); + } + } + }); + + // TUI setup let backend = CrosstermBackend::new(io::stderr()); let terminal = Terminal::new(backend)?; let events = EventHandler::new(250); // Update time in ms let mut tui = Tui::new(terminal, events); tui.init()?; - // Start the main loop. - while app.running { - // Render the user interface. - tui.draw(&mut app)?; - // Handle events. - match tui.events.next()? { - Event::Tick => { - trace!(target: "app", "Event::Tick"); - app.tick().await?; + // Main loop + loop { + { + let app_guard = app.lock().await; + if !app_guard.running { + break; } + } + + { + let mut app_guard = app.lock().await; + tui.draw(&mut app_guard)?; + } + + match tui.events.next()? { + Event::Tick => {} Event::Key(key_event) => { - trace!(target: "app", "Event::Key: {:?}", key_event); - if let Some(action) = get_action(key_event, &mut app).await? { - trace!(target: "app", "Action: {:?}", action); - update(&mut app, action).await?; + let mut app_guard = app.lock().await; + if let Some(action) = get_action(key_event, &mut app_guard).await? { + update(&mut app_guard, action).await?; } } Event::Mouse(mouse_event) => { @@ -58,7 +78,6 @@ async fn main() -> Result<()> { } } - // Exit the user interface. tui.exit()?; Ok(()) } diff --git a/src/ui/table.rs b/src/ui/table.rs index 5381400..9fa1826 100644 --- a/src/ui/table.rs +++ b/src/ui/table.rs @@ -67,10 +67,10 @@ fn make_row<'a>( highlight: Style, ) -> Row<'a> { let cells = fields.iter().map(|&field| { - if let Some(id) = torrent.id { - if selected.contains(&id) { - return field.value(torrent).set_style(highlight); - } + if let Some(id) = torrent.id + && selected.contains(&id) + { + return field.value(torrent).set_style(highlight); } field.value(torrent).into() });