Communities
This commit is contained in:
parent
6ae6e85b5a
commit
1c08956387
26 changed files with 1988 additions and 338 deletions
108
src/communities/interactables/category.rs
Normal file
108
src/communities/interactables/category.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use crate::{
|
||||
communities::{community::Community, interactables::interactable::Interactable},
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
};
|
||||
use axum::Json;
|
||||
use json::JsonValue;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Category {
|
||||
name: String,
|
||||
path: String,
|
||||
community: Arc<Community>,
|
||||
children: Vec<Arc<Box<dyn Interactable>>>,
|
||||
}
|
||||
impl Category {
|
||||
pub fn new() -> Category {
|
||||
Category {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
community: Arc::new(Community::new()),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn get_child(&self, path: String, name: String) -> Option<Arc<Box<dyn Interactable>>> {
|
||||
if path.is_empty() {
|
||||
self.children
|
||||
.iter()
|
||||
.find(|child| child.get_name() == &name)
|
||||
.cloned()
|
||||
} else {
|
||||
let sub_module = path.split("/").next().unwrap();
|
||||
let next = self
|
||||
.children
|
||||
.iter()
|
||||
.find(|child| child.get_name() == sub_module)
|
||||
.unwrap();
|
||||
if next.get_codec() == "category" {
|
||||
let next_cat = next.as_any().downcast_ref::<Category>().unwrap();
|
||||
next_cat.get_child(path, name)
|
||||
} else {
|
||||
Some(next.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_children(&self) -> Vec<Arc<Box<dyn Interactable>>> {
|
||||
self.children.iter().map(|child| child.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Interactable for Category {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
fn get_codec(&self) -> String {
|
||||
"category".to_string()
|
||||
}
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.path = path;
|
||||
}
|
||||
fn get_community(&self) -> &Arc<Community> {
|
||||
&self.community
|
||||
}
|
||||
fn set_community(&mut self, community: Arc<Community>) {
|
||||
self.community = community;
|
||||
}
|
||||
fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
fn get_path(&self) -> &String {
|
||||
&self.path
|
||||
}
|
||||
fn get_total_path(&self) -> String {
|
||||
String::new() + &self.path + "/" + &self.name
|
||||
}
|
||||
fn get_data(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
for child in &self.children {
|
||||
let mut subject = JsonValue::new_object();
|
||||
subject["codec"] = JsonValue::String(child.get_codec());
|
||||
subject["data"] = child.get_data();
|
||||
v[child.get_name()] = subject;
|
||||
}
|
||||
v
|
||||
}
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
|
||||
CommunicationValue::new(CommunicationType::error)
|
||||
}
|
||||
fn to_json(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
v["children"] = JsonValue::new_array();
|
||||
for child in &self.children {
|
||||
v["children"].push(child.to_json());
|
||||
}
|
||||
v
|
||||
}
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
|
||||
self.community = community;
|
||||
self.name = name;
|
||||
self.path = path;
|
||||
}
|
||||
}
|
||||
27
src/communities/interactables/interactable.rs
Normal file
27
src/communities/interactables/interactable.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use crate::{
|
||||
communities::community::Community,
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
};
|
||||
use axum::Json;
|
||||
use json::JsonValue;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type InteractableFactory = fn() -> Box<dyn Interactable>;
|
||||
|
||||
pub trait Interactable: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn get_codec(&self) -> String;
|
||||
fn get_name(&self) -> &String;
|
||||
fn get_path(&self) -> &String;
|
||||
fn get_total_path(&self) -> String;
|
||||
fn set_name(&mut self, name: String);
|
||||
fn set_path(&mut self, path: String);
|
||||
fn get_community(&self) -> &Arc<Community>;
|
||||
fn set_community(&mut self, community: Arc<Community>);
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue;
|
||||
fn get_data(&self) -> JsonValue;
|
||||
fn to_json(&self) -> JsonValue;
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue);
|
||||
}
|
||||
73
src/communities/interactables/registry.rs
Normal file
73
src/communities/interactables/registry.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use crate::communities::community::Community;
|
||||
use crate::communities::interactables::category::Category;
|
||||
use crate::communities::interactables::interactable::{Interactable, InteractableFactory};
|
||||
use crate::communities::interactables::text_chat::TextChat;
|
||||
use crate::communities::interactables::voice_chat::VoiceChat;
|
||||
use crate::gui::log_panel;
|
||||
use crate::util::file_util;
|
||||
use json::JsonValue;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub static INTERACTABLE_REGISTRY: Lazy<Arc<Mutex<HashMap<String, InteractableFactory>>>> =
|
||||
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
|
||||
pub async fn load_interactables() {
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(TextChat::new().get_codec(), || {
|
||||
Box::new(TextChat::new()) as Box<dyn Interactable>
|
||||
});
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(VoiceChat::new().get_codec(), || {
|
||||
Box::new(VoiceChat::new()) as Box<dyn Interactable>
|
||||
});
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(Category::new().get_codec(), || {
|
||||
Box::new(Category::new()) as Box<dyn Interactable>
|
||||
});
|
||||
}
|
||||
pub async fn register_interactable(name: String, interactable: InteractableFactory) {
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(name.to_string(), interactable)
|
||||
.unwrap();
|
||||
}
|
||||
pub async fn get_interactable(name: &str) -> Box<dyn Interactable> {
|
||||
INTERACTABLE_REGISTRY.lock().await.get(name).unwrap()()
|
||||
}
|
||||
pub async fn save(interactable: &Arc<Box<dyn Interactable>>) {
|
||||
let mut json_object: JsonValue = interactable.to_json().clone();
|
||||
json_object["codec"] = JsonValue::String(interactable.get_codec());
|
||||
file_util::save_file(
|
||||
&format!(
|
||||
"communities/{}/interactables/{}",
|
||||
interactable.get_community().get_name(),
|
||||
interactable.get_path()
|
||||
),
|
||||
&format!("{}.json", interactable.get_name()),
|
||||
&json_object.to_string(),
|
||||
);
|
||||
}
|
||||
pub async fn load(
|
||||
c: Arc<Community>,
|
||||
path: String,
|
||||
name: String,
|
||||
) -> Box<dyn Interactable + 'static> {
|
||||
let s = file_util::load_file(
|
||||
&format!("communities/{}/interactables/{}", c.get_name(), path),
|
||||
&format!("{}.json", name),
|
||||
);
|
||||
let json_object: JsonValue = json::parse(&s).unwrap();
|
||||
let codec: String = json_object["codec"].as_str().unwrap().to_string();
|
||||
let mut interactable = get_interactable(&codec).await;
|
||||
interactable.load(c, path, name, &json_object);
|
||||
interactable
|
||||
}
|
||||
225
src/communities/interactables/text_chat.rs
Normal file
225
src/communities/interactables/text_chat.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
use crate::{
|
||||
communities::{community::Community, interactables::interactable::Interactable},
|
||||
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
||||
gui::log_panel::log_message,
|
||||
util::file_util::{get_children, load_file, save_file},
|
||||
};
|
||||
use aes_gcm::aead::Payload;
|
||||
use axum::Json;
|
||||
use json::{JsonValue, array, object};
|
||||
use std::any::Any;
|
||||
use std::fs::{self, File};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
pub struct TextChat {
|
||||
name: String,
|
||||
path: String,
|
||||
community: Arc<Community>,
|
||||
}
|
||||
impl TextChat {
|
||||
pub fn new() -> TextChat {
|
||||
TextChat {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
community: Arc::new(Community::new()),
|
||||
}
|
||||
}
|
||||
pub fn add_message(&self, send_time: u128, sender: Uuid, message: &str) {
|
||||
let user_dir = &format!(
|
||||
"communities/{}/interactables/{}/{}",
|
||||
self.get_community().get_name(),
|
||||
self.get_path(),
|
||||
self.get_name()
|
||||
);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(user_dir) {
|
||||
log_message(format!("Failed to create chat directory: {}", e));
|
||||
return;
|
||||
}
|
||||
|
||||
let mut chunk_index = 0;
|
||||
let mut message_chunk = array![];
|
||||
|
||||
// find latest chunk not full (max 800 msgs)
|
||||
loop {
|
||||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
let file_content = load_file(&user_dir, &file_name);
|
||||
|
||||
if !file_content.is_empty() {
|
||||
if let Ok(current_chunk) = json::parse(&file_content) {
|
||||
if current_chunk.is_array() && current_chunk.len() < 800 {
|
||||
message_chunk = current_chunk;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log_message(format!("Failed to parse existing JSON file: {}", file_name));
|
||||
}
|
||||
} else {
|
||||
// New file, use empty array
|
||||
break;
|
||||
}
|
||||
|
||||
chunk_index += 1;
|
||||
if chunk_index > 1000 {
|
||||
log_message(format!("Too many message chunks. Aborting add."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let json_obj = object! {
|
||||
"timestamp" => send_time as i64,
|
||||
"content" => message,
|
||||
"sender" => sender.to_string(),
|
||||
};
|
||||
|
||||
if let Err(e) = message_chunk.push(json_obj) {
|
||||
log_message(format!("Failed to push new message into JSON array: {}", e));
|
||||
return;
|
||||
}
|
||||
|
||||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
log_message(format!("Saving message to {}/{}", user_dir, file_name));
|
||||
save_file(&user_dir, &file_name, &message_chunk.dump());
|
||||
}
|
||||
pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue {
|
||||
let mut messages = array![];
|
||||
|
||||
let mut latest_chunk_index: i32 = -1;
|
||||
let files = get_children(&format!(
|
||||
"communities/{}/interactables/{}/{}",
|
||||
self.get_community().get_name(),
|
||||
self.get_path(),
|
||||
self.get_name()
|
||||
));
|
||||
|
||||
for entry in files {
|
||||
if let Some(num) = {
|
||||
entry
|
||||
.strip_prefix("msgs_")
|
||||
.and_then(|s| s.strip_suffix(".json"))
|
||||
} {
|
||||
if let Ok(index) = num.parse::<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 = load_file(
|
||||
&format!(
|
||||
"communities/{}/interactables/{}/{}",
|
||||
self.get_community().get_name(),
|
||||
self.get_path(),
|
||||
self.get_name()
|
||||
),
|
||||
&file_name,
|
||||
);
|
||||
if file_content.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(chunk) = json::parse(&file_content) {
|
||||
for i in (0..chunk.len()).rev() {
|
||||
if needed == 0 {
|
||||
break;
|
||||
}
|
||||
if to_skip > 0 {
|
||||
to_skip -= 1;
|
||||
continue;
|
||||
}
|
||||
messages.push(chunk[i].clone()).unwrap();
|
||||
needed -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
}
|
||||
impl Interactable for TextChat {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
fn get_codec(&self) -> String {
|
||||
"text".to_string()
|
||||
}
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.path = path;
|
||||
}
|
||||
fn get_community(&self) -> &Arc<Community> {
|
||||
&self.community
|
||||
}
|
||||
fn set_community(&mut self, community: Arc<Community>) {
|
||||
self.community = community;
|
||||
}
|
||||
fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
fn get_path(&self) -> &String {
|
||||
&self.path
|
||||
}
|
||||
fn get_total_path(&self) -> String {
|
||||
String::new() + &self.path + "/" + &self.name
|
||||
}
|
||||
fn get_data(&self) -> JsonValue {
|
||||
JsonValue::new_object()
|
||||
}
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
|
||||
let payload = cv.get_data(DataTypes::payload).unwrap();
|
||||
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "get_messages" {
|
||||
let amount = payload["amount"].as_i64().unwrap();
|
||||
let loaded_messages = payload["loaded_messages"].as_i64().unwrap();
|
||||
let messages = self.get_messages(loaded_messages, amount);
|
||||
let mut payload = JsonValue::new_object();
|
||||
payload["messages"] = messages;
|
||||
return CommunicationValue::new(CommunicationType::function)
|
||||
.with_id(cv.get_id())
|
||||
.add_data_str(DataTypes::name, self.name.clone())
|
||||
.add_data_str(DataTypes::path, self.path.clone())
|
||||
.add_data_str(DataTypes::result, "message_chunk".to_string())
|
||||
.add_data(DataTypes::payload, payload);
|
||||
}
|
||||
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" {
|
||||
let message = payload["message"].as_str().unwrap();
|
||||
let milliseconds_timestamp: u128 = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
self.add_message(milliseconds_timestamp, cv.get_sender().unwrap(), message);
|
||||
return CommunicationValue::new(CommunicationType::function)
|
||||
.with_id(cv.get_id())
|
||||
.add_data_str(DataTypes::name, self.name.clone())
|
||||
.add_data_str(DataTypes::path, self.path.clone())
|
||||
.add_data_str(DataTypes::result, "message_received".to_string())
|
||||
.add_data(DataTypes::payload, JsonValue::new_object());
|
||||
}
|
||||
CommunicationValue::new(CommunicationType::error).with_id(cv.get_id())
|
||||
}
|
||||
fn to_json(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
v
|
||||
}
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
|
||||
self.community = community;
|
||||
self.name = name;
|
||||
self.path = path;
|
||||
}
|
||||
}
|
||||
83
src/communities/interactables/voice_chat.rs
Normal file
83
src/communities/interactables/voice_chat.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use crate::{
|
||||
communities::{community::Community, interactables::interactable::Interactable},
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
};
|
||||
use json::JsonValue;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
pub enum CallUserState {
|
||||
Active,
|
||||
Muted,
|
||||
Deafed,
|
||||
}
|
||||
|
||||
pub struct CallUser {
|
||||
user_id: Uuid,
|
||||
user_state: CallUserState,
|
||||
streaming: bool,
|
||||
}
|
||||
|
||||
pub struct VoiceChat {
|
||||
name: String,
|
||||
path: String,
|
||||
community: Arc<Community>,
|
||||
users: Vec<CallUser>,
|
||||
}
|
||||
impl VoiceChat {
|
||||
pub fn new() -> VoiceChat {
|
||||
VoiceChat {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
community: Arc::new(Community::new()),
|
||||
users: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Interactable for VoiceChat {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
fn get_codec(&self) -> String {
|
||||
"voice".to_string()
|
||||
}
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.path = path;
|
||||
}
|
||||
fn get_community(&self) -> &Arc<Community> {
|
||||
&self.community
|
||||
}
|
||||
fn set_community(&mut self, community: Arc<Community>) {
|
||||
self.community = community;
|
||||
}
|
||||
fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
fn get_path(&self) -> &String {
|
||||
&self.path
|
||||
}
|
||||
fn get_total_path(&self) -> String {
|
||||
String::new() + &self.path + "/" + &self.name
|
||||
}
|
||||
fn get_data(&self) -> JsonValue {
|
||||
JsonValue::new_object()
|
||||
}
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
|
||||
CommunicationValue::new(CommunicationType::error)
|
||||
}
|
||||
fn to_json(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
v
|
||||
}
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
|
||||
self.community = community;
|
||||
self.name = name;
|
||||
self.path = path;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue