Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions rustatio-core/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ pub fn set_instance_context(instance_id: Option<u32>) {

/// Get the current instance context
fn get_instance_prefix() -> String {
INSTANCE_CONTEXT.with(|ctx| {
ctx.borrow().as_ref().map_or_else(String::new, |id| format!("[Instance {id}] "))
})
INSTANCE_CONTEXT
.with(|ctx| ctx.borrow().as_ref().map_or_else(String::new, |label| format!("[{label}] ")))
}

#[cfg(all(not(target_arch = "wasm32"), feature = "desktop"))]
Expand Down Expand Up @@ -310,15 +309,15 @@ mod tests {
#[test]
fn test_instance_context_str_prefix() {
set_instance_context_str(Some("abc"));
assert_eq!(get_instance_prefix(), "[Instance abc] ");
assert_eq!(get_instance_prefix(), "[abc] ");
set_instance_context_str(None);
assert_eq!(get_instance_prefix(), "");
}

#[test]
fn test_instance_context_prefix() {
set_instance_context(Some(7));
assert_eq!(get_instance_prefix(), "[Instance 7] ");
assert_eq!(get_instance_prefix(), "[7] ");
set_instance_context(None);
assert_eq!(get_instance_prefix(), "");
}
Expand Down
27 changes: 20 additions & 7 deletions rustatio-desktop/src/commands/faker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ use tauri::{AppHandle, State};
use crate::logging::log_and_emit;
use crate::state::{AppState, FakerInstance};

fn set_instance_label(state: &AppState, instance_id: u32, fallback: Option<&str>) {
let label = state
.fakers
.try_read()
.ok()
.and_then(|fakers| fakers.get(&instance_id).map(|instance| instance.summary.name.clone()))
.filter(|name| !name.is_empty())
.or_else(|| fallback.map(std::string::ToString::to_string))
.unwrap_or_else(|| instance_id.to_string());

rustatio_core::logger::set_instance_context_str(Some(&label));
}

#[tauri::command]
pub async fn start_faker(
instance_id: u32,
Expand Down Expand Up @@ -46,7 +59,7 @@ pub async fn start_faker(

let torrent_info_hash = torrent.info_hash;

rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, Some(&torrent.name));

// Check if instance already exists (restarting) - preserve cumulative stats
let mut config_with_cumulative = config.clone();
Expand Down Expand Up @@ -137,7 +150,7 @@ pub async fn stop_faker(
app: AppHandle,
) -> Result<(), String> {
log_and_emit!(&app, instance_id, info, "Stopping faker");
rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, None);

// Clone the Arc under read lock, then drop the HashMap lock
let faker = {
Expand Down Expand Up @@ -181,7 +194,7 @@ pub async fn stop_faker(

#[tauri::command]
pub async fn update_faker(instance_id: u32, state: State<'_, AppState>) -> Result<(), String> {
rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, None);

let faker = {
let fakers = state.fakers.read().await;
Expand All @@ -200,7 +213,7 @@ pub async fn update_stats_only(
instance_id: u32,
state: State<'_, AppState>,
) -> Result<FakerStats, String> {
rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, None);

let faker = {
let fakers = state.fakers.read().await;
Expand Down Expand Up @@ -233,7 +246,7 @@ pub async fn scrape_tracker(
instance_id: u32,
state: State<'_, AppState>,
) -> Result<(i64, i64, i64), String> {
rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, None);

let faker = {
let fakers = state.fakers.read().await;
Expand All @@ -254,7 +267,7 @@ pub async fn pause_faker(
app: AppHandle,
) -> Result<(), String> {
log_and_emit!(&app, instance_id, info, "Pausing faker");
rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, None);

let faker = {
let fakers = state.fakers.read().await;
Expand All @@ -278,7 +291,7 @@ pub async fn resume_faker(
app: AppHandle,
) -> Result<(), String> {
log_and_emit!(&app, instance_id, info, "Resuming faker");
rustatio_core::logger::set_instance_context(Some(instance_id));
set_instance_label(&state, instance_id, None);

let faker = {
let fakers = state.fakers.read().await;
Expand Down
26 changes: 19 additions & 7 deletions rustatio-desktop/src/commands/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::sync::Arc;
use tauri::{AppHandle, State};
use tokio::task::JoinSet;

use crate::logging::log_and_emit;
use crate::logging::{format_instance_message, log_and_emit};
use crate::state::{hex_info_hash, now_secs, AppState, FakerInstance};
use rustatio_watch::InstanceSource;

Expand Down Expand Up @@ -256,7 +256,7 @@ pub async fn grid_import_files(
pub async fn grid_start(
ids: Vec<u32>,
state: State<'_, AppState>,
_app: AppHandle,
app: AppHandle,
) -> Result<GridActionResponse, String> {
let mut succeeded = Vec::new();
let mut failed = Vec::new();
Expand All @@ -281,6 +281,7 @@ pub async fn grid_start(
}

// Spawn HTTP announces in background — return immediately
let app_handle = app.clone();
tauri::async_runtime::spawn(async move {
let mut join_set = JoinSet::new();
for (id, faker) in to_start {
Expand All @@ -292,8 +293,14 @@ pub async fn grid_start(

while let Some(result) = join_set.join_next().await {
match result {
Ok((id, Ok(()))) => log::info!("[Instance {id}] Started via grid action"),
Ok((id, Err(e))) => log::error!("[Instance {id}] Grid start failed: {e}"),
Ok((id, Ok(()))) => log::info!(
"{}",
format_instance_message(&app_handle, id, "Started via grid action")
),
Ok((id, Err(e))) => {
let msg = format!("Grid start failed: {e}");
log::error!("{}", format_instance_message(&app_handle, id, &msg));
}
Err(e) => log::error!("Grid start join error: {e}"),
}
}
Expand All @@ -306,7 +313,7 @@ pub async fn grid_start(
pub async fn grid_stop(
ids: Vec<u32>,
state: State<'_, AppState>,
_app: AppHandle,
app: AppHandle,
) -> Result<GridActionResponse, String> {
let mut succeeded = Vec::new();
let mut failed = Vec::new();
Expand All @@ -332,6 +339,7 @@ pub async fn grid_stop(

// Spawn HTTP stop announces + cumulative stats update in background
let fakers_arc = Arc::clone(&state.fakers);
let app_handle = app.clone();
tauri::async_runtime::spawn(async move {
let mut join_set = JoinSet::new();
for (id, faker) in to_stop {
Expand All @@ -346,11 +354,15 @@ pub async fn grid_stop(
while let Some(result) = join_set.join_next().await {
match result {
Ok((id, stats, Ok(()))) => {
log::info!("[Instance {id}] Stopped via grid action");
log::info!(
"{}",
format_instance_message(&app_handle, id, "Stopped via grid action")
);
stats_updates.push((id, stats));
}
Ok((id, _, Err(e))) => {
log::error!("[Instance {id}] Grid stop failed: {e}");
let msg = format!("Grid stop failed: {e}");
log::error!("{}", format_instance_message(&app_handle, id, &msg));
}
Err(e) => log::error!("Grid stop join error: {e}"),
}
Expand Down
31 changes: 27 additions & 4 deletions rustatio-desktop/src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use serde::Serialize;
use tauri::{AppHandle, Emitter};
use tauri::{AppHandle, Emitter, Manager};

use crate::state::AppState;

#[derive(Clone, Serialize)]
pub struct LogEvent {
Expand All @@ -23,6 +25,24 @@ pub fn emit_log(app: &AppHandle, level: &str, message: String) {
let _ = app.emit("log-event", log_event);
}

pub fn resolve_instance_label(app: &AppHandle, instance_id: u32) -> String {
let state = app.state::<AppState>();
let Ok(fakers) = state.fakers.try_read() else {
return instance_id.to_string();
};

fakers
.get(&instance_id)
.map(|instance| instance.summary.name.clone())
.filter(|name| !name.is_empty())
.unwrap_or_else(|| instance_id.to_string())
}

pub fn format_instance_message(app: &AppHandle, instance_id: u32, message: &str) -> String {
let label = resolve_instance_label(app, instance_id);
format!("[{label}] {message}")
}

macro_rules! log_and_emit {
($app:expr, info, $($arg:tt)*) => {
{
Expand All @@ -40,14 +60,16 @@ macro_rules! log_and_emit {
};
($app:expr, $instance_id:expr, info, $($arg:tt)*) => {
{
let msg = format!("[Instance {}] {}", $instance_id, format!($($arg)*));
let msg_body = format!($($arg)*);
let msg = $crate::logging::format_instance_message($app, $instance_id, &msg_body);
log::info!("{}", msg);
$crate::logging::emit_log($app, "info", msg);
}
};
($app:expr, $instance_id:expr, warn, $($arg:tt)*) => {
{
let msg = format!("[Instance {}] {}", $instance_id, format!($($arg)*));
let msg_body = format!($($arg)*);
let msg = $crate::logging::format_instance_message($app, $instance_id, &msg_body);
log::warn!("{}", msg);
$crate::logging::emit_log($app, "warn", msg);
}
Expand All @@ -61,7 +83,8 @@ macro_rules! log_and_emit {
};
($app:expr, $instance_id:expr, error, $($arg:tt)*) => {
{
let msg = format!("[Instance {}] {}", $instance_id, format!($($arg)*));
let msg_body = format!($($arg)*);
let msg = $crate::logging::format_instance_message($app, $instance_id, &msg_body);
log::error!("{}", msg);
$crate::logging::emit_log($app, "error", msg);
}
Expand Down
47 changes: 41 additions & 6 deletions rustatio-server/src/services/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ use rustatio_core::logger::set_instance_context_str;
use rustatio_core::FakerStats;
use std::sync::Arc;

fn resolve_instance_label(
instances: &std::collections::HashMap<String, super::instance::FakerInstance>,
id: &str,
) -> String {
instances
.get(id)
.map(|instance| instance.summary.name.clone())
.filter(|name| !name.is_empty())
.unwrap_or_else(|| id.to_string())
}

#[async_trait]
pub trait InstanceLifecycle {
async fn start_instance(&self, id: &str) -> Result<(), String>;
Expand All @@ -17,7 +28,11 @@ pub trait InstanceLifecycle {
#[async_trait]
impl InstanceLifecycle for AppState {
async fn start_instance(&self, id: &str) -> Result<(), String> {
set_instance_context_str(Some(id));
let label = {
let instances = self.instances.read().await;
resolve_instance_label(&instances, id)
};
set_instance_context_str(Some(&label));

let (faker, restore) = {
let instances = self.instances.read().await;
Expand Down Expand Up @@ -45,7 +60,11 @@ impl InstanceLifecycle for AppState {
}

async fn stop_instance(&self, id: &str) -> Result<FakerStats, String> {
set_instance_context_str(Some(id));
let label = {
let instances = self.instances.read().await;
resolve_instance_label(&instances, id)
};
set_instance_context_str(Some(&label));

let faker = {
let instances = self.instances.read().await;
Expand Down Expand Up @@ -75,7 +94,11 @@ impl InstanceLifecycle for AppState {
}

async fn pause_instance(&self, id: &str) -> Result<(), String> {
set_instance_context_str(Some(id));
let label = {
let instances = self.instances.read().await;
resolve_instance_label(&instances, id)
};
set_instance_context_str(Some(&label));

let faker = {
let instances = self.instances.read().await;
Expand All @@ -94,7 +117,11 @@ impl InstanceLifecycle for AppState {
}

async fn resume_instance(&self, id: &str) -> Result<(), String> {
set_instance_context_str(Some(id));
let label = {
let instances = self.instances.read().await;
resolve_instance_label(&instances, id)
};
set_instance_context_str(Some(&label));

let faker = {
let instances = self.instances.read().await;
Expand All @@ -113,7 +140,11 @@ impl InstanceLifecycle for AppState {
}

async fn update_instance(&self, id: &str) -> Result<FakerStats, String> {
set_instance_context_str(Some(id));
let label = {
let instances = self.instances.read().await;
resolve_instance_label(&instances, id)
};
set_instance_context_str(Some(&label));

let faker = {
let instances = self.instances.read().await;
Expand All @@ -137,7 +168,11 @@ impl InstanceLifecycle for AppState {
}

async fn update_stats_only(&self, id: &str) -> Result<FakerStats, String> {
set_instance_context_str(Some(id));
let label = {
let instances = self.instances.read().await;
resolve_instance_label(&instances, id)
};
set_instance_context_str(Some(&label));

let faker = {
let instances = self.instances.read().await;
Expand Down
10 changes: 9 additions & 1 deletion rustatio-server/src/services/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,15 @@ async fn update_all_running_instances(
continue;
}

set_instance_context_str(Some(&id));
let label = {
let guard = instances.read().await;
guard
.get(&id)
.map(|instance| instance.summary.name.clone())
.filter(|name| !name.is_empty())
.unwrap_or_else(|| id.clone())
};
set_instance_context_str(Some(&label));
if let Err(e) = faker.update().await {
tracing::warn!("Scheduler: update failed for instance {}: {}", id, e);
continue;
Expand Down
2 changes: 1 addition & 1 deletion rustatio-server/src/services/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ impl AppState {
}

async fn create_instance_internal(&self, context: InstanceBuildContext) -> Result<(), String> {
set_instance_context_str(Some(&context.id));
set_instance_context_str(Some(context.summary.name.as_str()));

let id = context.id.clone();
let existing = self.collect_existing_instance_state(&context).await;
Expand Down
Loading
Loading