[WIP] New UI Logic

This commit is contained in:
Alex Emmet 2026-02-18 10:30:41 +01:00
commit 6db4e806f8
26 changed files with 815 additions and 674 deletions

View file

@ -3,7 +3,7 @@ use json::{self, JsonValue, array, object};
use std::fs::{self};
use std::path::Path;
use crate::gui::log_panel::log_message;
use crate::log;
#[derive(PartialEq, Debug, Clone)]
pub enum MessageState {
@ -58,7 +58,7 @@ pub fn add_message(
);
if let Err(e) = fs::create_dir_all(&user_dir) {
log_message(format!("Failed to create chat directory: {}", e));
log!("Failed to create chat directory: {}", e);
return;
}
@ -77,7 +77,7 @@ pub fn add_message(
break;
}
} else {
log_message(format!("Failed to parse existing JSON file: {}", file_name));
log!("Failed to parse existing JSON file: {}", file_name);
}
} else {
break;
@ -85,7 +85,7 @@ pub fn add_message(
chunk_index += 1;
if chunk_index > 1000 {
log_message(format!("Too many message chunks. Aborting add."));
log!("Too many message chunks. Aborting add.");
return;
}
}
@ -98,7 +98,7 @@ pub fn add_message(
};
if let Err(e) = message_chunk.push(json_obj) {
log_message(format!("Failed to push new message into JSON array: {}", e));
log!("Failed to push new message into JSON array: {}", e);
return;
}

View file

@ -9,7 +9,7 @@ use uuid::Uuid;
use walkdir::WalkDir;
use zip::ZipArchive;
use crate::gui::log_panel::log_message;
use crate::log;
#[allow(dead_code)]
pub fn delete_directory(path: &str) -> bool {
@ -23,11 +23,11 @@ fn delete_dir_recursive(directory: &Path) -> bool {
return false;
}
if let Err(e) = fs::remove_dir_all(directory) {
log_message(format!(
log!(
"[IMPORTANT] Couldn't delete directory {}: {}",
directory.display(),
e,
));
);
return false;
}
true
@ -97,7 +97,7 @@ pub fn load_file(path: &str, name: &str) -> String {
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
log_message(format!("[IMPORTANT] Couldn't create directories: {}", e));
log!("[IMPORTANT] Couldn't create directories: {}", e);
return String::new();
}
return String::new();
@ -105,7 +105,7 @@ pub fn load_file(path: &str, name: &str) -> String {
if !file_path.exists() {
if let Err(e) = File::create(&file_path) {
log_message(format!("[IMPORTANT] Couldn't create file: {}", e));
log!("[IMPORTANT] Couldn't create file: {}", e);
}
return String::new();
}
@ -130,17 +130,17 @@ pub fn save_file(path: &str, name: &str, value: &str) {
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
log_message(format!("[IMPORTANT] Couldn't create directories: {}", e));
log!("[IMPORTANT] Couldn't create directories: {}", e);
return;
}
}
if let Err(e) = fs::write(&file_path, value) {
log_message(format!(
log!(
"[IMPORTANT] Couldn't write file {}: {}",
file_path.display(),
e
));
);
}
}
@ -231,7 +231,7 @@ pub async fn download_zip(url: &str, zip_path: &Path) -> Result<(), Box<dyn std:
if !response.status().is_success() {
let err_msg = format!("Failed to download file: Status {}", response.status());
log_message(err_msg.clone());
log!("{}", err_msg.clone());
return Err(err_msg.into());
}
@ -314,7 +314,7 @@ fn extract_zip_contents_to_folder(
}
}
log_message("Extracting directly (no single root folder detected).");
log!("Extracting directly (no single root folder detected).");
let _ = fs::remove_dir_all(target_dir);
fs::rename(&staging_dir, target_dir)?;
@ -323,14 +323,14 @@ fn extract_zip_contents_to_folder(
#[allow(dead_code)]
pub async fn download_and_extract_zip(url: &str, as_name: &str) {
log_message("Downloading ZIP file...");
log!("Downloading ZIP file...");
let base_dir = PathBuf::from(get_directory());
let zip_filename = format!("{}.zip", Uuid::new_v4());
let zip_path = base_dir.join(&zip_filename);
let target_dir = base_dir.join(as_name);
if let Err(e) = download_zip(url, &zip_path).await {
log_message(format!("Error downloading file: {}", e));
log!("Error downloading file: {}", e);
return;
}
@ -341,18 +341,14 @@ pub async fn download_and_extract_zip(url: &str, as_name: &str) {
let successful = match extract_result {
Ok(()) => true,
Err(e) => {
log_message(format!("Error during ZIP extraction: {}", e));
log!("Error during ZIP extraction: {}", e);
false
}
};
if let Err(e) = tokio::fs::remove_file(&zip_path).await {
log_message(format!(
"Error cleaning up ZIP file {}: {}",
zip_path.display(),
e
));
log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e);
} else if successful {
log_message("Downloaded and extracted ZIP file successfully.");
log!("Downloaded and extracted ZIP file successfully.");
}
}

298
src/util/logger.rs Normal file
View file

@ -0,0 +1,298 @@
use std::{
fs::{self, OpenOptions},
io::Write,
path::Path,
sync::{OnceLock, mpsc},
thread,
time::{SystemTime, UNIX_EPOCH},
};
use ratatui::style::Color;
use crate::{APP_STATE, gui::elements::log_card::UiLogEntry, langu::language_manager};
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
#[derive(Clone, Copy)]
#[allow(unused)]
pub enum PrintType {
Call,
Client,
Iota,
Omikron,
Omega,
General,
}
struct LogMessage {
timestamp_ms: u128,
prefix: &'static str,
kind: PrintType,
is_error: bool,
translation_key: Option<String>,
format_args: Vec<String>,
message: Option<String>,
}
pub fn startup() {
let (tx, rx) = mpsc::channel::<LogMessage>();
LOGGER.set(tx).expect("Logger already initialized");
thread::spawn(move || {
let log_dir = Path::new("logs");
fs::create_dir_all(log_dir).expect("Failed to create log directory");
let start_ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let path = log_dir.join(format!("log_{}.txt", start_ts));
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.expect("Failed to open log file");
for msg in rx {
let resolved_message = if let Some(key) = msg.translation_key {
let args: Vec<&str> = msg.format_args.iter().map(|s| s.as_str()).collect();
language_manager::format(&key, &args)
} else {
msg.message.unwrap_or_default()
};
let ts = fixed_box(&msg.timestamp_ms.to_string(), 13);
let line = format!("{} {}", msg.prefix, resolved_message);
let _ = writeln!(file, "{} {}", ts, line);
let color = colorize(msg.kind, msg.is_error);
let ui_entry = UiLogEntry {
line: line.clone(),
color,
};
{
let mut state = APP_STATE.lock().unwrap();
state.push_log(ui_entry);
}
}
});
}
fn colorize(kind: PrintType, is_error: bool) -> Color {
if is_error {
return Color::Red;
}
match kind {
PrintType::Call => Color::Magenta,
PrintType::Client => Color::Green,
PrintType::Iota => Color::Yellow,
PrintType::Omikron => Color::Blue,
PrintType::Omega => Color::Cyan,
PrintType::General => Color::White,
}
}
fn fixed_box(content: &str, width: usize) -> String {
let s: String = content.chars().take(width).collect();
let len = s.chars().count();
if len < width {
format!("[{}{}]", " ".repeat(width - len), s)
} else {
s
}
}
pub fn log_internal_translated(
kind: PrintType,
prefix: &'static str,
is_error: bool,
key: &str,
args: Vec<String>,
) {
if let Some(tx) = LOGGER.get() {
let _ = tx.send(LogMessage {
timestamp_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis(),
prefix,
kind,
is_error,
translation_key: Some(key.to_string()),
format_args: args,
message: None,
});
}
}
pub fn log_internal(kind: PrintType, prefix: &'static str, is_error: bool, message: String) {
if let Some(tx) = LOGGER.get() {
let _ = tx.send(LogMessage {
timestamp_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis(),
prefix,
kind,
is_error,
translation_key: None,
format_args: Vec::new(),
message: Some(message),
});
}
}
use crate::data::communication::CommunicationValue;
use json::JsonValue;
pub fn log_cv_internal(cv: &CommunicationValue, print_type: Option<PrintType>) {
let formatted = format_cv(cv);
log_internal(
print_type.unwrap_or(PrintType::General),
"",
false,
formatted,
);
}
pub fn format_cv(cv: &CommunicationValue) -> String {
let mut parts = Vec::new();
let sender = cv.get_sender();
let receiver = cv.get_receiver();
if sender > 0 && receiver > 0 {
parts.push(format!("{} > {}", sender, receiver));
} else if sender > 0 {
parts.push(format!("{}", sender));
} else if receiver > 0 {
parts.push(format!("> {}", receiver));
}
let comm_type = cv.get_type().to_string();
parts.push(format!("{}", comm_type));
let mut data_parts = Vec::new();
if let JsonValue::Object(data) = &cv.clone().to_json()["data"] {
for (key, value) in data.iter() {
let val_string = match value {
JsonValue::String(s) => s.clone(),
_ => value.dump(),
};
data_parts.push(format!("{} {}", key, val_string));
}
}
if !data_parts.is_empty() {
parts.push(format!("{}", data_parts.join(", ")));
}
parts.join(": ")
}
#[macro_export]
macro_rules! log_cv {
($kind:expr, $cv:expr) => {
$crate::util::logger::log_cv_internal(&$cv, Some($kind))
};
($cv:expr) => {
$crate::util::logger::log_cv_internal(&$cv, None)
};
}
#[macro_export]
macro_rules! log_t {
($key:expr) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"",
false,
$key,
vec![]
)
};
($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"",
false,
$key,
vec![$($arg),+]
)
};
}
#[macro_export]
macro_rules! log_t_err {
($key:expr) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
">>",
true,
$key,
vec![]
)
};
($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
">>",
true,
$key,
vec![$($arg.to_string()),+]
)
};
}
/// Log a general informational message.
#[macro_export]
macro_rules! log {
($($arg:tt)*) => {
$crate::util::logger::log_internal($crate::util::logger::PrintType::General, "", false, format!($($arg)*))
};
}
/// Log an inbound message (`>`).
#[macro_export]
macro_rules! log_in {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::General,
">",
false,
format!($($arg)*)
)
};
}
/// Log an outbound message (`<`).
#[macro_export]
macro_rules! log_out {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::General,
"<",
false,
format!($($arg)*)
)
};
}
/// Log an error message (`>>`).
#[macro_export]
macro_rules! log_err {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::General,
">>",
true,
format!($($arg)*)
)
};
}

View file

@ -4,3 +4,4 @@ pub mod config_util;
pub mod crypto_helper;
pub mod crypto_util;
pub mod file_util;
pub mod logger;