basic creation

This commit is contained in:
Alex Emmet 2025-08-30 22:37:16 +02:00
commit e15b2b407a
14 changed files with 1258 additions and 8 deletions

243
src/data/communication.rs Normal file
View file

@ -0,0 +1,243 @@
use json::{object, JsonValue};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DataTypes {
ErrorType,
ChatPartnerId,
IotaId,
UserId,
UserIds,
UserState,
UserStates,
UserPings,
CallState,
ScreenShare,
PrivateKeyHash,
Accepted,
AcceptedProfiles,
DeniedProfiles,
MessageContent,
MessageChunk,
SendTime,
GetTime,
GetVariant,
SharedSecretOwn,
SharedSecretOther,
SharedSecretSign,
SharedSecret,
CallId,
CallName,
CallSecretSha,
CallSecret,
SharedCallSecret,
StartDate,
EndDate,
ReceiverId,
SenderId,
Signature,
Signed,
Message,
LastPing,
PingIota,
PingClients,
Matches,
Omikron,
LoadedMessages,
MessageAmount,
Position,
Name,
Path,
Codec,
Function,
Payload,
Result,
Interactables,
WantToWatch,
Watcher,
CreatedAt,
Username,
Display,
Avatar,
About,
Status,
PublicKey,
SubLevel,
SubEnd,
CommunityAddress,
Challenge,
CommunityTitle,
Communities,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommunicationType {
Error,
Success,
Message,
MessageLive,
MessageOtherIota,
MessageChunk,
MessageGet,
ChangeConfirm,
ConfirmReceive,
ConfirmRead,
GetChats,
GetStates,
AddCommunity,
RemoveCommunity,
GetCommunities,
Challenge,
ChallengeResponse,
Register,
RegisterResponse,
Identification,
IdentificationResponse,
Ping,
Pong,
AddChat,
SendChat,
IotaConnected,
IotaClosed,
ClientChanged,
ClientConnected,
ClientClosed,
PublicKey,
PrivateKey,
WebrtcSdp,
WebrtcIce,
StartStream,
EndStream,
WatchStream,
GetCall,
NewCall,
CallInvite,
EndCall,
Function,
Update,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LogLevel {
Important = 2,
Normal = 1,
Debug = 0,
None = -1,
DebugOnly = -2,
}
#[derive(Debug, Clone)]
pub struct LogValue {
pub message: String,
pub log_level: LogLevel,
}
impl LogValue {
pub fn new(message: impl Into<String>, log_level: LogLevel) -> Self {
Self {
message: message.into(),
log_level,
}
}
pub fn to_json(&self) -> JsonValue {
object! {
message: self.message.clone(),
log_level: self.log_level.clone() as i32
}
}
}
#[derive(Debug, Clone)]
pub struct CommunicationValue {
pub id: Uuid,
pub comm_type: CommunicationType,
pub log_value: Option<LogValue>,
pub sender: Option<Uuid>,
pub receiver: Option<Uuid>,
pub data: HashMap<DataTypes, JsonValue>,
}
impl CommunicationValue {
pub fn new(comm_type: CommunicationType) -> Self {
Self {
id: Uuid::new_v4(),
comm_type,
log_value: None,
sender: None,
receiver: None,
data: HashMap::new(),
}
}
pub fn with_log(mut self, log: LogValue) -> Self {
self.log_value = Some(log);
self
}
pub fn with_sender(mut self, sender: Uuid) -> Self {
self.sender = Some(sender);
self
}
pub fn with_receiver(mut self, receiver: Uuid) -> Self {
self.receiver = Some(receiver);
self
}
pub fn add_data(mut self, key: DataTypes, value: JsonValue) -> Self {
self.data.insert(key, value);
self
}
pub fn to_json(&self) -> JsonValue {
let mut jdata = object!{};
for (k, v) in &self.data {
jdata[&format!("{:?}", k)] = v.clone();
}
object! {
id: self.id.to_string(),
type: format!("{:?}", self.comm_type),
sender: self.sender.map(|u| u.to_string()).unwrap_or_default(),
receiver: self.receiver.map(|u| u.to_string()).unwrap_or_default(),
log: self.log_value.as_ref().map(|l| l.to_json()).unwrap_or(JsonValue::Null),
data: jdata
}
}
pub fn from_json(json_str: &str) -> Result<Self, String> {
let parsed = json::parse(json_str).map_err(|e| e.to_string())?;
let message_id = parsed["id"].as_str()
.and_then(|s| Uuid::parse_str(s).ok())
.unwrap_or_else(|| Uuid::new_v4());
let comm_type = match parsed["type"].as_str() {
Some("Message") => CommunicationType::Message,
Some("Success") => CommunicationType::Success,
_ => CommunicationType::Error,
};
let sender = parsed["sender"].as_str().and_then(|s| Uuid::parse_str(s).ok());
let receiver = parsed["receiver"].as_str().and_then(|s| Uuid::parse_str(s).ok());
let log_value = if parsed["log"].is_object() {
Some(LogValue::new(
parsed["log"]["message"].as_str().unwrap_or("").to_string(),
LogLevel::Normal,
))
} else {
None
};
Ok(Self {
id: message_id,
comm_type,
log_value,
sender,
receiver,
data: HashMap::new(),
})
}
}

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

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

View file

@ -1,5 +1,16 @@
use json;
use json::{self, JsonValue};
use uuid::Uuid;
mod data;
mod omikron;
use crate::omikron::omikronConnection::{OmikronConnection};
use crate::data::communication::{CommunicationValue, LogLevel, LogValue, CommunicationType, DataTypes};
fn main() {
println!("Hello, world!");
let rt = tokio::runtime::Runtime::new().unwrap();
let omikron = OmikronConnection::new();
rt.block_on(async {
omikron.connect().await;
});
}

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

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

View file

@ -0,0 +1,147 @@
use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::tungstenite::Utf8Bytes;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};
use tokio_tungstenite::{
client_async,
tungstenite::protocol::Message,
MaybeTlsStream,
WebSocketStream,
};
use uuid::Uuid;
#[derive(Clone)]
pub struct OmikronConnection {
writer: Arc<Mutex<Option<futures_util::stream::SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>>,
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(String) + Send>>>>,
}
impl OmikronConnection {
pub fn new() -> Self {
Self {
writer: Arc::new(Mutex::new(None)),
waiting: Arc::new(Mutex::new(HashMap::new())),
}
}
pub async fn connect(&self) {
loop {
match connect_async("wss://tensamin.methanium.net/ws/iota/").await {
Ok((ws_stream, _)) => {
println!("[Omikron] Connected to server");
// Split into writer + reader
let (write_half, read_half) = ws_stream.split();
*self.writer.lock().await = Some(write_half);
// Spawn listener with read_half
self.spawn_listener(read_half);
break;
}
Err(e) => {
println!(
"[Omikron] Connection failed: {}. Retrying in 2s...",
e
);
sleep(Duration::from_secs(2)).await;
}
}
}
}
pub async fn close(&self) {
let mut writer = self.writer.lock().await;
if let Some(mut ws) = writer.take() {
let _ = ws.close().await;
}
println!("[Omikron] Connection closed");
}
fn spawn_listener(
&self,
mut read_half: futures_util::stream::SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
) {
let waiting = self.waiting.clone();
tokio::spawn(async move {
while let Some(msg) = read_half.next().await {
match msg {
Ok(Message::Text(text)) => {
println!("[Omikron] Message received: {}", text);
if text.contains("\"type\":\"pong\"") {
println!("[Omikron] Pong received");
}
// Example: trigger callback if message_id is present
if let Some(id_pos) = text.find("\"message_id\":\"") {
let s = &text[id_pos + 14..];
if let Some(end) = s.find('"') {
let mid = &s[..end];
if let Ok(uuid) = Uuid::parse_str(mid) {
if let Some(callback) =
waiting.lock().await.remove(&uuid)
{
callback(text.to_string());
}
}
}
}
}
Ok(Message::Close(frame)) => {
println!("[Omikron] Closed: {:?}", frame);
break;
}
Err(e) => {
println!("[Omikron] Error: {}", e);
break;
}
_ => {}
}
}
});
}
pub async fn send_message(&self, msg: &str) {
let mut guard = self.writer.lock().await;
if let Some(writer) = guard.as_mut() {
let utf8: Utf8Bytes = Utf8Bytes::from(msg.to_string());
if let Err(e) = writer.send(Message::Text(utf8)).await {
println!("[Omikron] Send failed: {}", e);
}
}
}
pub fn on_answer<F>(&self, message_id: Uuid, callback: F)
where
F: Fn(String) + Send + 'static,
{
tokio::spawn({
let waiting = self.waiting.clone();
async move {
waiting.lock().await.insert(message_id, Box::new(callback));
}
});
}
}
#[tokio::main]
async fn main() {
let omikron = OmikronConnection::new();
omikron.connect().await;
let identification = r#"{
"type": "identification",
"iota_id": "iota-12345",
"user_ids": ["user1", "user2"]
}"#;
omikron.send_message(identification).await;
loop {
sleep(Duration::from_secs(30)).await;
}
}

0
src/users/contact.rs Normal file
View file

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

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

212
src/util/chatFiles.rs Normal file
View file

@ -0,0 +1,212 @@
use json::{self, Value};
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageState {
Read,
Received,
Sending,
Error,
}
impl MessageState {
fn as_str(&self) -> &'static str {
match self {
MessageState::Read => "READ",
MessageState::Received => "RECEIVED",
MessageState::Sending => "SENDING",
MessageState::Error => "ERROR",
}
}
}
pub struct ChatFiles;
impl ChatFiles {
fn load_file(dir: &str, file_name: &str) -> String {
let path = Path::new(dir).join(file_name);
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(dir: &str, file_name: &str, content: &str) -> std::io::Result<()> {
fs::create_dir_all(dir)?;
let path = Path::new(dir).join(file_name);
let mut file = File::create(path)?;
file.write_all(content.as_bytes())
}
pub fn add_message(
send_time: i64,
storage_owner_is_sender: bool,
storage_owner: Uuid,
external_user: Uuid,
message: &str,
) -> std::io::Result<()> {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
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 = Self::load_file(&user_dir, &file_name);
if !file_content.is_empty() {
if let Ok(current_chunk) = json::parse(&file_content) {
if current_chunk.len() < 800 {
message_chunk = current_chunk;
break;
}
}
} else {
break;
}
chunk_index += 1;
}
let json_obj = object! {
"message_time" => send_time,
"message_content" => message,
"sender_is_me" => storage_owner_is_sender,
"message_state" => MessageState::Sending.as_str()
};
message_chunk.push(json_obj).unwrap();
Self::save_file(&user_dir, &format!("msgs_{}.json", chunk_index), &message_chunk.dump())
}
pub fn change_message_state(
storage_owner: Uuid,
external_user: Uuid,
timestamp: i64,
new_state: MessageState,
) -> std::io::Result<()> {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
let path = Path::new(&user_dir);
if !path.exists() {
return Ok(());
}
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let fname = entry.file_name();
let fname_str = fname.to_string_lossy();
if fname_str.starts_with("msgs_") && fname_str.ends_with(".json") {
let file_content = Self::load_file(&user_dir, &fname_str);
if file_content.is_empty() {
continue;
}
if let Ok(mut chunk) = json::parse(&file_content) {
let mut modified = false;
for i in 0..chunk.len() {
if chunk[i]["message_time"].as_i64() == Some(timestamp) {
chunk[i]["message_state"] = JsonValue::from(new_state.as_str());
modified = true;
break;
}
}
if modified {
Self::save_file(&user_dir, &fname_str, &chunk.dump())?;
break;
}
}
}
}
Ok(())
}
pub fn get_messages(
storage_owner: Uuid,
external_user: Uuid,
loaded_messages: usize,
amount: usize,
) -> JsonValue {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
let path = Path::new(&user_dir);
let mut messages = array![];
if !path.exists() {
return messages;
}
let mut latest_chunk_index: i32 = -1;
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.flatten() {
let fname = entry.file_name();
let fname_str = fname.to_string_lossy();
if fname_str.starts_with("msgs_") && fname_str.ends_with(".json") {
if let Some(num) = fname_str
.strip_prefix("msgs_")
.and_then(|s| s.strip_suffix(".json"))
{
if let Ok(index) = num.parse::<i32>() {
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 = Self::load_file(&user_dir, &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
}
}
fn main() {
let owner = Uuid::new_v4();
let external = Uuid::new_v4();
ChatFiles::add_message(1234567890, true, owner, external, "Hello!").unwrap();
ChatFiles::change_message_state(owner, external, 1234567890, MessageState::Read).unwrap();
let msgs = ChatFiles::get_messages(owner, external, 0, 10);
println!("Messages: {}", msgs.dump());
}

91
src/util/chatsUtil.rs Normal file
View file

@ -0,0 +1,91 @@
use json::{self, Value};
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use uuid::Uuid;
use crate::users::Contact::Contact; // assuming you have a Contact struct in a module
pub struct ChatsUtil;
impl ChatsUtil {
fn load_file(dir: &str, file_name: &str) -> String {
let path = Path::new(dir).join(file_name);
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(dir: &str, file_name: &str, content: &str) -> std::io::Result<()> {
fs::create_dir_all(dir)?;
let path = Path::new(dir).join(file_name);
let mut file = File::create(path)?;
file.write_all(content.as_bytes())
}
pub fn mod_user(storage_owner: Uuid, contact: Contact) -> std::io::Result<()> {
let dir = format!("users/{}/contacts/", storage_owner);
let file_name = "contacts.json";
let s = Self::load_file(&dir, file_name);
let mut contacts = if !s.is_empty() {
json::parse(&s).unwrap_or(array![])
} else {
array![]
};
for i in 0..contacts.len() {
if contacts[i]["userID"].as_str() == Some(&contact.user_id.to_string()) {
contacts.remove(i);
break;
}
}
contacts.push(contact.to_json()).unwrap();
Self::save_file(&dir, file_name, &contacts.dump())
}
pub fn get_user(storage_owner: Uuid, user_id: Uuid) -> Option<Contact> {
let dir = format!("users/{}/contacts/", storage_owner);
let file_name = "contacts.json";
let s = Self::load_file(&dir, file_name);
if s.is_empty() {
return None;
}
if let Ok(contacts) = json::parse(&s) {
for i in 0..contacts.len() {
if let Some(uid) = contacts[i]["userID"].as_str() {
if Uuid::parse_str(uid).ok()? == user_id {
return Contact::from_json(&contacts[i]);
}
}
}
}
None
}
pub fn get_users(storage_owner: Uuid) -> Value {
let dir = format!("/users/{}/contacts/", storage_owner);
let file_name = "contacts.json";
let s = Self::load_file(&dir, file_name);
let mut contacts_out = array![];
if !s.is_empty() {
if let Ok(contacts) = json::parse(&s) {
for i in 0..contacts.len() {
if let Some(c) = Contact::from_json(&contacts[i]) {
contacts_out.push(c.info()).unwrap();
}
}
}
}
contacts_out
}
}

65
src/util/configUtil.rs Normal file
View file

@ -0,0 +1,65 @@
use json::JsonValue;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use crate::files::Files;
pub struct ConfigUtil {
pub config: JsonValue,
pub unique: bool,
}
impl ConfigUtil {
pub fn new() -> Self {
Self {
config: JsonValue::new_object(),
unique: false,
}
}
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(Files::MAIN);
if !s.is_empty() {
self.config = json::parse(&s).unwrap_or(JsonValue::new_object());
}
if self.config.has("port") {
if let Some(port) = self.config["port"].as_i32() {
// Set community manager port
}
}
}
pub fn change(&mut self, key: &str, value: JsonValue) {
self.config[key] = value;
self.unique = true;
}
pub fn update(&mut self) {
if self.unique {
let _ = self.save();
}
}
pub fn save(&self) -> std::io::Result<()> {
Self::save_file(Files::MAIN, &self.config.dump())
}
}

162
src/util/fileUtil.rs Normal file
View file

@ -0,0 +1,162 @@
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::ffi::OsStr;
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_directory(path: &str) -> bool {
let dir = Path::new(&Self::get_jar_directory()).join(path);
Self::delete_dir_recursive(&dir)
}
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
}
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 load_file(path: &str, name: &str) -> String {
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 String::new();
}
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
}
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 let Err(e) = fs::write(&file_path, value) {
println!("[IMPORTANT] Couldn't write file {}: {}", file_path.display(), e);
}
}
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 {
// Rust has no direct Runtime like Java.
// As a placeholder, read process memory usage from sysinfo.
use sysinfo::{System, SystemExt, ProcessExt};
let mut sys = System::new_all();
sys.refresh_all();
if let Some(process) = sys.process(process::id() as i32) {
let used = process.memory() * 1024; // kB to bytes
let total = sys.total_memory() * 1024;
return format!(
"{}/{}",
Self::design_byte(used),
Self::design_byte(total)
);
}
"Unknown".to_string()
}
}

4
src/util/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod fileUtil;
pub mod chatFiles;
pub mod configUtil;
pub mod chatsUtil;