UI Utility

This commit is contained in:
Alex Emmet 2025-09-28 18:58:42 +00:00
commit cbd357ce14
14 changed files with 962 additions and 622 deletions

126
Cargo.lock generated
View file

@ -334,6 +334,31 @@ dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
version = "0.28.1"
@ -913,7 +938,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
"windows-core 0.61.2",
]
[[package]]
@ -1281,25 +1306,6 @@ dependencies = [
"libc",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]]
name = "object"
version = "0.36.7"
@ -1534,6 +1540,26 @@ dependencies = [
"unicode-width 0.2.0",
]
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.17"
@ -1964,15 +1990,16 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.0"
version = "0.30.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"once_cell",
"rayon",
"windows",
]
@ -2531,24 +2558,21 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-link 0.1.3",
"windows-numerics",
"windows-core 0.52.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-core",
"windows-targets 0.52.6",
]
[[package]]
@ -2564,17 +2588,6 @@ dependencies = [
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core",
"windows-link 0.1.3",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
@ -2609,16 +2622,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core",
"windows-link 0.1.3",
]
[[package]]
name = "windows-registry"
version = "0.5.3"
@ -2717,15 +2720,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"

View file

@ -17,7 +17,7 @@ cmake = "*"
tokio = { version = "*", features = ["full"] }
uuid = { version = "*", features = ["v4"] }
walkdir = "2.5.0"
sysinfo = "*"
sysinfo = "0.30"
serde = { version = "1.0.219", features = ["derive"] }
ratatui = { version = "*", features = ["all-widgets"]}
bytes = "*"

View file

