Files
realm-chat/client/src/app.rs

554 lines
18 KiB
Rust

use tarpc::context;
use tarpc::tokio_serde::formats::Json;
use tokio::sync::broadcast;
use tokio::sync::broadcast::{Receiver, Sender};
use tracing::log::*;
use realm_auth::types::RealmAuthClient;
use realm_server::types::{RealmChatClient, Room, User};
use realm_shared::stoken;
use realm_shared::types::ErrorCode::*;
use realm_shared::types::ErrorCode;
use crate::types::{CServer, CUser};
use crate::ui::gui;
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
pub struct RealmApp {
#[serde(skip)]
pub selected_serverid: String,
#[serde(skip)]
pub selected_roomid: String,
#[serde(skip)]
pub current_user: Option<CUser>,
pub saved_username: Option<String>,
pub saved_token: Option<String>,
pub saved_auth_address: Option<String>,
#[serde(skip)]
pub active_servers: Option<Vec<CServer>>,
#[serde(skip)]
pub value: f32,
#[serde(skip)]
pub login_window_open: bool,
#[serde(skip)]
pub login_window_username: String,
#[serde(skip)]
pub login_window_code: String,
#[serde(skip)]
pub login_window_server_domain: String,
#[serde(skip)]
pub login_window_server_port: String,
#[serde(skip)]
pub login_window_email: String,
#[serde(skip)]
pub login_ready_for_code_input: bool,
#[serde(skip)]
pub signup_window_open: bool,
#[serde(skip)]
pub server_window_open: bool,
#[serde(skip)]
pub server_window_domain: String,
#[serde(skip)]
pub server_window_port: String,
#[serde(skip)]
pub room_window_open: bool,
#[serde(skip)]
pub room_window_name: String,
#[serde(skip)]
pub room_window_admin_only_send: bool,
#[serde(skip)]
pub room_window_admin_only_view: bool,
#[serde(skip)]
pub info_window_open: bool,
#[serde(skip)]
pub login_start_channel: (Sender<Result<(), ErrorCode>>, Receiver<Result<(), ErrorCode>>),
#[serde(skip)]
pub login_ending_channel: (Sender<Result<String, ErrorCode>>, Receiver<Result<String, ErrorCode>>),
#[serde(skip)]
pub fetching_user_data_channel: (Sender<Result<CUser, ErrorCode>>, Receiver<Result<CUser, ErrorCode>>),
#[serde(skip)]
pub add_server_channel: (Sender<Result<String, ErrorCode>>, Receiver<Result<String, ErrorCode>>),
#[serde(skip)]
pub remove_server_channel: (Sender<Result<(), ErrorCode>>, Receiver<Result<(), ErrorCode>>),
#[serde(skip)]
pub join_server_channel: (Sender<Result<(), ErrorCode>>, Receiver<Result<(), ErrorCode>>),
#[serde(skip)]
pub leave_server_channel: (Sender<Result<(String, String, u16), ErrorCode>>, Receiver<Result<(String, String, u16), ErrorCode>>),
#[serde(skip)]
pub fetching_servers_channel: (Sender<Result<CServer, ErrorCode>>, Receiver<Result<CServer, ErrorCode>>),
#[serde(skip)]
pub add_room_channel: (Sender<Result<CServer, ErrorCode>>, Receiver<Result<CServer, ErrorCode>>),
#[serde(skip)]
pub delete_room_channel: (Sender<Result<CServer, ErrorCode>>, Receiver<Result<CServer, ErrorCode>>),
#[serde(skip)]
pub room_changes_channel: (Sender<Result<(CServer, Vec<Room>), ErrorCode>>, Receiver<Result<(CServer, Vec<Room>), ErrorCode>>)
}
impl Default for RealmApp {
fn default() -> Self {
Self {
selected_serverid: String::new(),
selected_roomid: String::new(),
current_user: None,
saved_username: None,
saved_token: None,
saved_auth_address: None,
active_servers: None,
value: 2.7,
login_window_open: false,
login_window_username: String::new(),
login_window_code: String::new(),
login_window_server_domain: String::new(),
login_window_server_port: "5052".to_string(),
login_start_channel: broadcast::channel(256),
login_ending_channel: broadcast::channel(256),
login_ready_for_code_input: false,
login_window_email: String::new(),
signup_window_open: false,
server_window_open: false,
server_window_domain: String::new(),
server_window_port: "5051".to_string(),
room_window_open: false,
room_window_name: String::new(),
room_window_admin_only_send: false,
room_window_admin_only_view: false,
info_window_open: false,
fetching_user_data_channel: broadcast::channel(256),
add_server_channel: broadcast::channel(256),
remove_server_channel: broadcast::channel(256),
join_server_channel: broadcast::channel(256),
leave_server_channel: broadcast::channel(256),
fetching_servers_channel: broadcast::channel(256),
add_room_channel: broadcast::channel(256),
delete_room_channel: broadcast::channel(256),
room_changes_channel: broadcast::channel(256),
}
}
}
impl RealmApp {
/// Called once before the first frame.
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
// This is also where you can customize the look and feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
// Load previous app state (if any).
// Note that you must enable the `persistence` feature for this to work.
// if let Some(storage) = cc.storage {
// return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
// }
Default::default()
}
}
pub fn fetch_user_data(send_channel: Sender<Result<CUser, ErrorCode>>, server_address: String, username: String, token: String) {
let _handle = tokio::spawn(async move {
let mut transport = tarpc::serde_transport::tcp::connect(&server_address, Json::default);
transport.config_mut().max_frame_length(usize::MAX);
let result = transport.await;
let auth_connection = match result {
Ok(connection) => connection,
Err(_) => {
send_channel.send(Err(UnableToConnectToServer)).unwrap();
return;
}
};
let client = RealmAuthClient::new(tarpc::client::Config::default(), auth_connection).spawn();
let result = client.get_all_data(context::current(), username, token.clone()).await;
match result {
Ok(r) => {
if let Err(code) = r {
send_channel.send(Err(code)).unwrap();
} else {
let auth_user = r.unwrap();
send_channel.send(Ok(CUser {
id: auth_user.id,
auth_address: server_address,
username: auth_user.username,
email: auth_user.email,
//avatar: auth_user.avatar,
server_addresses: auth_user.servers.split('|').map(|s| s.to_string()).collect(),
token,
})).unwrap();
}
}
Err(_) => {
send_channel.send(Err(RPCError)).unwrap();
}
};
});
}
pub fn fetch_server_data(channel: Sender<Result<CServer, ErrorCode>>, addresses: Vec<String>, token: String, username: String){
for server_address in addresses {
let send_channel = channel.clone();
let token = token.clone();
let userid = username.clone();
let _handle = tokio::spawn(async move {
let mut transport = tarpc::serde_transport::tcp::connect(&server_address, Json::default);
transport.config_mut().max_frame_length(usize::MAX);
let result = transport.await;
let connection = match result {
Ok(connection) => connection,
Err(_) => {
send_channel.send(Err(UnableToConnectToServer)).unwrap();
return;
}
};
let client = RealmChatClient::new(tarpc::client::Config::default(), connection).spawn();
let info = client.get_info(context::current()).await.unwrap();
let domain = server_address.split(':').collect::<Vec<&str>>()[0].to_string();
let port = server_address.split(':').collect::<Vec<&str>>()[1].to_string().parse::<u16>().unwrap();
let stoken = stoken(&token, &info.server_id, &domain, port);
let is_admin = client.is_user_admin(context::current(), userid.clone()).await.unwrap();
let is_owner = client.is_user_owner(context::current(), userid.clone()).await.unwrap();
let rooms = client.get_rooms(context::current(), stoken.clone(), userid.clone()).await.unwrap().unwrap();
send_channel.send(Ok(CServer {
tarpc_conn: client,
server_id: info.server_id,
domain,
port,
is_admin,
is_owner,
rooms,
})).unwrap();
});
}
}
pub fn fetch_rooms_data(send_channel: Sender<Result<(CServer, Vec<Room>), ErrorCode>>, server: CServer, token: String, userid: String) {
let _handle = tokio::spawn(async move {
let result = server.tarpc_conn.get_rooms(
context::current(),
stoken(&token, &server.server_id, &server.domain, server.port),
userid
).await;
match result {
Ok(r) => {
if let Ok(rooms) = r {
send_channel.send(Ok((server, rooms))).unwrap();
} else {
send_channel.send(Err(r.unwrap_err())).unwrap();
}
}
Err(_) => { send_channel.send(Err(RPCError)).unwrap(); }
}
});
}
impl eframe::App for RealmApp {
/// Called each time the UI needs repainting, which may be many times per second.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`.
// For inspiration and more examples, go to https://emilk.github.io/egui
// Init at launch and refresh our user data from the auth server
if self.current_user.is_none() &&
self.saved_token.is_some() &&
self.saved_auth_address.is_some() &&
self.saved_username.is_some()
{
let send_channel = self.fetching_user_data_channel.0.clone();
let server_address = self.saved_auth_address.clone().unwrap();
let username = self.saved_username.clone().unwrap();
let token = self.saved_token.clone().unwrap();
fetch_user_data(send_channel, server_address, username, token);
}
// Fetching servers
if self.active_servers.is_none() && self.current_user.is_some() {
self.active_servers = Some(Vec::new());
fetch_server_data(
self.fetching_servers_channel.0.clone(),
self.current_user.as_ref().unwrap().server_addresses.clone(),
self.current_user.as_ref().unwrap().token.clone(),
self.current_user.as_ref().unwrap().username.clone()
);
}
// Starting the login flow
while let Ok(result) = self.login_start_channel.1.try_recv() {
match result {
Ok(_) => self.login_ready_for_code_input = true,
Err(e) => tracing::error!("Error in login/account creation flow: {:?}", e),
}
}
// End of the login flow
while let Ok(result) = self.login_ending_channel.1.try_recv() {
match result {
Ok(token) => {
info!("Login successful! Token: {token}");
self.login_ready_for_code_input = false;
self.login_window_open = false;
self.signup_window_open = false;
self.login_window_code.clear();
info!("Fetching user data...");
let send_channel = self.fetching_user_data_channel.0.clone();
let server_address = format!("{}:{}", self.login_window_server_domain, self.login_window_server_port);
let port = self.login_window_server_port.clone().parse::<u16>().unwrap();
let username = format!("@{}:{}", self.login_window_username, self.login_window_server_domain);
self.saved_token = Some(token.clone());
self.saved_username = Some(username.clone());
self.saved_auth_address = Some(server_address.clone());
fetch_user_data(send_channel, server_address, username, token);
}
Err(e) => tracing::error!("Error in login flow: {:?}", e),
}
}
// Fetching user data
while let Ok(result) = self.fetching_user_data_channel.1.try_recv() {
match result {
Ok(client_user) => {
info!("Got data! User: {:?}", client_user);
self.current_user.replace(client_user);
}
Err(e) => error!("Error fetching data: {:?}", e),
}
}
// Adding a server (auth)
while let Ok(result) = self.add_server_channel.1.try_recv() {
match result {
Ok(address) => {
info!("New server added at: {:?}", address);
self.server_window_open = false;
let send_channel = self.join_server_channel.0.clone();
let auth_address = self.saved_auth_address.clone().unwrap();
let username = self.saved_username.clone().unwrap();
let token = self.saved_token.clone().unwrap();
let thread_username = username.clone();
let thread_token = token.clone();
let _handle = tokio::spawn(async move {
let mut transport = tarpc::serde_transport::tcp::connect(&address, Json::default);
transport.config_mut().max_frame_length(usize::MAX);
let result = transport.await;
let connection = match result {
Ok(connection) => connection,
Err(_) => {
send_channel.clone().send(Err(UnableToConnectToServer)).unwrap();
return;
}
};
let client = RealmChatClient::new(tarpc::client::Config::default(), connection).spawn();
let domain = address.split(':').collect::<Vec<&str>>()[0].to_string();
let port = address.split(':').collect::<Vec<&str>>()[1].to_string().parse::<u16>().unwrap();
let info = client.get_info(context::current()).await.unwrap();
let result = client.join_server(context::current(), stoken(&thread_token, &info.server_id, &domain, port), thread_username).await;
match result {
Ok(r) => {
info!("Joined server!");
match r {
Ok(_) => { send_channel.send(Ok(())).unwrap(); },
Err(e) => { send_channel.send(Err(e)).unwrap(); },
}
},
Err(_) => {
error!("Error joining server");
send_channel.send(Err(RPCError)).unwrap();
},
}
});
fetch_user_data(self.fetching_user_data_channel.0.clone(), auth_address, username, token);
}
Err(e) => error!("Error in adding a server: {:?}", e),
}
}
// Joining a server
while let Ok(result) = self.join_server_channel.1.try_recv() {
match result {
Ok(_) => {
info!("Successfully joined a server");
fetch_server_data(
self.fetching_servers_channel.0.clone(),
self.current_user.as_ref().unwrap().server_addresses.clone(),
self.current_user.as_ref().unwrap().token.clone(),
self.current_user.as_ref().unwrap().username.clone()
);
},
Err(code) => {
error!("Error joining server: {:?}", code);
}
}
}
// Leaving a server
while let Ok(result) = self.leave_server_channel.1.try_recv() {
match result {
Ok((serverid, domain, port)) => {
info!("Successfully left a server");
self.active_servers.as_mut().unwrap().retain(|s| !s.server_id.eq(&serverid));
self.selected_serverid.clear();
self.selected_roomid.clear();
let send_channel = self.remove_server_channel.0.clone();
let auth_address = self.current_user.clone().unwrap().auth_address;
let username = self.current_user.clone().unwrap().username;
let token = self.current_user.clone().unwrap().token;
let _handle = tokio::spawn(async move {
let mut transport = tarpc::serde_transport::tcp::connect(&auth_address, Json::default);
transport.config_mut().max_frame_length(usize::MAX);
let result = transport.await;
let connection = match result {
Ok(connection) => connection,
Err(_) => {
send_channel.clone().send(Err(UnableToConnectToServer)).unwrap();
return;
}
};
let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn();
let result = client.remove_server(context::current(), username, token, domain, port).await;
match result {
Ok(r) => { send_channel.send(r).unwrap(); },
Err(_) => { send_channel.send(Err(RPCError)).unwrap(); },
}
});
},
Err(code) => {
error!("Error leaving server: {:?}", code);
}
}
}
// Removing a server (auth)
while let Ok(result) = self.remove_server_channel.1.try_recv() {
match result {
Ok(_) => {
let send_channel = self.fetching_user_data_channel.0.clone();
let server_address = self.saved_auth_address.clone().unwrap();
let username = self.saved_username.clone().unwrap();
let token = self.saved_token.clone().unwrap();
fetch_user_data(send_channel, server_address, username, token);
info!("Successfully removed a server");
}
Err(code) => {
error!("Error removing server: {:?}", code);
}
}
}
// Fetching servers
while let Ok(result) = self.fetching_servers_channel.1.try_recv() {
match result {
Ok(server) => {
info!("Got server data! Server: {:?}", server);
if let Some(active_servers) = &mut self.active_servers {
active_servers.push(server);
}
}
Err(e) => error!("Error fetching server data: {:?}", e),
}
}
// Added Room
while let Ok(result) = self.add_room_channel.1.try_recv() {
match result {
Ok(server) => {
info!("Got room add! Fetching them...");
fetch_rooms_data(
self.room_changes_channel.0.clone(),
server,
self.current_user.as_ref().unwrap().token.clone(),
self.current_user.as_ref().unwrap().username.clone()
);
self.room_window_open = false;
}
Err(e) => error!("Error adding room: {:?}", e),
}
}
// Deleting a room
while let Ok(result) = self.delete_room_channel.1.try_recv() {
match result {
Ok(server) => {
info!("Got room delete! Fetching them...");
self.selected_roomid.clear();
fetch_rooms_data(
self.room_changes_channel.0.clone(),
server,
self.current_user.as_ref().unwrap().token.clone(),
self.current_user.as_ref().unwrap().username.clone()
);
}
Err(e) => error!("Error deleting room: {:?}", e),
}
}
// Fetching rooms
while let Ok(result) = self.room_changes_channel.1.try_recv() {
match result {
Ok(tuple) => {
info!("Got room data for a server: {:?}", tuple);
if let Some(servers) = &mut self.active_servers {
for server in servers {
if server.server_id.eq(&tuple.0.server_id) {
server.rooms = tuple.1.clone();
}
}
}
}
Err(e) => error!("Error fetching room data: {:?}", e),
}
}
// File -> Quit
gui::top_panel(self, ctx);
gui::servers(self, ctx);
gui::rooms(self, ctx);
gui::messages(self, ctx);
gui::modals(self, ctx)
}
/// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
}
}