use crate::{ communities::{ community::Community, community_connection::CommunityConnection, interactables::interactable::Interactable, }, data::communication::{CommunicationType, CommunicationValue, DataTypes}, gui::log_panel::log_message, util::file_util::{get_children, load_file, save_file}, }; use aes_gcm::aead::Payload; use async_trait::async_trait; use axum::Json; use json::{JsonValue, array, object}; use rustls::ClientConnection; use std::sync::Arc; use std::{any::Any, collections::HashMap}; use std::{ fs::{self, File}, pin::Pin, }; use uuid::Uuid; pub struct TextChat { name: String, path: String, community: Arc, } impl TextChat { pub fn new() -> TextChat { TextChat { name: String::new(), path: String::new(), community: Arc::new(Community::new()), } } pub fn add_message(&self, send_time: u128, sender: Uuid, message: &str) { let user_dir = &format!( "communities/{}/interactables/{}/{}", self.get_community().get_name(), self.get_path(), self.get_name() ); if let Err(e) = fs::create_dir_all(user_dir) { log_message(format!("Failed to create chat directory: {}", e)); return; } let mut chunk_index = 0; let mut message_chunk = array![]; // find latest chunk not full (max 800 msgs) loop { let file_name = format!("msgs_{}.json", chunk_index); let file_content = load_file(&user_dir, &file_name); if !file_content.is_empty() { if let Ok(current_chunk) = json::parse(&file_content) { if current_chunk.is_array() && current_chunk.len() < 800 { message_chunk = current_chunk; break; } } else { log_message(format!("Failed to parse existing JSON file: {}", file_name)); } } else { // New file, use empty array break; } chunk_index += 1; if chunk_index > 1000 { log_message(format!("Too many message chunks. Aborting add.")); return; } } let json_obj = object! { "timestamp" => send_time as i64, "content" => message, "sender" => sender.to_string(), }; if let Err(e) = message_chunk.push(json_obj) { log_message(format!("Failed to push new message into JSON array: {}", e)); return; } let file_name = format!("msgs_{}.json", chunk_index); log_message(format!("Saving message to {}/{}", user_dir, file_name)); save_file(&user_dir, &file_name, &message_chunk.dump()); } pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue { let mut messages = array![]; let mut latest_chunk_index: i32 = -1; let files = get_children(&format!( "communities/{}/interactables/{}/{}", self.get_community().get_name(), self.get_path(), self.get_name() )); for entry in files { if let Some(num) = { entry .strip_prefix("msgs_") .and_then(|s| s.strip_suffix(".json")) } { if let Ok(index) = num.parse::() { if index > latest_chunk_index { latest_chunk_index = index; } } } } if latest_chunk_index == -1 { return messages; } let mut to_skip = loaded_messages; let mut needed = amount; for chunk_index in (0..=latest_chunk_index).rev() { if needed == 0 { break; } let file_name = format!("msgs_{}.json", chunk_index); let file_content = load_file( &format!( "communities/{}/interactables/{}/{}", self.get_community().get_name(), self.get_path(), self.get_name() ), &file_name, ); if file_content.is_empty() { continue; } if let Ok(chunk) = json::parse(&file_content) { for i in (0..chunk.len()).rev() { if needed == 0 { break; } if to_skip > 0 { to_skip -= 1; continue; } messages.push(chunk[i].clone()).unwrap(); needed -= 1; } } } messages } } #[async_trait] impl Interactable for TextChat { fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn get_codec(&self) -> String { "text".to_string() } fn set_name(&mut self, name: String) { self.name = name; } fn set_path(&mut self, path: String) { self.path = path; } fn get_community(&self) -> &Arc { &self.community } fn set_community(&mut self, community: Arc) { self.community = community; } fn get_name(&self) -> &String { &self.name } fn get_path(&self) -> &String { &self.path } fn get_total_path(&self) -> String { String::new() + &self.path + "/" + &self.name } fn get_data(&self) -> JsonValue { JsonValue::new_object() } async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue { let payload = cv.get_data(DataTypes::payload).unwrap(); if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "get_messages" { let amount = payload["amount"].as_i64().unwrap(); let loaded_messages = payload["loaded_messages"].as_i64().unwrap(); let messages = self.get_messages(loaded_messages, amount).clone(); let mut payload = JsonValue::new_object(); payload["messages"] = messages; return CommunicationValue::new(CommunicationType::function) .with_id(cv.get_id()) .add_data_str(DataTypes::name, self.name.clone()) .add_data_str(DataTypes::path, self.path.clone()) .add_data_str(DataTypes::result, "message_chunk".to_string()) .add_data(DataTypes::payload, payload); } if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" { let message = payload["message"].as_str().unwrap(); let milliseconds_timestamp: u128 = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis(); self.add_message(milliseconds_timestamp, cv.get_sender().unwrap(), message); let mut distribution_payload = JsonValue::new_object(); distribution_payload["message"] = JsonValue::String(message.to_string()); distribution_payload["sender_id"] = JsonValue::String(cv.get_sender().unwrap().to_string()); distribution_payload["send_time"] = JsonValue::String(milliseconds_timestamp.to_string()); let distribution = CommunicationValue::new(CommunicationType::update) .with_id(cv.get_id()) .add_data_str(DataTypes::name, self.name.clone()) .add_data_str(DataTypes::path, self.path.clone()) .add_data_str(DataTypes::result, "message_live".to_string()) .add_data(DataTypes::payload, distribution_payload); let connections: HashMap>> = self.get_community().get_connections().await.clone(); for con in connections.values() { for c in con { let cd: &Arc = c; cd.send_message(&distribution).await; } } return CommunicationValue::new(CommunicationType::function) .with_id(cv.get_id()) .add_data_str(DataTypes::name, self.name.clone()) .add_data_str(DataTypes::path, self.path.clone()) .add_data_str(DataTypes::result, "message_received".to_string()) .add_data(DataTypes::payload, JsonValue::new_object()); } CommunicationValue::new(CommunicationType::error).with_id(cv.get_id()) } fn to_json(&self) -> JsonValue { let mut v = JsonValue::new_object(); v } fn load(&mut self, community: Arc, path: String, name: String, json: &JsonValue) { self.community = community; self.name = name; self.path = path; } }