feat: improve event loop

This commit is contained in:
Kristofers Solo 2025-08-11 23:49:34 +03:00
parent 46486a3409
commit fc3db9746e
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
4 changed files with 102 additions and 30 deletions

42
Cargo.lock generated
View File

@ -99,6 +99,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.1" version = "1.10.1"
@ -447,7 +453,8 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn", "thiserror 2.0.12",
"unsynn",
] ]
[[package]] [[package]]
@ -504,6 +511,15 @@ dependencies = [
"pin-utils", "pin-utils",
] ]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.16" version = "0.2.16"
@ -1002,6 +1018,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "mutants"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -1553,6 +1575,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shadow_counted"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65da48d447333cebe1aadbdd3662f3ba56e76e67f53bc46f3dd5f67c74629d6b"
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -2122,6 +2150,18 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsynn"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7940603a9e25cf11211cc43b81f4fcad2b8ab4df291ca855f32c40e1ac22d5bc"
dependencies = [
"fxhash",
"mutants",
"proc-macro2",
"shadow_counted",
]
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"

View File

@ -17,6 +17,8 @@ use std::{
}; };
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
const DEFAULT_CONFIG_STR: &str = include_str!("../../config/default.toml");
#[derive(Debug, Clone, FromFile)] #[derive(Debug, Clone, FromFile)]
pub struct Config { pub struct Config {
pub keybinds: KeybindsConfig, pub keybinds: KeybindsConfig,
@ -25,12 +27,23 @@ pub struct Config {
} }
impl 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 /// # 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")] #[tracing::instrument(name = "Loading configuration")]
pub fn load() -> Result<Self> { pub fn load() -> Result<Self> {
let mut cfg_file = ConfigFile::default(); let mut cfg_file = toml::from_str::<ConfigFile>(DEFAULT_CONFIG_STR)
.context("Failed to parse embedded default config")?;
let candidates = [ let candidates = [
("system-wide", PathBuf::from("/etc/xdg/traxor/config.toml")), ("system-wide", PathBuf::from("/etc/xdg/traxor/config.toml")),

View File

@ -1,7 +1,11 @@
use color_eyre::Result; use color_eyre::Result;
use ratatui::{Terminal, backend::CrosstermBackend}; use ratatui::{Terminal, backend::CrosstermBackend};
use std::io; use std::{io, sync::Arc};
use tracing::{debug, trace}; use tokio::{
sync::Mutex,
time::{self, Duration},
};
use tracing::{trace, warn};
use traxor::{ use traxor::{
app::App, app::App,
config::Config, config::Config,
@ -15,38 +19,54 @@ use traxor::{
async fn main() -> Result<()> { async fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
debug!("Loading configuration...");
let config = Config::load()?; let config = Config::load()?;
debug!("Configuration loaded.");
// Setup the logger.
setup_logger(&config)?; setup_logger(&config)?;
// Create an application. // Wrap App in Arc<Mutex<>> so we can share it between UI and updater
let mut app = App::new(config)?; 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 backend = CrosstermBackend::new(io::stderr());
let terminal = Terminal::new(backend)?; let terminal = Terminal::new(backend)?;
let events = EventHandler::new(250); // Update time in ms let events = EventHandler::new(250); // Update time in ms
let mut tui = Tui::new(terminal, events); let mut tui = Tui::new(terminal, events);
tui.init()?; tui.init()?;
// Start the main loop. // Main loop
while app.running { loop {
// Render the user interface. {
tui.draw(&mut app)?; let app_guard = app.lock().await;
// Handle events. if !app_guard.running {
match tui.events.next()? { break;
Event::Tick => {
trace!(target: "app", "Event::Tick");
app.tick().await?;
} }
}
{
let mut app_guard = app.lock().await;
tui.draw(&mut app_guard)?;
}
match tui.events.next()? {
Event::Tick => {}
Event::Key(key_event) => { Event::Key(key_event) => {
trace!(target: "app", "Event::Key: {:?}", key_event); let mut app_guard = app.lock().await;
if let Some(action) = get_action(key_event, &mut app).await? { if let Some(action) = get_action(key_event, &mut app_guard).await? {
trace!(target: "app", "Action: {:?}", action); update(&mut app_guard, action).await?;
update(&mut app, action).await?;
} }
} }
Event::Mouse(mouse_event) => { Event::Mouse(mouse_event) => {
@ -58,7 +78,6 @@ async fn main() -> Result<()> {
} }
} }
// Exit the user interface.
tui.exit()?; tui.exit()?;
Ok(()) Ok(())
} }

View File

@ -67,11 +67,11 @@ fn make_row<'a>(
highlight: Style, highlight: Style,
) -> Row<'a> { ) -> Row<'a> {
let cells = fields.iter().map(|&field| { let cells = fields.iter().map(|&field| {
if let Some(id) = torrent.id { if let Some(id) = torrent.id
if selected.contains(&id) { && selected.contains(&id)
{
return field.value(torrent).set_style(highlight); return field.value(torrent).set_style(highlight);
} }
}
field.value(torrent).into() field.value(torrent).into()
}); });
Row::new(cells) Row::new(cells)