[Add] Console Features, QOL

This commit is contained in:
Alex Emmet 2026-02-19 22:58:22 +01:00
commit faf60b144a
11 changed files with 707 additions and 560 deletions

View file

@ -1,19 +1,32 @@
use actix_web::web::block; use crossterm::event::{KeyCode, KeyEvent};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use json::JsonValue;
use ratatui::{ use ratatui::{
Frame, Frame,
layout::{Alignment, Constraint, Layout, Rect}, layout::Rect,
style::{Color, Style}, style::{Color, Style},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},
}; };
use crate::gui::{ use crate::{
ACTIVE_TASKS,
data::communication::{CommunicationType, CommunicationValue, DataTypes},
gui::{
elements::elements::{Element, InteractableElement, JoinableElement}, elements::elements::{Element, InteractableElement, JoinableElement},
interaction_result::InteractionResult, interaction_result::InteractionResult,
ui::{FPS, UI},
util::borders::draw_block_joins, util::borders::draw_block_joins,
},
log, log_cv,
omikron::omikron_connection::OMIKRON_CONNECTION,
users::{user_manager, user_profile::UserProfile},
util::file_util,
};
use std::{
any::Any,
sync::Arc,
time::{Duration, SystemTime},
}; };
use std::any::Any;
pub struct ConsoleCard { pub struct ConsoleCard {
focused: bool, focused: bool,
@ -115,6 +128,10 @@ impl InteractableElement for ConsoleCard {
fn interact(&mut self, key: KeyEvent) -> InteractionResult { fn interact(&mut self, key: KeyEvent) -> InteractionResult {
match key.code { match key.code {
KeyCode::Enter => { KeyCode::Enter => {
let command = self.content.clone();
tokio::spawn(async move {
run_command(&command).await;
});
self.content = "".to_string(); self.content = "".to_string();
InteractionResult::Handled InteractionResult::Handled
} }
@ -145,3 +162,114 @@ impl InteractableElement for ConsoleCard {
self.focused = f; self.focused = f;
} }
} }
pub async fn run_command(command: &str) {
log!(":{}", command);
let parts = command.split(" ").collect::<Vec<&str>>();
match parts.as_slice() {
["tasks"] => {
let active_tasks: Vec<String> =
ACTIVE_TASKS.clone().iter().map(|v| v.to_string()).collect();
log!("Active tasks: {:?}", active_tasks);
}
["fps"] => {
log!("FPS: {}", *FPS.read().await);
}
["ping"] => {
let time = 20;
let now = SystemTime::now();
let conn = {
let guard = OMIKRON_CONNECTION.read().await;
guard.as_ref().cloned()
};
let conn = match conn {
Some(c) => c,
None => return,
};
let response_cv = conn
.await_response(
&CommunicationValue::new(CommunicationType::ping),
Some(Duration::from_secs(time)),
)
.await;
let elapsed = now.elapsed().unwrap_or(Duration::ZERO);
match response_cv {
Ok(response) => log_cv!(response.add_data(
DataTypes::get_time,
JsonValue::from(elapsed.as_millis() as i64)
)),
Err(err) => log!("Ping error: {:?}", err),
}
}
["ping", time] => {
let time = time.parse::<u64>().unwrap_or(20);
let conn = {
let guard = OMIKRON_CONNECTION.read().await;
guard.as_ref().cloned()
};
let conn = match conn {
Some(c) => c,
None => return,
};
let response_cv = conn
.await_response(
&CommunicationValue::new(CommunicationType::ping),
Some(Duration::from_secs(time)),
)
.await;
match response_cv {
Ok(response) => log_cv!(response),
Err(err) => log!("Ping error: {:?}", err),
}
}
["user", "add", username] => {
if let (Some(user), Some(_)) = user_manager::create_user(username).await {
log!("Created user {}", user.user_id);
} else {
log!("Failed to create user");
}
}
["user", "remove", username] => {
if let Some(user) = user_manager::get_user_by_username(username) {
user_manager::remove_user(user.user_id);
log!("Removed user {}", user.user_id);
} else {
log!("Failed to find user");
}
}
["user", "list"] => {
let users: Vec<UserProfile> = user_manager::get_users();
for user in users {
let storage = file_util::get_designed_storage(user.user_id);
log!(
"> Username: {}, ID: {}, created at: {}, storage: {}",
user.username,
user.user_id,
user.created_at,
storage
);
}
}
["user", "info", username] => {
if let Some(user) = user_manager::get_user_by_username(username) {
user_manager::remove_user(user.user_id);
log!("Removed user {}", user.user_id);
} else {
log!("Failed to find user");
}
}
_ => {
log!("Unknown command");
}
}
}

View file

@ -7,10 +7,7 @@ use std::time::Duration;
pub fn setup_input_handler(ui: Arc<UI>) { pub fn setup_input_handler(ui: Arc<UI>) {
tokio::spawn(async move { tokio::spawn(async move {
ACTIVE_TASKS ACTIVE_TASKS.insert("Input Handler".to_string());
.lock()
.unwrap()
.push("Input Handler".to_string());
loop { loop {
{ {
@ -42,8 +39,7 @@ pub fn setup_input_handler(ui: Arc<UI>) {
} }
} }
{ {
let mut tasks = ACTIVE_TASKS.lock().unwrap(); ACTIVE_TASKS.remove("Input Handler");
tasks.retain(|t| t != "Input Handler");
} }
}); });
} }

View file

@ -1,7 +1,7 @@
use crate::gui::{ use crate::gui::{
elements::{ elements::{
console_card::ConsoleCard, console_card::ConsoleCard,
elements::{Element, InteractableElement, JoinableElement}, elements::{InteractableElement, JoinableElement},
log_card::LogCard, log_card::LogCard,
}, },
interaction_result::InteractionResult, interaction_result::InteractionResult,

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
SHUTDOWN, ACTIVE_TASKS, SHUTDOWN,
gui::{ gui::{
input_handler::setup_input_handler, interaction_result::InteractionResult, input_handler::setup_input_handler, interaction_result::InteractionResult,
screens::screens::Screen, screens::screens::Screen,
@ -13,10 +13,12 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::Duration, time::Duration,
}; };
use tokio::sync::RwLock; use tokio::{sync::RwLock, time::Instant};
/// UI state and rendering /// UI state and rendering
pub static UNIQUE: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true)); pub static UNIQUE: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
pub static FPS: Lazy<RwLock<f64>> = Lazy::new(|| RwLock::new(0.0));
pub struct UI { pub struct UI {
pub terminal: Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>, pub terminal: Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>,
screen: Arc<RwLock<Option<Box<dyn Screen>>>>, screen: Arc<RwLock<Option<Box<dyn Screen>>>>,
@ -26,6 +28,9 @@ pub fn start_tui() -> Arc<UI> {
let ui = Arc::new(UI::new()); let ui = Arc::new(UI::new());
let uic = ui.clone(); let uic = ui.clone();
tokio::spawn(async move { tokio::spawn(async move {
ACTIVE_TASKS.insert("UI Renderer".to_string());
let mut last_render = Instant::now();
let mut last: Vec<f64> = Vec::new();
loop { loop {
if *SHUTDOWN.read().await { if *SHUTDOWN.read().await {
break; break;
@ -33,10 +38,19 @@ pub fn start_tui() -> Arc<UI> {
if *UNIQUE.read().await { if *UNIQUE.read().await {
uic.render().await; uic.render().await;
} else { let elapsed = last_render.elapsed().as_secs_f64();
tokio::time::sleep(Duration::from_millis(50)).await; if elapsed > 0.0 {
last.push(1.0 / elapsed);
} }
if last.len() > 10 {
last.remove(0);
} }
*FPS.write().await = last.iter().sum::<f64>() / last.len() as f64;
last_render = Instant::now();
}
tokio::time::sleep(Duration::from_millis(16)).await;
}
ACTIVE_TASKS.remove("UI Renderer");
ratatui::restore(); ratatui::restore();
}); });
setup_input_handler(ui.clone()); setup_input_handler(ui.clone());

View file

@ -1,3 +1,4 @@
use dashmap::DashSet;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use pnet::datalink::NetworkInterface; use pnet::datalink::NetworkInterface;
use std::sync::Arc; use std::sync::Arc;
@ -37,7 +38,7 @@ pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
pub static SHUTDOWN: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(false)); pub static SHUTDOWN: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(false));
pub static RELOAD: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true)); pub static RELOAD: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
pub static ACTIVE_TASKS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); pub static ACTIVE_TASKS: Lazy<DashSet<String>> = Lazy::new(|| DashSet::new());
#[tokio::main(flavor = "multi_thread", worker_threads = 8)] #[tokio::main(flavor = "multi_thread", worker_threads = 8)]
#[allow(unused_must_use, dead_code)] #[allow(unused_must_use, dead_code)]
@ -48,20 +49,12 @@ async fn main() {
let ui = start_tui(); let ui = start_tui();
let ui_clone = ui.clone();
tokio::spawn(async move {
loop {
ui_clone.render().await;
sleep(Duration::from_millis(16)).await;
}
});
let (eula, tos_pp) = consent_state::check(ui.clone()).await; let (eula, tos_pp) = consent_state::check(ui.clone()).await;
if !eula { if !eula {
*SHUTDOWN.write().await = true; *SHUTDOWN.write().await = true;
loop { loop {
if ACTIVE_TASKS.lock().unwrap().is_empty() { if ACTIVE_TASKS.is_empty() {
break; break;
} }
sleep(Duration::from_secs(1)).await; sleep(Duration::from_secs(1)).await;
@ -73,7 +66,7 @@ async fn main() {
if !tos_pp { if !tos_pp {
*SHUTDOWN.write().await = true; *SHUTDOWN.write().await = true;
loop { loop {
if ACTIVE_TASKS.lock().unwrap().is_empty() { if ACTIVE_TASKS.is_empty() {
break; break;
} }
sleep(Duration::from_millis(100)).await; sleep(Duration::from_millis(100)).await;
@ -171,8 +164,10 @@ async fn main() {
} }
let omikron: Arc<OmikronConnection> = Arc::new(OmikronConnection::new()); let omikron: Arc<OmikronConnection> = Arc::new(OmikronConnection::new());
omikron.connect().await; omikron.connect().await;
{
let mut omikron_connection = OMIKRON_CONNECTION.write().await; let mut omikron_connection = OMIKRON_CONNECTION.write().await;
*omikron_connection = Some(omikron.clone()); *omikron_connection = Some(omikron.clone());
}
log_t!("setup_completed"); log_t!("setup_completed");
loop { loop {
if *SHUTDOWN.read().await { if *SHUTDOWN.read().await {
@ -186,7 +181,7 @@ async fn main() {
} }
if *RELOAD.read().await { if *RELOAD.read().await {
loop { loop {
if ACTIVE_TASKS.lock().unwrap().is_empty() { if ACTIVE_TASKS.is_empty() {
break; break;
} }
sleep(Duration::from_secs(1)).await; sleep(Duration::from_secs(1)).await;

View file

@ -4,9 +4,8 @@ use crate::util::chat_files::{MessageState, change_message_state};
use crate::util::chats_util::{get_user, mod_user}; use crate::util::chats_util::{get_user, mod_user};
use crate::util::crypto_util::{DataFormat, SecurePayload}; use crate::util::crypto_util::{DataFormat, SecurePayload};
use crate::util::file_util::{get_children, load_file, save_file}; use crate::util::file_util::{get_children, load_file, save_file};
use crate::util::logger::PrintType;
use crate::util::{chat_files, chats_util}; use crate::util::{chat_files, chats_util};
use crate::{ACTIVE_TASKS, SHUTDOWN, log, log_cv, log_t}; use crate::{ACTIVE_TASKS, SHUTDOWN, log, log_cv_in, log_cv_out, log_t};
use crate::{ use crate::{
data::communication::{CommunicationType, CommunicationValue, DataTypes}, data::communication::{CommunicationType, CommunicationValue, DataTypes},
util::{config_util::CONFIG, crypto_helper}, util::{config_util::CONFIG, crypto_helper},
@ -166,6 +165,9 @@ impl OmikronConnection {
} }
pub async fn send_message(&self, cv: &CommunicationValue) { pub async fn send_message(&self, cv: &CommunicationValue) {
if !cv.is_type(CommunicationType::ping) {
log_cv_out!(cv);
}
Self::send_message_static( Self::send_message_static(
&self.writer, &self.writer,
Arc::clone(&self.is_connected), Arc::clone(&self.is_connected),
@ -184,31 +186,31 @@ impl OmikronConnection {
let sel_out = self.clone(); let sel_out = self.clone();
{ {
ACTIVE_TASKS.lock().unwrap().push("Listener".to_string()); ACTIVE_TASKS.insert("Omikron Listener".to_string());
} }
tokio::spawn(async move { tokio::spawn(async move {
while let Some(msg) = read_half.next().await { while let Some(msg) = read_half.next().await {
if *SHUTDOWN.read().await { if *SHUTDOWN.read().await {
break; break;
} }
sel_out.clone().handle_message( sel_out
.clone()
.handle_message(
msg, msg,
waiting_out.clone(), waiting_out.clone(),
writer_out.clone(), writer_out.clone(),
is_connected_out.clone(), is_connected_out.clone(),
); )
.await;
} }
*is_connected_out.lock().await = false; *is_connected_out.lock().await = false;
log!("Connection closed."); log!("Connection closed.");
});
{ {
ACTIVE_TASKS ACTIVE_TASKS.remove("Omikron Listener");
.lock()
.unwrap()
.retain(|t| !t.eq(&"Listener".to_string()));
} }
});
} }
pub fn handle_message( pub async fn handle_message(
self: Arc<Self>, self: Arc<Self>,
msg: Result<Message, tungstenite::Error>, msg: Result<Message, tungstenite::Error>,
waiting: Arc<DashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync + 'static>>>, waiting: Arc<DashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync + 'static>>>,
@ -219,7 +221,6 @@ impl OmikronConnection {
>, >,
is_connected: Arc<Mutex<bool>>, is_connected: Arc<Mutex<bool>>,
) { ) {
tokio::spawn(async move {
match msg { match msg {
Ok(Message::Close(Some(frame))) => { Ok(Message::Close(Some(frame))) => {
log!("[Omikron] Closed: {:?}", frame); log!("[Omikron] Closed: {:?}", frame);
@ -228,10 +229,15 @@ impl OmikronConnection {
} }
Ok(Message::Text(text)) => { Ok(Message::Text(text)) => {
let cv = CommunicationValue::from_json(&text); let cv = CommunicationValue::from_json(&text);
if let Some((_, y)) = waiting.remove(&cv.get_id()) {
y(cv);
return;
}
if cv.is_type(CommunicationType::pong) { if cv.is_type(CommunicationType::pong) {
self.handle_pong(&cv, true).await; self.handle_pong(&cv, true).await;
return; return;
} }
log_cv_in!(&cv);
if cv.is_type(CommunicationType::challenge) { if cv.is_type(CommunicationType::challenge) {
let conf = CONFIG.read().await; let conf = CONFIG.read().await;
let private_key = conf.get_private_key().unwrap(); let private_key = conf.get_private_key().unwrap();
@ -292,8 +298,7 @@ impl OmikronConnection {
log!("Iota registered with ID: {}", iota_id); log!("Iota registered with ID: {}", iota_id);
let login_message = let login_message =
CommunicationValue::new(CommunicationType::identification) CommunicationValue::new(CommunicationType::identification).add_data(
.add_data(
DataTypes::iota_id, DataTypes::iota_id,
JsonValue::Number(json::number::Number::from(iota_id)), JsonValue::Number(json::number::Number::from(iota_id)),
); );
@ -317,11 +322,6 @@ impl OmikronConnection {
// ************************************************ // // ************************************************ //
// Direct messages // // Direct messages //
// ************************************************ // // ************************************************ //
log_cv!(PrintType::General, &cv);
if let Some((_, y)) = waiting.remove(&cv.get_id()) {
y(cv);
return;
}
if cv.is_type(CommunicationType::message_state) { if cv.is_type(CommunicationType::message_state) {
let sender_id = &cv.get_sender(); let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver(); let receiver_id = &cv.get_receiver();
@ -390,12 +390,8 @@ impl OmikronConnection {
.unwrap_or(""), .unwrap_or(""),
) )
.upgrade(MessageState::Received); .upgrade(MessageState::Received);
let _ = change_message_state( let _ =
timestamp, change_message_state(timestamp, *receiver_id, *sender_id, ms.clone());
*receiver_id,
*sender_id,
ms.clone(),
);
self.send_message( self.send_message(
&CommunicationValue::new(CommunicationType::message_state) &CommunicationValue::new(CommunicationType::message_state)
.with_id(cv.get_id()) .with_id(cv.get_id())
@ -405,10 +401,7 @@ impl OmikronConnection {
DataTypes::send_time, DataTypes::send_time,
cv.get_data(DataTypes::send_time).unwrap().clone(), cv.get_data(DataTypes::send_time).unwrap().clone(),
) )
.add_data( .add_data(DataTypes::message_state, JsonValue::from(ms.as_str())),
DataTypes::message_state,
JsonValue::from(ms.as_str()),
),
) )
.await; .await;
} else { } else {
@ -461,8 +454,7 @@ impl OmikronConnection {
) )
.await; .await;
let forward = let forward = CommunicationValue::new(CommunicationType::message_other_iota)
CommunicationValue::new(CommunicationType::message_other_iota)
.with_id(cv.get_id()) .with_id(cv.get_id())
.with_receiver(other_id) .with_receiver(other_id)
.add_data( .add_data(
@ -470,19 +462,11 @@ impl OmikronConnection {
JsonValue::Number(Number::from(other_id)), JsonValue::Number(Number::from(other_id)),
) )
.with_sender(my_id) .with_sender(my_id)
.add_data( .add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string()))
DataTypes::send_time, .add_data(DataTypes::sender_id, JsonValue::Number(Number::from(my_id)))
JsonValue::String(now_ms.to_string()),
)
.add_data(
DataTypes::sender_id,
JsonValue::Number(Number::from(my_id)),
)
.add_data( .add_data(
DataTypes::content, DataTypes::content,
JsonValue::String( JsonValue::String(cv.get_data(DataTypes::content).unwrap().to_string()),
cv.get_data(DataTypes::content).unwrap().to_string(),
),
); );
Self::send_message_static( Self::send_message_static(
&writer.clone(), &writer.clone(),
@ -550,8 +534,7 @@ impl OmikronConnection {
.unwrap_or(&JsonValue::Null) .unwrap_or(&JsonValue::Null)
.as_i64() .as_i64()
.unwrap_or(0); .unwrap_or(0);
let mut contact = let mut contact = get_user(user_id, other_id).unwrap_or(Contact::new(other_id));
get_user(user_id, other_id).unwrap_or(Contact::new(other_id));
contact.set_last_message_at( contact.set_last_message_at(
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
@ -630,8 +613,7 @@ impl OmikronConnection {
if cv.is_type(CommunicationType::settings_save) { if cv.is_type(CommunicationType::settings_save) {
let my_id = cv.get_sender(); let my_id = cv.get_sender();
let settings_name = let settings_name = cv.get_data(DataTypes::settings_name).unwrap().to_string();
cv.get_data(DataTypes::settings_name).unwrap().to_string();
let settings_value = cv.get_data(DataTypes::payload).unwrap().to_string(); let settings_value = cv.get_data(DataTypes::payload).unwrap().to_string();
save_file( save_file(
@ -654,8 +636,7 @@ impl OmikronConnection {
} }
if cv.is_type(CommunicationType::settings_load) { if cv.is_type(CommunicationType::settings_load) {
let my_id = cv.get_sender(); let my_id = cv.get_sender();
let settings_name = let settings_name = cv.get_data(DataTypes::settings_name).unwrap().to_string();
cv.get_data(DataTypes::settings_name).unwrap().to_string();
let settings_value_str = load_file( let settings_value_str = load_file(
&format!("users/{}/settings/", my_id), &format!("users/{}/settings/", my_id),
&format!("{}.settings", settings_name), &format!("{}.settings", settings_name),
@ -707,7 +688,6 @@ impl OmikronConnection {
} }
_ => {} _ => {}
} }
});
} }
pub async fn send_message_static( pub async fn send_message_static(
@ -718,27 +698,26 @@ impl OmikronConnection {
msg: String, msg: String,
) { ) {
let mut guard = writer.lock().await; let mut guard = writer.lock().await;
if let Some(writer) = guard.as_mut() { if let Some(writer) = guard.as_mut() {
match writer.send(Message::Text(Utf8Bytes::from(msg))).await { if let Err(e) = writer.send(Message::Text(Utf8Bytes::from(msg))).await {
Ok(_) => match writer.flush().await {
Ok(_) => return,
Err(e) => {
log_t!("send_message_failed", e.to_string()); log_t!("send_message_failed", e.to_string());
*connected.lock().await = false; *connected.lock().await = false;
return;
} }
},
Err(e) => { if let Err(e) = writer.flush().await {
log_t!("send_message_failed", e.to_string()); log_t!("send_message_failed", e.to_string());
*connected.lock().await = false; *connected.lock().await = false;
} }
}
} else { } else {
log_t!("send_message_failed", "Immutable Writer".to_string()); log_t!("send_message_failed", "Writer not initialized".to_string());
*connected.lock().await = false; *connected.lock().await = false;
} }
} }
pub async fn await_response( pub async fn await_response(
self: Arc<Self>, &self,
cv: &CommunicationValue, cv: &CommunicationValue,
timeout_duration: Option<Duration>, timeout_duration: Option<Duration>,
) -> Result<CommunicationValue, String> { ) -> Result<CommunicationValue, String> {

View file

@ -66,9 +66,9 @@ pub async fn start(port: u16) -> bool {
let server_handle = server.handle(); let server_handle = server.handle();
tx.send(server_handle).unwrap(); tx.send(server_handle).unwrap();
ACTIVE_TASKS.lock().unwrap().push("WebServer".into()); ACTIVE_TASKS.insert("WebServer".into());
server.await.unwrap(); server.await.unwrap();
ACTIVE_TASKS.lock().unwrap().retain(|t| t != "WebServer"); ACTIVE_TASKS.remove("WebServer");
log!("Web Server shutdown complete."); log!("Web Server shutdown complete.");
}); });

View file

@ -1,5 +1,4 @@
use actix_web::{HttpRequest, HttpResponse, web}; use actix_web::{HttpRequest, HttpResponse};
use json::JsonValue;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::util::file_util::load_file_vec; use crate::util::file_util::load_file_vec;
@ -17,32 +16,21 @@ fn codec_for_ext(ext: &str) -> &'static str {
} }
} }
pub async fn handle(req: HttpRequest, body: web::Bytes) -> HttpResponse { pub async fn handle(req: HttpRequest) -> HttpResponse {
// Optional JSON parsing
let _body_json: Option<JsonValue> = if !body.is_empty() {
json::parse(std::str::from_utf8(&body).unwrap_or("")).ok()
} else {
None
};
let req_path = req.path().trim_start_matches('/'); let req_path = req.path().trim_start_matches('/');
// 1⃣ Resolve the filesystem path
let mut fs_path = PathBuf::from("web"); let mut fs_path = PathBuf::from("web");
// Boolean P: no path provided → redirect to index.html
if req_path.is_empty() { if req_path.is_empty() {
fs_path.push("index.html"); fs_path.push("index.html");
} else { } else {
fs_path.extend(req_path.split('/')); fs_path.extend(req_path.split('/'));
} }
// Boolean D: path is directory → serve index.html inside
if fs_path.is_dir() { if fs_path.is_dir() {
fs_path.push("index.html"); fs_path.push("index.html");
} }
// Boolean E: extension provided
let ext_opt = fs_path.extension().and_then(|e| e.to_str()); let ext_opt = fs_path.extension().and_then(|e| e.to_str());
let mut final_name = fs_path let mut final_name = fs_path
.file_name() .file_name()
@ -51,7 +39,6 @@ pub async fn handle(req: HttpRequest, body: web::Bytes) -> HttpResponse {
.to_string(); .to_string();
if ext_opt.is_none() { if ext_opt.is_none() {
// No extension provided → try HTML
if final_name.is_empty() { if final_name.is_empty() {
final_name = "index.html".to_string(); final_name = "index.html".to_string();
} else { } else {
@ -68,12 +55,10 @@ pub async fn handle(req: HttpRequest, body: web::Bytes) -> HttpResponse {
let dir = fs_path.parent().unwrap_or(Path::new("web")); let dir = fs_path.parent().unwrap_or(Path::new("web"));
// 2⃣ Try to load the resolved file
match load_file_vec(dir.to_str().unwrap_or("web"), &final_name) { match load_file_vec(dir.to_str().unwrap_or("web"), &final_name) {
Ok(content) => HttpResponse::Ok().content_type(content_type).body(content), Ok(content) => HttpResponse::Ok().content_type(content_type).body(content),
Err(_) => { Err(_) => {
// For static assets, return plain 404
let ext = fs_path.extension().and_then(|e| e.to_str()).unwrap_or(""); let ext = fs_path.extension().and_then(|e| e.to_str()).unwrap_or("");
if matches!(ext, "js" | "css" | "woff2") { if matches!(ext, "js" | "css" | "woff2") {
return HttpResponse::NotFound() return HttpResponse::NotFound()
@ -81,7 +66,6 @@ pub async fn handle(req: HttpRequest, body: web::Bytes) -> HttpResponse {
.body("Not found"); .body("Not found");
} }
// 3⃣ Try to serve 404.html from web folder
let fallback = load_file_vec("web", "404.html") let fallback = load_file_vec("web", "404.html")
.unwrap_or_else(|_| include_bytes!("../../static/web/404.html").to_vec()); .unwrap_or_else(|_| include_bytes!("../../static/web/404.html").to_vec());

View file

@ -1,10 +1,11 @@
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::log;
use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection}; use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
use crate::users::user_profile::UserProfile; use crate::users::user_profile::UserProfile;
use crate::util::crypto_helper::{self, public_key_to_base64}; use crate::util::crypto_helper::{self, public_key_to_base64};
use crate::util::file_util::{load_file, save_file}; use crate::util::file_util::{load_file, save_file};
use crate::util::logger::PrintType;
use crate::{RELOAD, SHUTDOWN}; use crate::{RELOAD, SHUTDOWN};
use crate::{log, log_cv};
use base64::{Engine as _, engine::general_purpose::STANDARD}; use base64::{Engine as _, engine::general_purpose::STANDARD};
use hex::{self}; use hex::{self};
use json::JsonValue; use json::JsonValue;
@ -47,26 +48,37 @@ pub async fn load_from_tu(username: &str) -> Result<(), ()> {
USERS.lock().unwrap().push(user_profile); USERS.lock().unwrap().push(user_profile);
Ok(()) Ok(())
} }
pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>) { pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>) {
let omikron_con: Arc<OmikronConnection> = let register_cv = CommunicationValue::new(CommunicationType::get_register);
OMIKRON_CONNECTION.read().await.as_ref().unwrap().clone();
let register_cv = if let Ok(register_cv) = omikron_con let conn = {
.clone() let guard = OMIKRON_CONNECTION.read().await;
.await_response( guard.as_ref().cloned()
&CommunicationValue::new(CommunicationType::get_register), };
Some(Duration::from_secs(20)),
) let conn = match conn {
Some(c) => c,
None => return (None, None),
};
let response_cv = match conn
.await_response(&register_cv, Some(Duration::from_secs(20)))
.await .await
{ {
register_cv Ok(cv) => cv,
} else { Err(_) => return (None, None),
return (None, None);
}; };
let user_id = register_cv log_cv!(PrintType::Omega, response_cv);
.get_data(DataTypes::register_id)
let user_id = match response_cv
.get_data(DataTypes::user_id)
.unwrap_or(&JsonValue::Null) .unwrap_or(&JsonValue::Null)
.as_i64() .as_i64()
.unwrap_or(0); {
Some(id) => id,
None => return (None, None),
};
let mut buf = [0u8; 56]; let mut buf = [0u8; 56];
let mut rng = OsRng; let mut rng = OsRng;
rng.fill_bytes(&mut buf); rng.fill_bytes(&mut buf);
@ -101,10 +113,22 @@ pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>
.add_data(DataTypes::iota_id, JsonValue::Number(Number::from(user_id))) .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(user_id)))
.add_data(DataTypes::reset_token, JsonValue::String(reset_token)); .add_data(DataTypes::reset_token, JsonValue::String(reset_token));
let response_cv = omikron_con let conn = {
let guard = OMIKRON_CONNECTION.read().await;
guard.as_ref().cloned()
};
let conn = match conn {
Some(c) => c,
None => return (None, None),
};
let response_cv = conn
.await_response(&cv, Some(Duration::from_secs(20))) .await_response(&cv, Some(Duration::from_secs(20)))
.await; .await;
if let Ok(resp) = response_cv { if let Ok(resp) = response_cv {
log_cv!(PrintType::Omega, resp);
if !resp.is_type(CommunicationType::success) { if !resp.is_type(CommunicationType::success) {
return (None, None); return (None, None);
} }
@ -125,6 +149,15 @@ pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>
(Some(up), Some(STANDARD.encode(&private_key.as_bytes()))) (Some(up), Some(STANDARD.encode(&private_key.as_bytes())))
} }
pub fn get_user_by_username(username: &str) -> Option<UserProfile> {
USERS
.lock()
.unwrap()
.iter()
.cloned()
.find(|u| u.username == username)
}
pub fn get_user(user_id: i64) -> Option<UserProfile> { pub fn get_user(user_id: i64) -> Option<UserProfile> {
USERS USERS
.lock() .lock()

View file

@ -34,7 +34,7 @@ fn delete_dir_recursive(directory: &Path) -> bool {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn delete_user_directory(user_id: Uuid) { pub fn delete_user_directory(user_id: i64) {
let user_dir = Path::new(&get_directory()) let user_dir = Path::new(&get_directory())
.join("users") .join("users")
.join(user_id.to_string()); .join(user_id.to_string());
@ -189,7 +189,7 @@ pub fn get_directory_size(directory: &Path) -> u64 {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn get_designed_storage(user_id: Uuid) -> String { pub fn get_designed_storage(user_id: i64) -> String {
let user_dir = Path::new(&get_directory()) let user_dir = Path::new(&get_directory())
.join("users") .join("users")
.join(user_id.to_string()); .join(user_id.to_string());

View file

@ -30,7 +30,7 @@ pub enum PrintType {
struct LogMessage { struct LogMessage {
timestamp_ms: u128, timestamp_ms: u128,
prefix: &'static str, prefix: String,
kind: PrintType, kind: PrintType,
is_error: bool, is_error: bool,
@ -116,7 +116,7 @@ fn fixed_box(content: &str, width: usize) -> String {
pub fn log_internal_translated( pub fn log_internal_translated(
kind: PrintType, kind: PrintType,
prefix: &'static str, prefix: String,
is_error: bool, is_error: bool,
key: &str, key: &str,
args: Vec<String>, args: Vec<String>,
@ -140,7 +140,7 @@ pub fn log_internal_translated(
} }
} }
pub fn log_internal(kind: PrintType, prefix: &'static str, is_error: bool, message: String) { pub fn log_internal(kind: PrintType, prefix: String, is_error: bool, message: String) {
if let Some(tx) = LOGGER.get() { if let Some(tx) = LOGGER.get() {
tokio::spawn(async move { tokio::spawn(async move {
*UNIQUE.write().await = true; *UNIQUE.write().await = true;
@ -162,12 +162,12 @@ pub fn log_internal(kind: PrintType, prefix: &'static str, is_error: bool, messa
use crate::data::communication::CommunicationValue; use crate::data::communication::CommunicationValue;
use json::JsonValue; use json::JsonValue;
pub fn log_cv_internal(cv: &CommunicationValue, print_type: Option<PrintType>) { pub fn log_cv_internal(prefix: String, cv: &CommunicationValue, print_type: Option<PrintType>) {
let formatted = format_cv(cv); let formatted = format_cv(cv);
log_internal( log_internal(
print_type.unwrap_or(PrintType::General), print_type.unwrap_or(PrintType::General),
"", prefix,
false, false,
formatted, formatted,
); );
@ -211,10 +211,28 @@ pub fn format_cv(cv: &CommunicationValue) -> String {
#[macro_export] #[macro_export]
macro_rules! log_cv { macro_rules! log_cv {
($kind:expr, $cv:expr) => { ($kind:expr, $cv:expr) => {
$crate::util::logger::log_cv_internal(&$cv, Some($kind)) $crate::util::logger::log_cv_internal("".to_string(), &$cv, Some($kind))
}; };
($cv:expr) => { ($cv:expr) => {
$crate::util::logger::log_cv_internal(&$cv, None) $crate::util::logger::log_cv_internal("".to_string(), &$cv, None)
};
}
#[macro_export]
macro_rules! log_cv_in {
($kind:expr, $cv:expr) => {
$crate::util::logger::log_cv_internal("> ".to_string(), &$cv, Some($kind))
};
($cv:expr) => {
$crate::util::logger::log_cv_internal("> ".to_string(), &$cv, None)
};
}
#[macro_export]
macro_rules! log_cv_out {
($kind:expr, $cv:expr) => {
$crate::util::logger::log_cv_internal("< ".to_string(), &$cv, Some($kind))
};
($cv:expr) => {
$crate::util::logger::log_cv_internal("< ".to_string(), &$cv, None)
}; };
} }
@ -223,7 +241,7 @@ macro_rules! log_t {
($key:expr) => { ($key:expr) => {
$crate::util::logger::log_internal_translated( $crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
"", "".to_string(),
false, false,
$key, $key,
vec![] vec![]
@ -233,7 +251,7 @@ macro_rules! log_t {
($key:expr, $($arg:expr),+) => { ($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated( $crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
"", "".to_string(),
false, false,
$key, $key,
vec![$($arg),+] vec![$($arg),+]
@ -245,7 +263,7 @@ macro_rules! log_t_err {
($key:expr) => { ($key:expr) => {
$crate::util::logger::log_internal_translated( $crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
">>", "".to_string(),
true, true,
$key, $key,
vec![] vec![]
@ -255,7 +273,7 @@ macro_rules! log_t_err {
($key:expr, $($arg:expr),+) => { ($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated( $crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
">>", "".to_string(),
true, true,
$key, $key,
vec![$($arg.to_string()),+] vec![$($arg.to_string()),+]
@ -267,7 +285,7 @@ macro_rules! log_t_err {
#[macro_export] #[macro_export]
macro_rules! log { macro_rules! log {
($($arg:tt)*) => { ($($arg:tt)*) => {
$crate::util::logger::log_internal($crate::util::logger::PrintType::General, "", false, format!($($arg)*)) $crate::util::logger::log_internal($crate::util::logger::PrintType::General, "".to_string(), false, format!($($arg)*))
}; };
} }
/// Log an inbound message (`>`). /// Log an inbound message (`>`).
@ -276,7 +294,7 @@ macro_rules! log_in {
($($arg:tt)*) => { ($($arg:tt)*) => {
$crate::util::logger::log_internal( $crate::util::logger::log_internal(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
">", ">".to_string(),
false, false,
format!($($arg)*) format!($($arg)*)
) )
@ -288,7 +306,7 @@ macro_rules! log_out {
($($arg:tt)*) => { ($($arg:tt)*) => {
$crate::util::logger::log_internal( $crate::util::logger::log_internal(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
"<", "<".to_string(),
false, false,
format!($($arg)*) format!($($arg)*)
) )
@ -300,7 +318,7 @@ macro_rules! log_err {
($($arg:tt)*) => { ($($arg:tt)*) => {
$crate::util::logger::log_internal( $crate::util::logger::log_internal(
$crate::util::logger::PrintType::General, $crate::util::logger::PrintType::General,
">>", ">>".to_string(),
true, true,
format!($($arg)*) format!($($arg)*)
) )