@ -1,4 +1,6 @@
use crate::CONFIG;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::log_panel::log_message;
use crate::users::user_profile::UserProfile;
use hex;
use json::JsonValue;
@ -48,7 +50,7 @@ impl AuthConnector {
.ok()?;
let json = res.text().await.ok()?;
let cv = CommunicationValue::from_json(&json);
Option::from(cv.is_type(CommunicationType::Success))
Option::from(cv.is_type(CommunicationType::success))
}
pub async fn get_uuid(username: &str) -> Option<Uuid> {
@ -60,10 +62,10 @@ impl AuthConnector {
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
if !cv.is_type(CommunicationType::Success) {
if !cv.is_type(CommunicationType::success) {
return None;
}
Uuid::parse_str(&*cv.get_data(DataTypes::UserId).unwrap().to_string()).ok()
Uuid::parse_str(&*cv.get_data(DataTypes::user_id).unwrap().to_string()).ok()
}
pub async fn get_user(user_id: Uuid) -> Option<AuthUser> {
@ -73,31 +75,31 @@ impl AuthConnector {
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
if cv.comm_type != CommunicationType::Success {
if cv.comm_type != CommunicationType::success {
return None;
}
Some(AuthUser {
created_at: cv
.get_data(DataTypes::CreatedAt)
.get_data(DataTypes::created_at)
.unwrap()
.to_string()
.parse::<i64>()
.unwrap_or(-1),
username: cv.get_data(DataTypes::Username).unwrap().to_string(),
display: cv.get_data(DataTypes::Display).unwrap().to_string(),
avatar: cv.get_data(DataTypes::Avatar).unwrap().to_string(),
about: cv.get_data(DataTypes::About).unwrap().to_string(),
status: cv.get_data(DataTypes::Status).unwrap().to_string(),
public_key: cv.get_data(DataTypes::PublicKey).unwrap().to_string(),
username: cv.get_data(DataTypes::username).unwrap().to_string(),
display: cv.get_data(DataTypes::display).unwrap().to_string(),
avatar: cv.get_data(DataTypes::avatar).unwrap().to_string(),
about: cv.get_data(DataTypes::about).unwrap().to_string(),
status: cv.get_data(DataTypes::status).unwrap().to_string(),
public_key: cv.get_data(DataTypes::public_key).unwrap().to_string(),
sub_level: cv
.get_data(DataTypes::SubLevel)
.get_data(DataTypes::sub_level)
.unwrap()
.to_string()
.parse::<i32>()
.unwrap_or(-1),
sub_end: cv
.get_data(DataTypes::SubEnd)
.get_data(DataTypes::sub_end)
.unwrap()
.to_string()
.parse::<i32>()
@ -112,7 +114,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().to_string()).ok()
Uuid::parse_str(&*cv.get_data(DataTypes::user_id).unwrap().to_string()).ok()
}
pub async fn complete_register(user_profile: &UserProfile, iota_id: &str) -> bool {
@ -139,10 +141,10 @@ impl AuthConnector {
let body = res.text().await.unwrap();
println!("Response body:\n{}", body);
CommunicationValue::from_json(&body).is_type(CommunicationType::Success)
CommunicationValue::from_json(&body).is_type(CommunicationType::success)
}
pub async fn migrate_user(user_profile: &mut UserProfile, iota_id: &str) -> bool {
pub async fn migrate_user(user_profile: &mut UserProfile) -> bool {
let url = format!(
"https://auth.tensamin.methanium.net/api/change/iota-id/{}",
user_profile.user_id
@ -150,7 +152,7 @@ impl AuthConnector {
let client = Self::client();
let mut payload = JsonValue::new_object();
payload["iota_id"] = iota_id.into();
payload["iota_id"] = JsonValue::String(CONFIG.lock().unwrap().get_iota_id().to_string());
payload["reset_token"] = user_profile.reset_token.clone().into();
payload["new_token"] = user_profile.randomize_reset_token().into();
@ -171,7 +173,7 @@ impl AuthConnector {
match resp.text().await {
Ok(text) => {
let cv = CommunicationValue::from_json(&text);
cv.comm_type == CommunicationType::Success
cv.comm_type == CommunicationType::success
}
Err(_) => false,
}

View file

@ -1,6 +1,6 @@
use axum::Json;
use json::number::Number;
use json::{JsonValue, object, parse, stringify};
use json::{Array, JsonValue, object, parse, stringify};
use std::any::Any;
use std::collections::HashMap;
use std::env::VarsOs;
@ -8,73 +8,73 @@ use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
#[derive(Eq, Hash, PartialEq, Debug)]
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
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,
error_type,
chat_partner_id,
iota_id,
user_id,
user_ids,
user_state,
user_states,
user_pings,
call_state,
screen_share,
private_key_hash,
accepted,
accepted_profiles,
denied_profiles,
message_content,
message_chunk,
send_time,
get_time,
get_variant,
shared_secret_own,
shared_secret_other,
shared_secret_sign,
shared_secret,
call_id,
call_name,
call_secret_sha,
call_secret,
shared_call_secret,
start_date,
end_date,
receiver_id,
sender_id,
signature,
signed,
message,
last_ping,
ping_iota,
ping_clients,
matches,
omikron,
loaded_messages,
message_amount,
position,
name,
path,
codec,
function,
payload,
result,
interactables,
want_to_watch,
watcher,
created_at,
username,
display,
avatar,
about,
status,
public_key,
sub_level,
sub_end,
community_address,
challenge,
community_title,
communities,
}
impl DataTypes {
@ -83,209 +83,179 @@ impl DataTypes {
let normalized = p0.to_lowercase().replace('_', "");
match normalized.as_str() {
"errortype" => DataTypes::ErrorType,
"chatpartnerid" => DataTypes::ChatPartnerId,
"iotaid" => DataTypes::IotaId,
"userid" => DataTypes::UserId,
"userids" => DataTypes::UserIds,
"userstate" => DataTypes::UserState,
"userstates" => DataTypes::UserStates,
"userpings" => DataTypes::UserPings,
"callstate" => DataTypes::CallState,
"screenshare" => DataTypes::ScreenShare,
"privatekeyhash" => DataTypes::PrivateKeyHash,
"accepted" => DataTypes::Accepted,
"acceptedprofiles" => DataTypes::AcceptedProfiles,
"deniedprofiles" => DataTypes::DeniedProfiles,
"messagecontent" => DataTypes::MessageContent,
"messagechunk" => DataTypes::MessageChunk,
"sendtime" => DataTypes::SendTime,
"gettime" => DataTypes::GetTime,
"getvariant" => DataTypes::GetVariant,
"sharedsecretown" => DataTypes::SharedSecretOwn,
"sharedsecretother" => DataTypes::SharedSecretOther,
"sharedsecretsign" => DataTypes::SharedSecretSign,
"sharedsecret" => DataTypes::SharedSecret,
"callid" => DataTypes::CallId,
"callname" => DataTypes::CallName,
"callsecretsha" => DataTypes::CallSecretSha,
"callsecret" => DataTypes::CallSecret,
"sharedcallsecret" => DataTypes::SharedCallSecret,
"startdate" => DataTypes::StartDate,
"enddate" => DataTypes::EndDate,
"receiverid" => DataTypes::ReceiverId,
"senderid" => DataTypes::SenderId,
"signature" => DataTypes::Signature,
"signed" => DataTypes::Signed,
"message" => DataTypes::Message,
"lastping" => DataTypes::LastPing,
"pingiota" => DataTypes::PingIota,
"pingclients" => DataTypes::PingClients,
"matches" => DataTypes::Matches,
"omikron" => DataTypes::Omikron,
"loadedmessages" => DataTypes::LoadedMessages,
"messageamount" => DataTypes::MessageAmount,
"position" => DataTypes::Position,
"name" => DataTypes::Name,
"path" => DataTypes::Path,
"codec" => DataTypes::Codec,
"function" => DataTypes::Function,
"payload" => DataTypes::Payload,
"result" => DataTypes::Result,
"interactables" => DataTypes::Interactables,
"wanttowatch" => DataTypes::WantToWatch,
"watcher" => DataTypes::Watcher,
"createdat" => DataTypes::CreatedAt,
"username" => DataTypes::Username,
"display" => DataTypes::Display,
"avatar" => DataTypes::Avatar,
"about" => DataTypes::About,
"status" => DataTypes::Status,
"publickey" => DataTypes::PublicKey,
"sublevel" => DataTypes::SubLevel,
"subend" => DataTypes::SubEnd,
"communityaddress" => DataTypes::CommunityAddress,
"challenge" => DataTypes::Challenge,
"communitytitle" => DataTypes::CommunityTitle,
"communities" => DataTypes::Communities,
_ => DataTypes::ErrorType, // fallback if unknown
"errortype" => DataTypes::error_type,
"chatpartnerid" => DataTypes::chat_partner_id,
"iotaid" => DataTypes::iota_id,
"userid" => DataTypes::user_id,
"userids" => DataTypes::user_ids,
"userstate" => DataTypes::user_state,
"userstates" => DataTypes::user_states,
"userpings" => DataTypes::user_pings,
"callstate" => DataTypes::call_state,
"screenshare" => DataTypes::screen_share,
"privatekeyhash" => DataTypes::private_key_hash,
"accepted" => DataTypes::accepted,
"acceptedprofiles" => DataTypes::accepted_profiles,
"deniedprofiles" => DataTypes::denied_profiles,
"messagecontent" => DataTypes::message_content,
"messagechunk" => DataTypes::message_chunk,
"sendtime" => DataTypes::send_time,
"gettime" => DataTypes::get_time,
"getvariant" => DataTypes::get_variant,
"sharedsecretown" => DataTypes::shared_secret_own,
"sharedsecretother" => DataTypes::shared_secret_other,
"sharedsecretsign" => DataTypes::shared_secret_sign,
"sharedsecret" => DataTypes::shared_secret,
"callid" => DataTypes::call_id,
"callname" => DataTypes::call_name,
"callsecretsha" => DataTypes::call_secret_sha,
"callsecret" => DataTypes::call_secret,
"sharedcallsecret" => DataTypes::shared_call_secret,
"startdate" => DataTypes::start_date,
"enddate" => DataTypes::end_date,
"receiverid" => DataTypes::receiver_id,
"senderid" => DataTypes::sender_id,
"signature" => DataTypes::signature,
"signed" => DataTypes::signed,
"message" => DataTypes::message,
"lastping" => DataTypes::last_ping,
"pingiota" => DataTypes::ping_iota,
"pingclients" => DataTypes::ping_clients,
"matches" => DataTypes::matches,
"omikron" => DataTypes::omikron,
"loadedmessages" => DataTypes::loaded_messages,
"messageamount" => DataTypes::message_amount,
"position" => DataTypes::position,
"name" => DataTypes::name,
"path" => DataTypes::path,
"codec" => DataTypes::codec,
"function" => DataTypes::function,
"payload" => DataTypes::payload,
"result" => DataTypes::result,
"interactables" => DataTypes::interactables,
"wanttowatch" => DataTypes::want_to_watch,
"watcher" => DataTypes::watcher,
"createdat" => DataTypes::created_at,
"username" => DataTypes::username,
"display" => DataTypes::display,
"avatar" => DataTypes::avatar,
"about" => DataTypes::about,
"status" => DataTypes::status,
"publickey" => DataTypes::public_key,
"sublevel" => DataTypes::sub_level,
"subend" => DataTypes::sub_end,
"communityaddress" => DataTypes::community_address,
"challenge" => DataTypes::challenge,
"communitytitle" => DataTypes::community_title,
"communities" => DataTypes::communities,
_ => DataTypes::error_type, // fallback if unknown
}
}
}
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Clone, Debug)]
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,
error,
success,
message,
message_live,
message_other_iota,
message_chunk,
message_get,
change_confirm,
confirm_receive,
confirm_read,
get_chats,
get_states,
add_community,
remove_community,
get_communities,
challenge,
challenge_response,
register,
register_response,
identification,
identification_response,
ping,
pong,
add_chat,
send_chat,
iota_connected,
iota_closed,
client_changed,
client_connected,
client_closed,
public_key,
private_key,
webrtc_sdp,
webrtc_ice,
start_stream,
end_stream,
watch_stream,
get_call,
new_call,
call_invite,
end_call,
function,
update,
}
impl CommunicationType {
pub fn parse(p0: String) -> CommunicationType {
let normalized = p0.to_lowercase().replace('_', "");
match normalized.as_str() {
"error" => CommunicationType::Error,
"success" => CommunicationType::Success,
"message" => CommunicationType::Message,
"messagelive" => CommunicationType::MessageLive,
"messageotheriota" => CommunicationType::MessageOtherIota,
"messagechunk" => CommunicationType::MessageChunk,
"messageget" => CommunicationType::MessageGet,
"changeconfirm" => CommunicationType::ChangeConfirm,
"confirmreceive" => CommunicationType::ConfirmReceive,
"confirmread" => CommunicationType::ConfirmRead,
"getchats" => CommunicationType::GetChats,
"getstates" => CommunicationType::GetStates,
"addcommunity" => CommunicationType::AddCommunity,
"removecommunity" => CommunicationType::RemoveCommunity,
"getcommunities" => CommunicationType::GetCommunities,
"challenge" => CommunicationType::Challenge,
"challengeresponse" => CommunicationType::ChallengeResponse,
"register" => CommunicationType::Register,
"registerresponse" => CommunicationType::RegisterResponse,
"identification" => CommunicationType::Identification,
"identificationresponse" => CommunicationType::IdentificationResponse,
"ping" => CommunicationType::Ping,
"pong" => CommunicationType::Pong,
"addchat" => CommunicationType::AddChat,
"sendchat" => CommunicationType::SendChat,
"iotaconnected" => CommunicationType::IotaConnected,
"iotaclosed" => CommunicationType::IotaClosed,
"clientchanged" => CommunicationType::ClientChanged,
"clientconnected" => CommunicationType::ClientConnected,
"clientclosed" => CommunicationType::ClientClosed,
"publickey" => CommunicationType::PublicKey,
"privatekey" => CommunicationType::PrivateKey,
"webrtcsdp" => CommunicationType::WebrtcSdp,
"webrtcice" => CommunicationType::WebrtcIce,
"startstream" => CommunicationType::StartStream,
"endstream" => CommunicationType::EndStream,
"watchstream" => CommunicationType::WatchStream,
"getcall" => CommunicationType::GetCall,
"newcall" => CommunicationType::NewCall,
"callinvite" => CommunicationType::CallInvite,
"endcall" => CommunicationType::EndCall,
"function" => CommunicationType::Function,
"update" => CommunicationType::Update,
_ => CommunicationType::Error, // fallback
"error" => CommunicationType::error,
"success" => CommunicationType::success,
"message" => CommunicationType::message,
"messagelive" => CommunicationType::message_live,
"messageotheriota" => CommunicationType::message_other_iota,
"messagechunk" => CommunicationType::message_chunk,
"messageget" => CommunicationType::message_get,
"changeconfirm" => CommunicationType::change_confirm,
"confirmreceive" => CommunicationType::confirm_receive,
"confirmread" => CommunicationType::confirm_read,
"getchats" => CommunicationType::get_chats,
"getstates" => CommunicationType::get_states,
"addcommunity" => CommunicationType::add_community,
"removecommunity" => CommunicationType::remove_community,
"getcommunities" => CommunicationType::get_communities,
"challenge" => CommunicationType::challenge,
"challengeresponse" => CommunicationType::challenge_response,
"register" => CommunicationType::register,
"registerresponse" => CommunicationType::register_response,
"identification" => CommunicationType::identification,
"identificationresponse" => CommunicationType::identification_response,
"ping" => CommunicationType::ping,
"pong" => CommunicationType::pong,
"addchat" => CommunicationType::add_chat,
"sendchat" => CommunicationType::send_chat,
"iotaconnected" => CommunicationType::iota_connected,
"iotaclosed" => CommunicationType::iota_closed,
"clientchanged" => CommunicationType::client_changed,
"clientconnected" => CommunicationType::client_connected,
"clientclosed" => CommunicationType::client_closed,
"publickey" => CommunicationType::public_key,
"privatekey" => CommunicationType::private_key,
"webrtcsdp" => CommunicationType::webrtc_sdp,
"webrtcice" => CommunicationType::webrtc_ice,
"startstream" => CommunicationType::start_stream,
"endstream" => CommunicationType::end_stream,
"watchstream" => CommunicationType::watch_stream,
"getcall" => CommunicationType::get_call,
"newcall" => CommunicationType::new_call,
"callinvite" => CommunicationType::call_invite,
"endcall" => CommunicationType::end_call,
"function" => CommunicationType::function,
"update" => CommunicationType::update,
_ => CommunicationType::error,
}
}
}
#[derive(Debug, Clone)]
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 as i32
}
}
}
#[derive(Debug)]
pub struct CommunicationValue {
pub id: Uuid,
pub comm_type: CommunicationType,
pub log_value: Option<LogValue>,
pub sender: Uuid,
pub receiver: Uuid,
pub data: HashMap<DataTypes, JsonValue>,
@ -296,7 +266,6 @@ impl CommunicationValue {
Self {
id: Uuid::new_v4(),
comm_type,
log_value: None,
sender: Uuid::new_v4(),
receiver: Uuid::new_v4(),
data: HashMap::new(),
@ -309,13 +278,6 @@ impl CommunicationValue {
pub fn get_id(&self) -> Uuid {
self.id.clone()
}
pub fn with_log(mut self, log: LogValue) -> Self {
self.log_value = Some(log);
self
}
pub fn get_log(&self) -> &Option<LogValue> {
&self.log_value
}
pub fn with_sender(mut self, sender: Uuid) -> Self {
self.sender = sender;
self
@ -342,6 +304,10 @@ impl CommunicationValue {
self.data.insert(key, value);
self
}
pub fn add_array(mut self, key: DataTypes, value: Array) -> Self {
self.data.insert(key, JsonValue::Array(value));
self
}
pub fn get_data(&mut self, key: DataTypes) -> Option<&JsonValue> {
self.data.get(&key)
}
@ -360,7 +326,6 @@ impl CommunicationValue {
type: format!("{:?}", self.comm_type),
sender: self.sender.to_string(),
receiver: self.receiver.to_string(),
log: self.log_value.as_ref().map(|l| l.clone().to_json()).unwrap_or(JsonValue::Null),
data: jdata
}
}
@ -379,14 +344,6 @@ impl CommunicationValue {
.and_then(|s| Uuid::parse_str(s).ok())
.unwrap_or(Uuid::new_v4());
let log_value = if parsed["log"].is_object() {
Some(LogValue::new(
parsed["log"]["message"].as_str().unwrap_or("").to_string(),
LogLevel::Normal,
))
} else {
None
};
let uuid = Uuid::parse_str(parsed["id"].as_str().unwrap_or("")).unwrap_or(Uuid::new_v4());
let mut data = HashMap::new();
if parsed["data"].is_object() {
@ -399,24 +356,23 @@ impl CommunicationValue {
Self {
id: uuid,
comm_type,
log_value,
sender,
receiver,
data,
}
}
pub fn ack_message(message_id: Uuid, sender: Uuid) -> CommunicationValue {
let mut cv = CommunicationValue::new(CommunicationType::Message).with_id(message_id);
let mut cv = CommunicationValue::new(CommunicationType::message).with_id(message_id);
if let s = sender {
cv = cv.add_data(DataTypes::SenderId, JsonValue::String(s.to_string()));
cv = cv.add_data(DataTypes::send_time, 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)
.get_data(DataTypes::receiver_id)
.unwrap()
.to_string(),
)
@ -428,15 +384,15 @@ impl CommunicationValue {
.unwrap()
.as_millis() as i64;
let cv = CommunicationValue::new(CommunicationType::MessageOtherIota)
let cv = CommunicationValue::new(CommunicationType::message_other_iota)
.with_id(original.get_id())
.with_receiver(receiver.unwrap())
.add_data(DataTypes::SendTime, JsonValue::String(now_ms.to_string()))
.add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string()))
.add_data(
DataTypes::MessageContent,
DataTypes::message_content,
JsonValue::String(
original
.get_data(DataTypes::MessageContent)
.get_data(DataTypes::message_content)
.unwrap()
.to_string(),
),
@ -444,6 +400,6 @@ impl CommunicationValue {
// include sender_id if the original had one
let sender = original.get_sender();
cv.add_data(DataTypes::SenderId, JsonValue::String(sender.to_string()))
cv.add_data(DataTypes::sender_id, JsonValue::String(sender.to_string()))
}
}

View file

@ -0,0 +1,405 @@
use crate::langu::language_manager::from_key;
use crate::{APP_STATE, omikron::ping_pong_task::PingPongTask};
use crossterm::{
ExecutableCommand,
terminal::{LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::widgets::canvas::{Canvas, Line};
use ratatui::{
Terminal,
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
style::Color,
widgets::{Block, Borders, List, ListItem, Paragraph},
};
use std::{collections::VecDeque, io::stdout, process::Command, thread, time::Duration};
use sysinfo::{RefreshKind, System};
const MAX_POINTS: usize = 1000;
const MAX_LOGS: usize = 100;
#[derive(Clone)]
pub struct AppState {
logs: VecDeque<String>,
cpu: Vec<(f64, f64)>,
ram: Vec<(f64, f64)>,
ping: Vec<(f64, f64)>,
net_up: Vec<(f64, f64)>,
net_down: Vec<(f64, f64)>,
sys_info: String,
}
impl AppState {
pub fn new() -> Self {
Self {
logs: VecDeque::new(),
cpu: Vec::new(),
ram: Vec::new(),
ping: Vec::new(),
net_up: Vec::new(),
net_down: Vec::new(),
sys_info: String::from("Loading..."),
}
}
pub fn push_log(&mut self, msg: String) {
if self.logs.len() >= MAX_LOGS {
self.logs.pop_front();
}
self.logs.push_back(msg);
}
pub fn push_cpu(&mut self, pt: (f64, f64)) {
self.cpu.push(pt);
if self.cpu.len() > MAX_POINTS {
self.cpu.remove(0);
}
}
pub fn push_ram(&mut self, pt: (f64, f64)) {
self.ram.push(pt);
if self.ram.len() > MAX_POINTS {
self.ram.remove(0);
}
}
pub fn push_ping_val(&mut self, pt: f64) {
self.ping.push((self.ping.len() as f64, pt));
if self.ping.len() > MAX_POINTS {
self.ping.remove(0);
}
}
pub fn push_net_up(&mut self, pt: (f64, f64)) {
self.net_up.push(pt);
if self.net_up.len() > MAX_POINTS {
self.net_up.remove(0);
}
}
pub fn push_net_down(&mut self, pt: (f64, f64)) {
self.net_down.push(pt);
if self.net_down.len() > MAX_POINTS {
self.net_down.remove(0);
}
}
}
pub fn log_message_trans(key: impl Into<String>) {
APP_STATE.lock().unwrap().push_log(from_key(&key.into()));
}
pub fn log_message(msg: impl Into<String>) {
APP_STATE.lock().unwrap().push_log(msg.into());
}
fn smooth_data(data: &[(f64, f64)], window_size: usize) -> Vec<(f64, f64)> {
if data.len() < window_size {
return data.to_vec();
}
let mut smoothed = Vec::with_capacity(data.len());
for i in 0..data.len() {
let start = if i + 1 >= window_size {
i + 1 - window_size
} else {
0
};
let window = &data[start..=i];
let avg = window.iter().map(|(_, y)| y).sum::<f64>() / window.len() as f64;
smoothed.push((data[i].0, avg));
}
smoothed
}
fn downsample_to_fit_width(data: &[(f64, f64)], width: u16) -> Vec<(f64, f64)> {
let width_usize = (width as usize) * 2;
let len = data.len();
if len == 0 {
return Vec::new();
}
let slice = if len <= width_usize {
data.to_vec()
} else {
data[len - width_usize..].to_vec()
};
slice
}
fn measure_ping_ms(host: &str) -> Option<f64> {
// adapt for Windows
let output = Command::new("ping")
.arg("-c")
.arg("1")
.arg("-W")
.arg("1")
.arg(host)
.output()
.ok()?;
let out = String::from_utf8_lossy(&output.stdout);
for line in out.lines() {
if line.contains("time=") {
if let Some(idx) = line.find("time=") {
let substr = &line[idx + 5..];
if let Some(end) = substr.find(" ms") {
let num = &substr[..end];
if let Ok(f) = num.parse::<f64>() {
return Some(f);
}
}
}
}
}
None
}
pub fn setup() {
enable_raw_mode().unwrap();
// Start a background thread to sample metrics
thread::spawn(move || {
let mut sys = System::new_with_specifics(RefreshKind::new());
let mut last_total_received = 0u64;
let mut last_total_transmitted = 0u64;
let mut counter = 0.0;
loop {
sys.refresh_all();
let mut tcpu = 0;
for cpu in sys.cpus() {
tcpu += cpu.cpu_usage() as i64;
tcpu /= 2;
}
let ram = (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0;
let mut total_received = 0u64;
let mut total_transmitted = 0u64;
let delta_received = if last_total_received == 0 {
0
} else {
total_received.saturating_sub(last_total_received)
};
let delta_transmitted = if last_total_transmitted == 0 {
0
} else {
total_transmitted.saturating_sub(last_total_transmitted)
};
last_total_received = total_received;
last_total_transmitted = total_transmitted;
let net_down = delta_received as f64;
let net_up = delta_transmitted as f64;
{
let mut st = APP_STATE.lock().unwrap();
st.push_cpu((counter, tcpu as f64));
st.push_ram((counter, ram));
st.push_net_down((counter, net_down));
st.push_net_up((counter, net_up));
st.sys_info = format!(
"CPU: {:.1}% RAM: {:.1}%\nNetDown: {} NetUp: {}",
tcpu, ram, delta_received, delta_transmitted
);
}
counter += 1.0;
thread::sleep(Duration::from_millis(250));
}
});
// UI rendering loop in a separate thread
thread::spawn(move || {
let stdout = stdout();
let mut terminal = Terminal::new(CrosstermBackend::new(stdout)).unwrap();
enable_raw_mode().unwrap();
loop {
terminal
.draw(|f| {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
.split(size);
let left = chunks[0];
let right = chunks[1];
{
let st = APP_STATE.lock().unwrap();
let items: Vec<ListItem> = st
.logs
.iter()
.rev()
.map(|s| ListItem::new(s.clone()))
.collect();
let list = List::new(items)
.block(Block::default().title("Logs").borders(Borders::ALL));
f.render_widget(list, left);
}
let right_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(5), Constraint::Min(0)])
.split(right);
{
let st = APP_STATE.lock().unwrap();
let header = Paragraph::new(st.sys_info.clone())
.block(Block::default().title("System Info").borders(Borders::ALL));
f.render_widget(header, right_chunks[0]);
}
let grid_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(right_chunks[1]);
let left_column = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(grid_chunks[0]);
let right_column = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(grid_chunks[1]);
let st = APP_STATE.lock().unwrap();
let w = grid_chunks[0].width.saturating_sub(2);
let ping_ds = downsample_to_fit_width(&smooth_data(&st.ping, 3), w);
let cpu_ds = downsample_to_fit_width(&smooth_data(&st.cpu, 3), w);
let ram_ds = downsample_to_fit_width(&smooth_data(&st.ram, 3), w);
let down_ds = downsample_to_fit_width(&st.net_down, w);
let up_ds = downsample_to_fit_width(&st.net_up, w);
// ---- Render CPU ----
{
let min_x = cpu_ds.first().map(|(x, _)| *x).unwrap_or(0.0);
let max_x = cpu_ds.last().map(|(x, _)| *x).unwrap_or(100.0);
let canvas = Canvas::default()
.block(Block::default().title("CPU %").borders(Borders::ALL))
.x_bounds([min_x, max_x])
.y_bounds([0.0, 100.0])
.paint(|ctx| {
for (x, y) in &cpu_ds {
ctx.draw(&Line {
x1: *x,
y1: 0.0,
x2: *x,
y2: *y,
color: Color::Cyan,
});
}
});
f.render_widget(canvas, left_column[0]);
}
// ---- Render RAM ----
{
let min_x = ram_ds.first().map(|(x, _)| *x).unwrap_or(0.0);
let max_x = ram_ds.last().map(|(x, _)| *x).unwrap_or(100.0);
let canvas = Canvas::default()
.block(Block::default().title("RAM %").borders(Borders::ALL))
.x_bounds([min_x, max_x])
.y_bounds([0.0, 100.0])
.paint(|ctx| {
for (x, y) in &ram_ds {
ctx.draw(&Line {
x1: *x,
y1: 0.0,
x2: *x,
y2: *y,
color: Color::Magenta,
});
}
});
f.render_widget(canvas, left_column[1]);
}
// ---- Render Ping ----
{
let min_x = ping_ds.first().map(|(x, _)| *x).unwrap_or(0.0);
let max_x = ping_ds.last().map(|(x, _)| *x).unwrap_or(100.0);
let max_y = ping_ds.iter().map(|(_, y)| *y).fold(1.0, f64::max);
let canvas = Canvas::default()
.block(
Block::default()
.title(format!("Ping {}(ms)", max_y))
.borders(Borders::ALL),
)
.x_bounds([min_x, max_x])
.y_bounds([0.0, max_y])
.paint(|ctx| {
for (x, y) in &ping_ds {
ctx.draw(&Line {
x1: *x,
y1: 0.0,
x2: *x,
y2: *y,
color: Color::Yellow,
});
}
});
f.render_widget(canvas, right_column[0]);
}
// ---- Render Network ----
{
let min_x = up_ds.first().map(|(x, _)| *x).unwrap_or(0.0);
let max_x = up_ds.last().map(|(x, _)| *x).unwrap_or(100.0);
let mut max_sum: f64 = 1.0;
for ((_, up), (_, down)) in up_ds.iter().zip(down_ds.iter()) {
max_sum = max_sum.max(up + down);
}
let canvas = Canvas::default()
.block(
Block::default()
.title("Network Up/Down")
.borders(Borders::ALL),
)
.x_bounds([min_x, max_x])
.y_bounds([0.0, max_sum])
.paint(|ctx| {
for (x, dval) in &down_ds {
ctx.draw(&Line {
x1: *x,
y1: 0.0,
x2: *x,
y2: *dval,
color: Color::Red,
});
}
for (x, uval) in &up_ds {
let dval = down_ds
.iter()
.find(|(xx, _)| (*xx - *x).abs() < f64::EPSILON)
.map(|(_, y)| *y)
.unwrap_or(0.0);
ctx.draw(&Line {
x1: *x,
y1: dval,
x2: *x,
y2: dval + *uval,
color: Color::Green,
});
}
});
f.render_widget(canvas, right_column[1]);
}
})
.unwrap();
thread::sleep(Duration::from_millis(100));
}
});
// Clean up terminal before exit
disable_raw_mode().unwrap();
stdout().execute(LeaveAlternateScreen).unwrap();
}

View file

@ -1 +1,2 @@
pub mod log_panel;
pub mod ratatui_interface;

View file

@ -9,19 +9,15 @@ pub fn create_languages() {
let mut debug_messages = JsonValue::new_object();
// FRONTEND
frontend_messages.insert(
"USER_CONTEXT_GET_CONVERSATIONS",
"User {} is loading conversations",
);
frontend_messages.insert(
"USER_CONTEXT_GET_COMMUNITIES",
"User {} is loading communities",
);
frontend_messages.insert("ADD_CONVERSATION", "User {} added {}");
frontend_messages.insert("GET_CHATS", "User {} is loading conversations");
frontend_messages.insert("MESSAGE_GET", "User {} is loading messages");
frontend_messages.insert("get_communities", "User {} is loading communities");
frontend_messages.insert("client_connected", "Client {} connected");
frontend_messages.insert("add_conversation", "User {} added {}");
// OMIKRON
omikron_messages.insert(
"IdentificationResponse",
"identification_response",
"IOTA identified on Omikron, {} users!",
);
@ -32,6 +28,7 @@ pub fn create_languages() {
general_texts.insert("IOTA_ID", "IOTA ID: {}-####-####-####-############");
general_texts.insert("USER_ID", "USER ID: {}");
general_texts.insert("USER_IDS", "USER IDS: {}");
general_texts.insert("SETUP_COMPLETED", "Launched");
// DEBUG
debug_messages.insert("", "");
@ -55,4 +52,9 @@ pub fn create_languages() {
"debug.json",
&debug_messages.to_string(),
);
save_file(
"languages/en_INT",
"general.json",
&general_texts.to_string(),
);
}

View file

@ -32,9 +32,7 @@ pub fn from_key(key: &str) -> String {
LANGUAGE_PACK
.lock()
.unwrap()
.language
.get(key)
.unwrap_or(&String::new())
.get_translation(key)
.to_string()
}
@ -90,9 +88,18 @@ impl LanguagePack {
self.language
.insert(key.to_string(), value.as_str().unwrap().to_string());
}
}
pub fn get_translation(&self, key: &str) -> &String {
self.language.get(key).unwrap()
let general_messages = file_util::load_file(&path, "general.json");
let general_messages = parse(&general_messages).unwrap();
for (key, value) in general_messages.entries() {
self.language
.insert(key.to_string(), value.as_str().unwrap().to_string());
}
}
pub fn get_translation(&self, key: &str) -> String {
match self.language.get(key) {
Some(v) if !v.is_empty() => v.clone(),
_ => key.to_uppercase(),
}
}
}

View file

@ -1,41 +1,30 @@
use base64::Engine;
use base64::engine::general_purpose;
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use futures_util::SinkExt;
use json::JsonValue::String;
use json::{self, JsonValue};
use rand::Rng;
use rand_core::OsRng;
use rand_core::RngCore;
use reqwest::header::PUBLIC_KEY_PINS_REPORT_ONLY;
use sha2::{Digest, Sha256};
use json::{self};
use std::sync::Arc;
use std::sync::LazyLock;
use std::sync::Mutex;
use uuid::Uuid;
use x448::{PublicKey, Secret};
mod auth;
mod data;
mod eula;
mod gui;
mod langu;
mod omikron;
mod users;
mod util;
mod gui {
pub mod ratatui_interface;
}
mod auth;
mod langu;
use crate::auth::auth_connector::AuthConnector;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::eula::*;
use crate::gui::log_panel;
use crate::gui::log_panel::{AppState, log_message, log_message_trans};
use crate::langu::language_creator;
use crate::langu::language_manager;
use crate::omikron::omikron_connection::OmikronConnection;
use crate::omikron::ping_pong_task::PingPongTask;
use crate::users::user_manager::UserManager;
use crate::users::user_profile::UserProfile;
use crate::users::user_profile_full::UserProfileFull;
use crate::util::config_util::{self, CONFIG, ConfigUtil};
use crate::util::file_util;
use crate::util::config_util::CONFIG;
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
#[tokio::main]
async fn main() {
@ -46,8 +35,8 @@ async fn main() {
//}
// UI
gui::ratatui_interface::launch(false);
log_panel::setup();
// LANGUAGE PACK
language_creator::create_languages();
@ -68,9 +57,10 @@ async fn main() {
if !sb.is_empty() {
sb.remove(0);
sb = sb + ",";
}
println!(
"IOTA ID: {}-####-####-####-############",
log_message(format!(
"IOTA ID: {}-####-####-####-############",
CONFIG
.lock()
.unwrap()
@ -79,8 +69,8 @@ async fn main() {
.split("-")
.next()
.unwrap()
);
println!("User ID: {}", sb);
));
log_message(format!("User IDS: {}", sb));
// IDENTIFICATION ON OMIKRON
let omikron: OmikronConnection = OmikronConnection::new();
@ -88,10 +78,10 @@ async fn main() {
let _ping_pong_task = PingPongTask::new(Arc::new(OmikronConnection::new()));
omikron
.send_message(
CommunicationValue::new(CommunicationType::Identification)
.add_data(DataTypes::UserIds, String(sb.to_string()))
CommunicationValue::new(CommunicationType::identification)
.add_data(DataTypes::user_ids, String(sb.to_string()))
.add_data(
DataTypes::IotaId,
DataTypes::iota_id,
String(CONFIG.lock().unwrap().get_iota_id().to_string()),
)
.to_json()
@ -100,6 +90,7 @@ async fn main() {
.to_string(),
)
.await;
log_message_trans("SETUP_COMPLETED");
loop {}
}

View file

@ -1,5 +1,8 @@
use crate::APP_STATE;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::omikron::ping_pong_task::PingPongTask;
use crate::gui::log_panel::log_message;
use crate::gui::log_panel::log_message_trans;
use crate::omikron::ping_pong_task::{self, PingPongTask};
use crate::users::contact::Contact;
use crate::users::user_community_util::UserCommunityUtil;
use crate::util::chat_files::ChatFiles;
@ -8,6 +11,7 @@ use futures_util::{SinkExt, StreamExt};
use json::JsonValue;
use json::number::Number;
use std::collections::HashMap;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
@ -31,8 +35,9 @@ pub struct OmikronConnection {
>,
>,
>,
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send>>>>, // waiting for responses
pingpong: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>, // ping-pong handler
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync>>>>, // waiting for responses
pingpong: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>, // ping-pong handler
ping_pong_task: Arc<Mutex<Option<Arc<PingPongTask>>>>,
}
impl OmikronConnection {
@ -41,18 +46,27 @@ impl OmikronConnection {
writer: Arc::new(Mutex::new(None)),
waiting: Arc::new(Mutex::new(HashMap::new())),
pingpong: Arc::new(Mutex::new(None)),
ping_pong_task: Arc::new(Mutex::new(None)),
}
}
/// Connect loop with retry
pub async fn connect<'a>(&'a self) {
pub async fn connect(&self) {
loop {
match connect_async("wss://tensamin.methanium.net/ws/iota/").await {
Ok((ws_stream, _)) => {
let (write_half, read_half) = ws_stream.split();
*self.writer.lock().await = Some(write_half);
self.spawn_listener(read_half);
self.start_ping_pong_task().await;
self.spawn_listener(read_half).await;
let ppt = PingPongTask::new(Arc::new(self.clone()));
*self.ping_pong_task.lock().await = Some(Arc::new(ppt.clone()));
tokio::spawn(async move {
loop {
ppt.send_ping();
sleep(Duration::from_secs(5)).await;
}
});
break;
}
Err(e) => {
@ -61,18 +75,6 @@ impl OmikronConnection {
}
}
}
// Start the PingPongTask
pub async fn start_ping_pong_task(&self) {
let ping_pong_task = PingPongTask::new(Arc::new(self.clone()));
// Store the task handle in `pingpong` so we can manage it
let mut pingpong_handle = self.pingpong.lock().await;
*pingpong_handle = Some(tokio::spawn(async move {
ping_pong_task.run_ping_loop();
}));
}
pub async fn send_message(&self, msg: String) {
Self::send_message_static(&self.writer, msg).await;
}
@ -87,7 +89,7 @@ impl OmikronConnection {
}
/// Listener for all incoming messages
fn spawn_listener(
async fn spawn_listener(
&self,
mut read_half: futures_util::stream::SplitStream<
WebSocketStream<MaybeTlsStream<TcpStream>>,
@ -95,57 +97,60 @@ impl OmikronConnection {
) {
let waiting = self.waiting.clone();
let writer = self.writer.clone();
let sel = self.clone();
tokio::spawn(async move {
while let Some(msg) = read_half.next().await {
match msg {
Ok(Message::Close(Some(frame))) => {
println!("[Omikron] Closed: {:?}", frame);
log_message(format!("[Omikron] Closed: {:?}", frame));
break;
}
Ok(Message::Text(text)) => {
let mut cv = CommunicationValue::from_json(&text); // needs CommunicationValue parser
if cv.is_type(CommunicationType::Pong) {
// handle pingpong reset here
continue;
if cv.is_type(CommunicationType::pong) {
sel.ping_pong_task
.lock()
.await
.as_mut()
.unwrap()
.handle_pong(&cv, true);
return;
}
// ************************************************ //
// Direct messages //
// ************************************************ //
println!("[Omikron] Received message: {:?}", cv);
if cv.is_type(CommunicationType::MessageOtherIota) {
log_message_trans(format!("{:?}", &cv.comm_type));
if cv.is_type(CommunicationType::message_other_iota) {
let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver();
ChatFiles::add_message(
cv.get_data(DataTypes::SendTime)
cv.get_data(DataTypes::send_time)
.unwrap()
.as_i64()
.unwrap_or(-1),
false,
*receiver_id,
*sender_id,
cv.get_data(DataTypes::MessageContent)
cv.get_data(DataTypes::message_content)
.unwrap()
.as_str()
.unwrap(),
false,
false,
0,
);
let response = CommunicationValue::new(CommunicationType::MessageLive)
let response = CommunicationValue::new(CommunicationType::message_live)
.with_id(cv.get_id())
.with_receiver(cv.get_receiver())
.add_data(
DataTypes::SendTime,
cv.get_data(DataTypes::SendTime).unwrap().clone(),
DataTypes::send_time,
cv.get_data(DataTypes::send_time).unwrap().clone(),
)
.add_data(
DataTypes::Message,
cv.get_data(DataTypes::MessageContent).unwrap().clone(),
DataTypes::message,
cv.get_data(DataTypes::message_content).unwrap().clone(),
)
.add_data(
DataTypes::SenderId,
DataTypes::sender_id,
JsonValue::String(cv.get_sender().clone().to_string()),
);
Self::send_message_static(
@ -156,26 +161,24 @@ impl OmikronConnection {
continue;
}
if cv.is_type(CommunicationType::Message) {
/*
"user": {
"avatar": true,
"display": true,
"timestamp": true
},
"body": {
"tint": "<hex color>",
"content": "<markdown (encrypted)>",
"files": [
{
"name": "<cool name>",
"id": "<uuid>",
"type": "[ image | image_top_right | file ]"
}
]
}
if cv.is_type(CommunicationType::message) {
/* DATA CONTAINER:
"sent_by_self": true,
"timestamp": unixTimestamp,
"files": [ // wenn keine files dann weglassen
{
"name": "<name>",
"id": "<uuid>",
"type": "[ image | image_top_right | file ]"
}
],
"content": "<enc markdown>"
*/
let my_id = cv.get_sender();
let other_id = Uuid::from_str(
&*cv.get_data(DataTypes::receiver_id).unwrap().to_string(),
)
.unwrap();
ChatFiles::add_message(
SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -183,20 +186,12 @@ impl OmikronConnection {
.as_millis() as i64,
true,
my_id,
Uuid::from_str(
&*cv.get_data(DataTypes::ReceiverId).unwrap().to_string(),
)
.unwrap(),
&*cv.get_data(DataTypes::MessageContent).unwrap().to_string(),
false,
false,
0,
other_id,
&*cv.get_data(DataTypes::message_content).unwrap().to_string(),
);
// ack
let ack = CommunicationValue::ack_message(cv.get_id(), my_id);
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.clone(),
@ -206,55 +201,52 @@ impl OmikronConnection {
continue;
}
if cv.is_type(CommunicationType::MessageGet) {
if cv.is_type(CommunicationType::message_get) {
let my_id = cv.get_sender();
let partner_id = Uuid::from_str(
&*cv.get_data(DataTypes::ChatPartnerId).unwrap().to_string(),
&*cv.get_data(DataTypes::user_id).unwrap().to_string(),
)
.unwrap();
let offset = cv
.get_data(DataTypes::LoadedMessages)
.get_data(DataTypes::loaded_messages)
.unwrap_or(&JsonValue::Null)
.to_string()
.parse::<i64>()
.unwrap_or(0);
let amount = cv
.get_data(DataTypes::MessageAmount)
.get_data(DataTypes::message_amount)
.unwrap_or(&JsonValue::Null)
.to_string()
.parse::<i64>()
.unwrap_or(0);
let messages =
ChatFiles::get_messages(my_id, partner_id, offset, amount); // needs ChatFiles
let mut resp = CommunicationValue::new(CommunicationType::MessageChunk)
ChatFiles::get_messages(my_id, partner_id, offset, amount);
let resp = CommunicationValue::new(CommunicationType::message_chunk)
.with_id(cv.get_id())
.with_receiver(my_id);
if !messages.is_empty() {
resp = resp.add_data(DataTypes::MessageChunk, messages);
}
.with_receiver(my_id)
.add_data(DataTypes::message_chunk, messages);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
.await;
continue;
}
if cv.is_type(CommunicationType::GetChats) {
if cv.is_type(CommunicationType::get_chats) {
let user_id = cv.get_sender();
let users = get_users(user_id); // needs ChatsUtil
let resp = CommunicationValue::new(CommunicationType::GetChats)
let users = get_users(user_id);
let resp = CommunicationValue::new(CommunicationType::get_chats)
.with_id(cv.get_id())
.with_receiver(user_id)
.add_data(DataTypes::UserIds, users);
println!("ALARM: {}", resp.to_json().to_string());
.add_data(DataTypes::user_ids, users);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
.await;
continue;
}
if cv.is_type(CommunicationType::AddChat) {
if cv.is_type(CommunicationType::add_chat) {
let user_id = cv.get_sender();
let other_id = Uuid::from_str(
&*cv.get_data(DataTypes::UserId).unwrap().to_string(),
&*cv.get_data(DataTypes::user_id).unwrap().to_string(),
)
.unwrap();
let mut contact =
@ -266,7 +258,7 @@ impl OmikronConnection {
.as_millis() as i64,
);
mod_user(user_id, &contact);
let resp = CommunicationValue::new(CommunicationType::AddChat)
let resp = CommunicationValue::new(CommunicationType::add_chat)
.with_id(cv.get_id())
.with_receiver(user_id);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
@ -274,16 +266,16 @@ impl OmikronConnection {
continue;
}
if cv.is_type(CommunicationType::AddCommunity) {
if cv.is_type(CommunicationType::add_community) {
UserCommunityUtil::add_community(
cv.get_sender(),
cv.get_data(DataTypes::CommunityAddress)
cv.get_data(DataTypes::community_address)
.unwrap()
.to_string(),
cv.get_data(DataTypes::CommunityTitle).unwrap().to_string(),
cv.get_data(DataTypes::Position).unwrap().to_string(),
cv.get_data(DataTypes::community_title).unwrap().to_string(),
cv.get_data(DataTypes::position).unwrap().to_string(),
);
let resp = CommunicationValue::new(CommunicationType::AddCommunity)
let resp = CommunicationValue::new(CommunicationType::add_community)
.with_id(cv.get_id())
.with_receiver(cv.get_sender());
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
@ -291,12 +283,12 @@ impl OmikronConnection {
continue;
}
if cv.is_type(CommunicationType::GetCommunities) {
let resp = CommunicationValue::new(CommunicationType::GetCommunities)
if cv.is_type(CommunicationType::get_communities) {
let resp = CommunicationValue::new(CommunicationType::get_communities)
.with_id(cv.get_id())
.with_receiver(cv.get_sender())
.add_data(
DataTypes::Communities,
.add_array(
DataTypes::communities,
UserCommunityUtil::get_communities(cv.get_sender()),
); // needs UserCommunityUtil
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
@ -304,14 +296,14 @@ impl OmikronConnection {
continue;
}
if cv.is_type(CommunicationType::RemoveCommunity) {
if cv.is_type(CommunicationType::remove_community) {
UserCommunityUtil::remove_community(
cv.get_sender(),
cv.get_data(DataTypes::CommunityAddress)
cv.get_data(DataTypes::community_address)
.unwrap()
.to_string(),
); // needs UserCommunityUtil
let resp = CommunicationValue::new(CommunicationType::RemoveCommunity)
let resp = CommunicationValue::new(CommunicationType::remove_community)
.with_id(cv.get_id())
.with_receiver(cv.get_sender());
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
@ -320,7 +312,7 @@ impl OmikronConnection {
}
}
Err(e) => {
eprintln!("[Omikron] Error: {}", e);
log_message(format!("[Omikron] Error: {}", e));
break;
}
_ => {}
@ -351,23 +343,11 @@ impl OmikronConnection {
}
}
pub fn on_answer<F>(&self, message_id: Uuid, callback: F)
where
F: Fn(CommunicationValue) + Send + 'static,
{
tokio::spawn({
let waiting = self.waiting.clone();
async move {
waiting.lock().await.insert(message_id, Box::new(callback));
}
});
}
pub async fn send_ping_message(&self, uuid: Uuid) {
// Send the ping message over the connection
let ping_message = CommunicationValue::new(CommunicationType::Ping)
let ping_message = CommunicationValue::new(CommunicationType::ping)
.with_id(uuid)
.add_data_num(DataTypes::LastPing, Number::from(2))
.add_data_num(DataTypes::last_ping, Number::from(2))
.to_json()
.to_string();
self.send_message(ping_message).await;

View file

@ -1,7 +1,10 @@
use crate::APP_STATE;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::log_panel::AppState;
use crate::omikron::omikron_connection::OmikronConnection;
use color_eyre::owo_colors::OwoColorize;
use futures_util::{SinkExt, StreamExt};
use std::arch::x86_64::_SIDD_MASKED_NEGATIVE_POLARITY;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
@ -10,12 +13,22 @@ use uuid::Uuid;
#[derive(Clone)]
pub struct PingPongTask {
pub parent: Arc<OmikronConnection>, // assuming OmikronConnection is your connection type
pub parent: Arc<OmikronConnection>,
pub message_send_times: Arc<Mutex<HashMap<Uuid, Instant>>>,
pub no_ping_in: Arc<Mutex<i32>>,
pub last_ping: Arc<Mutex<Option<u64>>>,
}
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn check_omikron_connection_send_sync() {
assert_send_sync::<OmikronConnection>();
}
#[test]
fn check_pingpong_task_send_sync() {
assert_send_sync::<PingPongTask>();
}
impl PingPongTask {
pub fn new(parent: Arc<OmikronConnection>) -> Self {
let message_send_times = Arc::new(Mutex::new(HashMap::new()));
@ -29,50 +42,32 @@ impl PingPongTask {
last_ping: last_ping.clone(),
};
// Spawn the periodic ping task
tokio::spawn({
let task = task.clone(); // Clone the task (Arc) so that it lives long enough for the async task
async move {
task.run_ping_loop().await;
}
});
task
}
pub async fn run_ping_loop(&self) {
loop {
sleep(Duration::from_secs(5)).await;
self.send_ping().await;
}
}
pub async fn send_ping(&self) {
let uuid = Uuid::new_v4();
let send_time = Instant::now();
{
let mut message_send_times = self.message_send_times.lock().await;
pub fn send_ping(&self) {
let sel = self.clone();
tokio::spawn(async move {
let uuid = Uuid::new_v4();
let send_time = Instant::now();
let mut message_send_times = sel.message_send_times.lock().await;
message_send_times.insert(uuid, send_time);
}
let no_ping_in = {
let no_ping_in = self.no_ping_in.lock().await;
*no_ping_in
};
let no_ping_in_val = {
let no_ping_in = sel.no_ping_in.lock().await;
*no_ping_in
};
if no_ping_in != -1 {
// Connection slow or disconnected
self.handle_slow_connection(no_ping_in).await;
} else {
// Connection is fine
self.parent.send_ping_message(uuid).await;
}
if no_ping_in_val != -1 {
sel.handle_slow_connection(no_ping_in_val).await;
} else {
sel.parent.send_ping_message(uuid).await;
}
});
}
pub async fn handle_slow_connection(&self, no_ping_in: i32) {
if no_ping_in > 8 {
// Attempt reconnection if ping times out
self.parent.reconnect().await;
self.reconnect().await;
}
@ -80,37 +75,39 @@ impl PingPongTask {
pub async fn reconnect(&self) {
let mut no_ping_in = self.no_ping_in.lock().await;
*no_ping_in = -1; // Reset slow count
*no_ping_in = -1;
}
pub async fn handle_pong(&self, cv: &CommunicationValue) {
let send_time = {
let message_send_times = self.message_send_times.lock().await;
message_send_times.get(&cv.get_id()).cloned()
};
pub fn handle_pong(&self, cv: &CommunicationValue, log: bool) {
let sel = self.clone();
let cv = cv.clone();
tokio::spawn(async move {
let send_time = {
let message_send_times = sel.message_send_times.lock().await;
message_send_times.get(&cv.get_id()).cloned()
};
if let Some(send_time) = send_time {
let receive_time = Instant::now();
let ping = receive_time.duration_since(send_time).as_millis() as u64;
if let Some(send_time) = send_time {
let receive_time = Instant::now();
let ping = receive_time.duration_since(send_time).as_millis() as u64;
{
let mut last_ping = self.last_ping.lock().await;
let mut last_ping = sel.last_ping.lock().await;
*last_ping = Some(ping);
}
{
let mut no_ping_in = self.no_ping_in.lock().await;
let mut no_ping_in = sel.no_ping_in.lock().await;
*no_ping_in = -1;
let mut message_send_times = sel.message_send_times.lock().await;
message_send_times.remove(&cv.get_id());
if log {
APP_STATE.lock().unwrap().push_log("12.0".to_string());
APP_STATE.lock().unwrap().push_ping_val(12.0);
}
}
}
});
}
pub async fn cancel(&self) {
// Cancel or stop the task
let mut no_ping_in = self.no_ping_in.lock().await;
*no_ping_in = -1; // Reset the counter
// Example of stopping the ping task gracefully
// If using a task join handle or similar
*no_ping_in = -1;
}
}

View file

@ -1,4 +1,5 @@
use json::{self, JsonValue};
use crate::util::file_util::save_file;
use json::{self, Array, JsonValue};
use std::fs;
use std::path::Path;
use uuid::Uuid;
@ -7,8 +8,8 @@ pub struct UserCommunityUtil;
impl UserCommunityUtil {
pub fn add_community(storage_owner: Uuid, address: String, title: String, position: String) {
let path = format!("users/{}/communities.json", storage_owner);
let mut communities: JsonValue = Self::load_array(&path);
let file_path = format!("users/{}/", storage_owner);
let mut communities = Self::load_array(&file_path);
let mut community = JsonValue::new_object();
community["title"] = JsonValue::String(title);
@ -16,44 +17,52 @@ impl UserCommunityUtil {
community["position"] = JsonValue::String(position);
communities.push(community);
Self::save_array(&path, communities);
save_file(
&file_path,
"communities.json",
&JsonValue::Array(communities).to_string(),
);
}
pub fn remove_community(storage_owner: Uuid, community_address: String) {
let path = format!("users/{}/communities.json", storage_owner);
let mut communities = Self::load_array(&path);
let file_path = format!("users/{}/", storage_owner);
let communities = Self::load_array(&file_path);
let mut new_array = JsonValue::new_array();
for entry in communities.members() {
if entry["address"].as_str() != Some(&community_address) {
new_array.push(entry.clone()).unwrap();
let filtered: Array = communities
.iter()
.filter(|entry| entry["address"].as_str() != Some(&community_address))
.cloned()
.collect();
save_file(
&file_path,
"communities.json",
&JsonValue::Array(filtered).to_string(),
);
}
pub fn get_communities(storage_owner: Uuid) -> Array {
let file_path = format!("users/{}/communities.json", storage_owner);
Self::load_array(&file_path)
}
fn load_array(file_path: &str) -> Array {
if !Path::new(file_path).exists() {
return Array::new();
}
match fs::read_to_string(file_path) {
Ok(content) => {
let parsed = json::parse(&content);
match parsed {
Ok(JsonValue::Array(arr)) => arr,
_ => Array::new(),
}
}
Err(err) => {
eprintln!("Failed to read file {}: {}", file_path, err);
Array::new()
}
}
Self::save_array(&path, new_array);
}
pub fn get_communities(storage_owner: Uuid) -> JsonValue {
let path = format!("users/{}/communities.json", storage_owner);
Self::load_array(&path)
}
fn load_array(path: &str) -> JsonValue {
if !Path::new(path).exists() {
return JsonValue::new_object();
}
match fs::read_to_string(path) {
Ok(content) => json::parse(&content).unwrap_or_else(|_| JsonValue::new_object()),
Err(_) => JsonValue::new_object(),
}
}
fn save_array(path: &str, arr: JsonValue) {
if let Some(parent) = Path::new(path).parent() {
let _ = fs::create_dir_all(parent);
}
let _ = fs::write(path, arr.pretty(3));
}
}

View file

@ -1,4 +1,5 @@
use crate::auth::auth_connector::AuthConnector;
use crate::gui::log_panel::log_message;
use crate::users::user_manager::UserManager;
use base64::{Engine as _, engine::general_purpose};
use json::{JsonValue, object, stringify};
@ -72,15 +73,14 @@ impl UserProfile {
reset_token,
);
// Migration hook (stubbed, since AuthConnector isnt implemented here)
if j.has_key("migrate")
|| j.has_key("migrating")
|| j.has_key("changing")
|| j.has_key("move")
|| j.has_key("moving")
{
if AuthConnector::migrate_user(&mut up, stringify!("{}", Uuid::new_v4())).await {
println!("[INFO] Migration triggered for {}", up.username);
if AuthConnector::migrate_user(&mut up).await {
log_message(format!("[INFO] Migration triggered for {}", up.username));
UserManager::set_unique(true);
}
}
@ -93,7 +93,6 @@ impl UserProfile {
OsRng.fill(bytes.as_mut());
let new_token = general_purpose::STANDARD.encode(&bytes);
self.reset_token = new_token.clone();
UserManager::save_users().ok();
new_token
}

View file

@ -37,11 +37,11 @@ impl ChatFiles {
}
}
fn save_file(dir: &str, file_name: &str, content: &str) -> std::io::Result<()> {
fs::create_dir_all(dir)?;
fn save_file(dir: &str, file_name: &str, content: &str) {
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())
let mut file = File::create(path).unwrap();
file.write_all(content.as_bytes());
}
pub fn add_message(
send_time: i64,
@ -49,10 +49,7 @@ impl ChatFiles {
storage_owner: Uuid,
external_user: Uuid,
message: &str,
avatar: bool,
timestamp: bool,
tint: i64,
) -> std::io::Result<()> {
) {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
let mut chunk_index = 0;
@ -88,7 +85,7 @@ impl ChatFiles {
&user_dir,
&format!("msgs_{}.json", chunk_index),
&message_chunk.dump(),
)
);
}
pub fn change_message_state(
@ -127,7 +124,7 @@ impl ChatFiles {
}
if modified {
Self::save_file(&user_dir, &fname_str, &chunk.dump())?;
Self::save_file(&user_dir, &fname_str, &chunk.dump());
break;
}
}