Omikron Connects Data Sends Identified Users loaded

This commit is contained in:
Alex Emmet 2025-09-07 15:56:48 +02:00
commit a1d8bcbeff
10 changed files with 228 additions and 209 deletions

View file

@ -56,7 +56,7 @@ impl AuthConnector {
if !cv.is_type(CommunicationType::Success) {
return None;
}
Uuid::parse_str(cv.get_data(DataTypes::UserId).unwrap()).ok()
Uuid::parse_str(&*cv.get_data(DataTypes::UserId).unwrap().to_string()).ok()
}
pub async fn get_user(user_id: Uuid) -> Option<AuthUser> {
@ -90,7 +90,7 @@ impl AuthConnector {
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
Uuid::parse_str(cv.get_data(DataTypes::UserId).unwrap()).ok()
Uuid::parse_str(&*cv.get_data(DataTypes::UserId).unwrap().to_string()).ok()
}
pub async fn complete_register(user_profile: &UserProfile, iota_id: &str) -> bool {

View file

@ -286,7 +286,7 @@ pub struct CommunicationValue {
pub log_value: Option<LogValue>,
pub sender: Option<Uuid>,
pub receiver: Option<Uuid>,
pub data: HashMap<DataTypes, String>,
pub data: HashMap<DataTypes, JsonValue>,
}
impl CommunicationValue {
@ -328,11 +328,15 @@ impl CommunicationValue {
pub fn get_receiver(&self) -> Option<Uuid> {
self.receiver.clone()
}
pub fn add_data(mut self, key: DataTypes, value: String) -> Self {
pub fn add_data_str(mut self, key: DataTypes, value: String) -> Self {
self.data.insert(key, JsonValue::String(value));
self
}
pub fn add_data(mut self, key: DataTypes, value: JsonValue) -> Self {
self.data.insert(key, value);
self
}
pub fn get_data(&mut self, key: DataTypes) -> Option<&String> {
pub fn get_data(&mut self, key: DataTypes) -> Option<&JsonValue> {
self.data.get(&key)
}
@ -376,7 +380,7 @@ impl CommunicationValue {
if parsed["data"].is_object() {
for (k, v) in parsed["data"].entries() {
if let Some(val) = v.as_str() {
data.insert(DataTypes::parse(k.to_string()), val.to_string());
data.insert(DataTypes::parse(k.to_string()), v.clone());
}
}
}
@ -394,12 +398,12 @@ impl CommunicationValue {
.with_id(message_id);
if let Some(s) = sender {
cv = cv.add_data(DataTypes::SenderId, s.to_string());
cv = cv.add_data(DataTypes::SenderId, JsonValue::String(s.to_string()));
}
cv
}
pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue {
let receiver = Uuid::from_str(original.get_data(DataTypes::ReceiverId).unwrap()).ok()
let receiver = Uuid::from_str(&*original.get_data(DataTypes::ReceiverId).unwrap().to_string()).ok()
.or(Option::from(Uuid::nil()));
let now_ms = SystemTime::now()
@ -410,12 +414,12 @@ impl CommunicationValue {
let mut cv = CommunicationValue::new(CommunicationType::MessageOtherIota)
.with_id(original.get_id())
.with_receiver(receiver.unwrap())
.add_data(DataTypes::SendTime, now_ms.to_string())
.add_data(DataTypes::MessageContent, original.get_data(DataTypes::MessageContent).unwrap().to_string());
.add_data(DataTypes::SendTime, JsonValue::String(now_ms.to_string()))
.add_data(DataTypes::MessageContent, JsonValue::String(original.get_data(DataTypes::MessageContent).unwrap().to_string()));
// include sender_id if the original had one
if let Some(sender) = original.get_sender() {
cv = cv.add_data(DataTypes::SenderId, sender.to_string());
cv = cv.add_data(DataTypes::SenderId, JsonValue::String(sender.to_string()));
}
cv
}

27
src/eula/eula_checker.rs Normal file
View file

@ -0,0 +1,27 @@
use crate::util::file_util::{load_file, save_file};
pub fn check_eula() -> bool{
let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\
\nYou can find our Terms of service on https://docs.tensamin.methanium.net/legal/terms-of-service/.\
\neula=false";
let file = load_file("", "eula.txt");
if(file.is_empty()){
save_file("", "eula.txt", eula);
return false;
}
if (file.contains("eula=false")) {
false
} else if (file.contains("eula=true")) {
true
} else {
false
}
}
pub fn accept_eula(){
let eula = "By changing the value to \"true\" you agree to some shit we say on our website IDK this shouldn't be public yet!\
\nYou also give us all rights to your soul, and we own your dog now.\
\neula=true";
save_file("", "eula.txt", eula);
}

1
src/eula/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod eula_checker;

View file

@ -1,42 +1,59 @@
use tokio::runtime::Runtime;
use std::process::{Command, ExitStatus};
use futures_util::SinkExt;
use json::{self};
use json::{self, JsonValue};
use json::JsonValue::String;
use uuid::Uuid;
mod data;
mod omikron;
mod util;
mod users;
mod eula;
mod gui {
pub mod ratatui_interface;
}
mod auth;
mod auth;
use crate::eula::*;
use crate::omikron::omikron_connection::{OmikronConnection};
use crate::data::communication::{CommunicationValue, CommunicationType, DataTypes};
use gui::{ratatui_interface};
use crate::users::user_manager::UserManager;
use crate::util::config_util::ConfigUtil;
#[tokio::main]
async fn main() {
let mut omikron: OmikronConnection = OmikronConnection::new();
if(!eula_checker::check_eula()){
println!("Please accept the end user license agreement before launching!");
return;
}
let mut c_util = ConfigUtil::new();
c_util.load();
if !c_util.config.has_key("iota_id") {
c_util.change("iota_id", Uuid::new_v4());
c_util.save();
}
UserManager::load_users().await;
let mut sb = "".to_string();
for up in UserManager::get_users() {
sb = sb + "," + &*up.user_id.to_string();
}
UserManager::save_users();
let omikron: OmikronConnection = OmikronConnection::new();
omikron.connect().await;
omikron.send_message(
CommunicationValue::new(
CommunicationType::Identification
)
.add_data(DataTypes::UserIds, Uuid::new_v4().to_string())
.add_data(DataTypes::IotaId, Uuid::new_v4().to_string())
.add_data(DataTypes::UserIds, String(sb.to_string()))
.add_data(DataTypes::IotaId, String(c_util.config["iota_id"].to_string()))
.to_json()
.to_string()
.as_mut()
.to_string()
);
let mut child = Command::new("sleep").arg("5").spawn().unwrap();
let _result = child.wait().unwrap();
omikron.close().await;
println!("reached end of main");
).await;
loop {}
}

View file

@ -1,9 +1,9 @@
use futures_util::{SinkExt, StreamExt};
use std::collections::HashMap;
use std::ptr::write;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use json::JsonValue;
use tokio::sync::Mutex;
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};
@ -37,7 +37,7 @@ impl OmikronConnection {
}
/// Connect loop with retry
pub async fn connect(&self) {
pub async fn connect<'a>(&'a self) {
loop {
match connect_async("wss://tensamin.methanium.net/ws/iota/").await {
Ok((ws_stream, _)) => {
@ -85,14 +85,6 @@ impl OmikronConnection {
println!("[Omikron] Pong received");
// handle pingpong reset here
}
if waiting.lock().await.contains_key(&cv.get_id()) {
if let Some(callback) = waiting.lock().await.remove(&cv.get_id()) {
callback(cv);
}
continue;
}
// ************************************************ //
// Direct messages //
// ************************************************ //
@ -100,19 +92,20 @@ impl OmikronConnection {
let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver();
ChatFiles::add_message(
cv.get_data(DataTypes::SendTime).unwrap().parse::<i64>().unwrap_or(-1),
cv.get_data(DataTypes::SendTime).unwrap().as_i64().unwrap_or(-1),
false,
receiver_id.unwrap(),
sender_id.unwrap(),
cv.get_data(DataTypes::MessageContent).unwrap().as_str(),
cv.get_data(DataTypes::MessageContent).unwrap().as_str().unwrap(),
);
let response = CommunicationValue::new(CommunicationType::MessageLive)
.with_id(cv.get_id())
.with_receiver(cv.get_receiver().unwrap())
.add_data(DataTypes::SendTime, cv.get_data(DataTypes::SendTime).unwrap().to_string())
.add_data(DataTypes::Message, cv.get_data(DataTypes::MessageContent).unwrap().to_string())
.add_data(DataTypes::SenderId, cv.get_sender().unwrap().to_string());
Self::send_message_static(&writer, response.to_json().to_string()).await;
.add_data(DataTypes::SendTime, cv.get_data(DataTypes::SendTime).unwrap().clone())
.add_data(DataTypes::Message, cv.get_data(DataTypes::MessageContent).unwrap().clone())
.add_data(DataTypes::SenderId, JsonValue::String(cv.get_sender().unwrap().clone().to_string()));
Self::send_message_static(&writer.clone(), response.to_json().to_string()).await;
continue;
}
@ -129,10 +122,10 @@ impl OmikronConnection {
);
// ack
let ack = CommunicationValue::ack_message(cv.get_id(), my_id);
Self::send_message_static(&writer, ack.to_json().to_string()).await;
Self::send_message_static(&writer.clone(), ack.to_json().to_string()).await;
// forward
let forward = CommunicationValue::forward_to_other_iota(&mut cv);
Self::send_message_static(&writer, forward.to_json().to_string()).await;
Self::send_message_static(&writer.clone(), forward.to_json().to_string()).await;
continue;
}
@ -147,9 +140,9 @@ impl OmikronConnection {
.with_id(cv.get_id())
.with_receiver(my_id);
if !messages.is_empty() {
resp = resp.add_data(DataTypes::MessageChunk, messages.to_string());
resp = resp.add_data(DataTypes::MessageChunk, messages);
}
Self::send_message_static(&writer, resp.to_json().to_string()).await;
Self::send_message_static(&writer.clone(), resp.to_json().to_string()).await;
continue;
}
@ -159,14 +152,14 @@ impl OmikronConnection {
let resp = CommunicationValue::new(CommunicationType::GetChats)
.with_id(cv.get_id())
.with_receiver(user_id.unwrap())
.add_data(DataTypes::UserIds, users.to_string());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
.add_data(DataTypes::UserIds, users);
Self::send_message_static(&writer.clone(), resp.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::AddChat) {
let user_id = cv.get_sender();
let other_id = Uuid::from_str(&*cv.get_data(DataTypes::ReceiverId).unwrap().to_string()).unwrap();
let other_id = Uuid::from_str(&*cv.get_data(DataTypes::UserId).unwrap().to_string()).unwrap();
let mut contact = ChatsUtil::get_user(user_id.unwrap(), other_id).unwrap_or(Contact::new(other_id)); // needs ChatsUtil + Contact
contact.set_last_message_at(SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -176,7 +169,7 @@ impl OmikronConnection {
let resp = CommunicationValue::new(CommunicationType::AddChat)
.with_id(cv.get_id())
.with_receiver(user_id.unwrap());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
Self::send_message_static(&writer.clone(), resp.to_json().to_string()).await;
continue;
}
@ -189,7 +182,7 @@ impl OmikronConnection {
let resp = CommunicationValue::new(CommunicationType::AddCommunity)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
Self::send_message_static(&writer.clone(), resp.to_json().to_string()).await;
continue;
}
@ -197,8 +190,8 @@ impl OmikronConnection {
let resp = CommunicationValue::new(CommunicationType::GetCommunities)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap())
.add_data(DataTypes::Communities, UserCommunityUtil::get_communities(cv.get_sender().unwrap()).to_string()); // needs UserCommunityUtil
Self::send_message_static(&writer, resp.to_json().to_string()).await;
.add_data(DataTypes::Communities, UserCommunityUtil::get_communities(cv.get_sender().unwrap())); // needs UserCommunityUtil
Self::send_message_static(&writer.clone(), resp.to_json().to_string()).await;
continue;
}
@ -207,7 +200,7 @@ impl OmikronConnection {
let resp = CommunicationValue::new(CommunicationType::RemoveCommunity)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
Self::send_message_static(&writer.clone(), resp.to_json().to_string()).await;
continue;
}
}
@ -220,16 +213,20 @@ impl OmikronConnection {
}
});
}
pub fn send_message(&self, msg: String){
OmikronConnection::send_message_static(&self.writer, msg);
pub async fn send_message(&self, msg: String) {
Self::send_message_static(&self.writer, msg).await;
}
pub async fn send_message_static(
writer: &Arc<Mutex<Option<futures_util::stream::SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>>,
msg: String,
) {
) -> Result<(), tokio_tungstenite::tungstenite::Error> {
let mut guard = writer.lock().await;
if let Some(writer) = guard.as_mut() {
let _ = writer.send(Message::Text(msg)).await;
writer.send(Message::Text(msg)).await?;
writer.flush().await?;
Ok(())
} else {
Err(tokio_tungstenite::tungstenite::Error::ConnectionClosed)
}
}

View file

@ -1,6 +1,5 @@
use json::{self, JsonValue};
use std::time::{SystemTime, UNIX_EPOCH};
use axum::Json;
use uuid::Uuid;
#[derive(Debug, Clone)]

View file

@ -10,6 +10,7 @@ use json::{JsonValue};
use once_cell::sync::Lazy;
use crate::users::user_profile::UserProfile;
use crate::users::user_profile_full::UserProfileFull;
use crate::util::file_util::{load_file, save_file};
pub struct UserManager;
@ -72,20 +73,19 @@ impl UserManager {
let users = USERS.lock().unwrap();
let arr: Vec<JsonValue> = users.iter().map(|u| u.to_json()).collect();
let json_str = JsonValue::Array(arr).dump();
fs::write("users.json", json_str)?;
save_file("", "users.json", &json_str);
Ok(())
}
pub async fn load_users() -> io::Result<()> {
let path = Path::new("users.json");
if !path.exists() {
return Ok(());
}
let content = fs::read_to_string(path)?;
let content = load_file("", "users.json");
if content.trim().is_empty() {
return Ok(());
}
let parsed = json::parse(&content).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let parsed = json::parse(&content)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
if let JsonValue::Array(arr) = parsed {
let mut users = USERS.lock().unwrap();
for j in arr.iter() {

View file

@ -2,6 +2,8 @@ use json::JsonValue;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use uuid::Uuid;
use crate::util::file_util::{load_file, save_file};
pub struct ConfigUtil {
pub config: JsonValue,
@ -16,37 +18,15 @@ impl ConfigUtil {
}
}
fn load_file(path: &str) -> String {
if let Ok(mut f) = File::open(path) {
let mut content = String::new();
let _ = f.read_to_string(&mut content);
content
} else {
String::new()
}
}
fn save_file(path: &str, content: &str) -> std::io::Result<()> {
if let Some(parent) = Path::new(path).parent() {
fs::create_dir_all(parent)?;
}
let mut file = File::create(path)?;
file.write_all(content.as_bytes())
}
pub fn load(&mut self) {
let s = Self::load_file("config.json");
let s = load_file("", "config.json");
if !s.is_empty() {
self.config = json::parse(&s).unwrap_or(JsonValue::new_object());
}
if self.config.has_key("ssl_port") {
if let Some(port) = self.config["ssl_port"].as_i32() {
}
}
}
pub fn change(&mut self, key: &str, value: JsonValue) {
self.config[key] = value;
pub fn change(&mut self, key: &str, value: Uuid) {
self.config[key] = JsonValue::String(value.to_string());
self.unique = true;
}
@ -56,7 +36,7 @@ impl ConfigUtil {
}
}
pub fn save(&self) -> std::io::Result<()> {
Self::save_file("config.json", &self.config.dump())
pub fn save(&self) {
save_file("","config.json", &self.config.to_string());
}
}

View file

@ -8,149 +8,143 @@ use std::process;
use walkdir::WalkDir;
use std::fmt::Write as FmtWrite;
use uuid::Uuid;
pub struct FileUtil;
impl FileUtil {
pub fn delete_file(path: &str, name: &str) -> bool {
let dir = Path::new(&Self::get_jar_directory()).join(path);
let file = dir.join(name);
if !file.exists() {
return false;
}
fs::remove_file(file).is_ok()
pub fn delete_file(path: &str, name: &str) -> bool {
let dir = Path::new(&get_directory()).join(path);
let file = dir.join(name);
if !file.exists() {
return false;
}
fs::remove_file(file).is_ok()
}
pub fn delete_directory(path: &str) -> bool {
let dir = Path::new(&Self::get_jar_directory()).join(path);
Self::delete_dir_recursive(&dir)
pub fn delete_directory(path: &str) -> bool {
let dir = Path::new(&get_directory()).join(path);
delete_dir_recursive(&dir)
}
fn delete_dir_recursive(directory: &Path) -> bool {
if !directory.exists() {
return false;
}
fn delete_dir_recursive(directory: &Path) -> bool {
if !directory.exists() {
return false;
}
if let Err(e) = fs::remove_dir_all(directory) {
println!("[IMPORTANT] Couldn't delete directory {}: {}", directory.display(), e);
return false;
}
true
if let Err(e) = fs::remove_dir_all(directory) {
println!("[IMPORTANT] Couldn't delete directory {}: {}", directory.display(), e);
return false;
}
true
}
pub fn delete_user_directory(user_id: Uuid) {
let user_dir = Path::new(&Self::get_jar_directory())
.join("users")
.join(user_id.to_string());
let _ = Self::delete_dir_recursive(&user_dir);
}
pub fn delete_user_directory(user_id: Uuid) {
let user_dir = Path::new(&get_directory())
.join("users")
.join(user_id.to_string());
let _ = delete_dir_recursive(&user_dir);
}
pub fn load_file(path: &str, name: &str) -> String {
let dir = Path::new(&Self::get_jar_directory()).join(path);
let file_path = dir.join(name);
pub fn load_file(path: &str, name: &str) -> String {
let dir = Path::new(&get_directory()).join(path);
let file_path = dir.join(name);
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
println!("[IMPORTANT] Couldn't create directories: {}", e);
return String::new();
}
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
println!("[IMPORTANT] Couldn't create directories: {}", e);
return String::new();
}
if !file_path.exists() {
if let Err(e) = File::create(&file_path) {
println!("[IMPORTANT] Couldn't create file: {}", e);
}
return String::new();
}
let mut content = String::new();
if let Ok(mut f) = File::open(&file_path) {
let _ = f.read_to_string(&mut content);
}
content
return String::new();
}
pub fn save_file(path: &str, name: &str, value: &str) {
let dir = Path::new(&Self::get_jar_directory()).join(path);
let file_path = dir.join(name);
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
println!("[IMPORTANT] Couldn't create directories: {}", e);
return;
}
if !file_path.exists() {
if let Err(e) = File::create(&file_path) {
println!("[IMPORTANT] Couldn't create file: {}", e);
}
return String::new();
}
if let Err(e) = fs::write(&file_path, value) {
println!("[IMPORTANT] Couldn't write file {}: {}", file_path.display(), e);
let mut content = String::new();
if let Ok(mut f) = File::open(&file_path) {
let _ = f.read_to_string(&mut content);
}
content
}
pub fn save_file(path: &str, name: &str, value: &str) {
let dir = Path::new(&get_directory()).join(path);
let file_path = dir.join(name);
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
println!("[IMPORTANT] Couldn't create directories: {}", e);
return;
}
}
pub fn get_jar_directory() -> String {
// In Rust, use current_exe as a proxy for JAR directory
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
exe.parent()
.unwrap_or(Path::new("."))
.to_string_lossy()
.to_string()
}
pub fn used_space() -> u64 {
Self::get_directory_size(&PathBuf::from(Self::get_jar_directory()))
}
pub fn get_directory_size(directory: &Path) -> u64 {
let mut size = 0;
for entry in WalkDir::new(directory).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file() {
if let Ok(metadata) = path.metadata() {
size += path
.file_name()
.unwrap_or(OsStr::new(""))
.len() as u64;
size += metadata.len();
}
}
}
size
}
pub fn get_designed_storage(user_id: Uuid) -> String {
let user_dir = Path::new(&Self::get_jar_directory())
.join("users")
.join(user_id.to_string());
Self::design_byte(Self::get_directory_size(&user_dir))
}
pub fn design_byte(bytes: u64) -> String {
let mut hr_size = format!("{:.2}B", bytes as f64);
let k = bytes as f64 / 1024.0;
let m = k / 1024.0;
let g = m / 1024.0;
let t = g / 1024.0;
if t >= 1.0 {
hr_size = format!("{:.2}TB", t);
} else if g >= 1.0 {
hr_size = format!("{:.2}GB", g);
} else if m >= 1.0 {
hr_size = format!("{:.2}MB", m);
} else if k >= 1.0 {
hr_size = format!("{:.2}KB", k);
}
hr_size
}
pub fn get_used_ram() -> String {
let mut sys = System::new_all();
sys.refresh_all();
let used = sys.used_memory() * 1024; // kB to bytes
let total = sys.total_memory() * 1024;
format!(
"{}/{}",
Self::design_byte(used),
Self::design_byte(total)
)
if let Err(e) = fs::write(&file_path, value) {
println!("[IMPORTANT] Couldn't write file {}: {}", file_path.display(), e);
}
}
pub fn get_directory() -> String {
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
exe.parent()
.unwrap_or(Path::new("."))
.to_string_lossy()
.to_string()
}
pub fn used_space() -> u64 {
get_directory_size(&PathBuf::from(get_directory()))
}
pub fn get_directory_size(directory: &Path) -> u64 {
let mut size = 0;
for entry in WalkDir::new(directory).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file() {
if let Ok(metadata) = path.metadata() {
size += path
.file_name()
.unwrap_or(OsStr::new(""))
.len() as u64;
size += metadata.len();
}
}
}
size
}
pub fn get_designed_storage(user_id: Uuid) -> String {
let user_dir = Path::new(&get_directory())
.join("users")
.join(user_id.to_string());
design_byte(get_directory_size(&user_dir))
}
pub fn design_byte(bytes: u64) -> String {
let mut hr_size = format!("{:.2}B", bytes as f64);
let k = bytes as f64 / 1024.0;
let m = k / 1024.0;
let g = m / 1024.0;
let t = g / 1024.0;
if t >= 1.0 {
hr_size = format!("{:.2}TB", t);
} else if g >= 1.0 {
hr_size = format!("{:.2}GB", g);
} else if m >= 1.0 {
hr_size = format!("{:.2}MB", m);
} else if k >= 1.0 {
hr_size = format!("{:.2}KB", k);
}
hr_size
}
pub fn get_used_ram() -> String {
let mut sys = System::new_all();
sys.refresh_all();
let used = sys.used_memory() * 1024; // kB to bytes
let total = sys.total_memory() * 1024;
format!(
"{}/{}",
design_byte(used),
design_byte(total)
)
}