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

View File

@ -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<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 = [
("system-wide", PathBuf::from("/etc/xdg/traxor/config.toml")),

View File

@ -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<Mutex<>> 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(())
}

View File

@ -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()
});