|
|
@@ -1,126 +1,46 @@
|
|
|
mod actions;
|
|
|
-mod message;
|
|
|
-mod persistence;
|
|
|
mod ui;
|
|
|
|
|
|
+use client_shared::{LOGO, LOGO_DURATION};
|
|
|
use cursive::align::Align;
|
|
|
-use cursive::reexports::log;
|
|
|
+use cursive::view::Resizable as _;
|
|
|
use cursive::views::TextView;
|
|
|
use cursive::{event, Cursive};
|
|
|
-use cursive::{traits::*, CursiveExt as _};
|
|
|
-use message::Message;
|
|
|
-use std::collections::HashMap;
|
|
|
-use std::io::{Read as _, Write as _};
|
|
|
+use cursive::CursiveExt as _;
|
|
|
+use std::ffi::OsString;
|
|
|
+use std::str::FromStr;
|
|
|
use std::thread;
|
|
|
use std::time::Duration;
|
|
|
-use utils::serialize::Serialize;
|
|
|
|
|
|
use ui::INPUT_FIELD_ID;
|
|
|
|
|
|
-use crate::persistence::Savedata;
|
|
|
-
|
|
|
-const MAX_MESSAGE_LENGTH: usize = 512;
|
|
|
-const MAX_USERNAME_LENGTH: usize = 16;
|
|
|
-const DEFAULT_USERNAME_PREFIX: &str = "Myst";
|
|
|
-const DEFAULT_CHANNEL: &str = "Root";
|
|
|
-const DEFAULT_PASSWORD: &str = "null";
|
|
|
-
|
|
|
-const API_CONNECTION_TIMEOUT: u64 = 2;
|
|
|
-const API_DEFAULT_PORT: u16 = 13337;
|
|
|
-
|
|
|
-const INVALID_MESSAGE_IDENT: &str = "ERR";
|
|
|
-
|
|
|
-const LOGO: &str = include_str!("../assets/logo.txt");
|
|
|
-const LOGO_DURATION: u128 = 500;
|
|
|
-
|
|
|
-const SAVE_FILE: &str = "savedata.bin";
|
|
|
-const SAVE_FILE_FUZZY: u64 = 0b0110110001101001011001110110110101100001001000000101100001000100;
|
|
|
-
|
|
|
-#[derive(Clone)]
|
|
|
-pub struct Appdata {
|
|
|
- pub persistent_data: Savedata,
|
|
|
-
|
|
|
- pub messages: HashMap<String, Message>,
|
|
|
- pub quick_close_window_count: usize,
|
|
|
- pub local_channels: Vec<String>,
|
|
|
- pub intro_screen_shown_until: u128,
|
|
|
-}
|
|
|
-
|
|
|
-impl Default for Appdata {
|
|
|
- fn default() -> Self {
|
|
|
- Self::new()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl Appdata {
|
|
|
- pub fn new() -> Self {
|
|
|
- Self {
|
|
|
- messages: HashMap::new(),
|
|
|
- quick_close_window_count: 0,
|
|
|
- local_channels: vec![DEFAULT_CHANNEL.to_string()],
|
|
|
- intro_screen_shown_until: 0,
|
|
|
- persistent_data: Savedata::new(),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+use client_shared::persistence::{Appdata, load_appdata, save_appdata};
|
|
|
|
|
|
pub fn get_appdata(siv: &mut Cursive) -> Appdata {
|
|
|
siv.with_user_data(|appdata: &mut Appdata| appdata.clone())
|
|
|
.expect("Failed to retrieve appdata.")
|
|
|
}
|
|
|
|
|
|
-pub fn save_appdata(siv: &mut Cursive) {
|
|
|
- let appdata = get_appdata(siv);
|
|
|
-
|
|
|
- let savedata = persistence::Savedata::from(appdata);
|
|
|
- let bytes = savedata.serialize_checked();
|
|
|
- let fuzzy_bytes = utils::binary::fuzzy_bytes(bytes, SAVE_FILE_FUZZY);
|
|
|
-
|
|
|
- let mut file = std::fs::File::create(SAVE_FILE).expect("Failed to create savedata file.");
|
|
|
- file.write_all(&fuzzy_bytes)
|
|
|
- .expect("Failed to write savedata file.");
|
|
|
-
|
|
|
- log::info!("Wrote savadata to file.");
|
|
|
-}
|
|
|
-
|
|
|
-pub fn load_appdata(siv: &mut Cursive) -> std::io::Result<()> {
|
|
|
- let mut df = logging::warn_deferred("Savedata file not found; using defaults");
|
|
|
-
|
|
|
- let mut file = std::fs::File::open(SAVE_FILE)?;
|
|
|
-
|
|
|
- let mut bytes = Vec::new();
|
|
|
- file.read_to_end(&mut bytes)?;
|
|
|
- let useful_bytes = utils::binary::fuzzy_bytes(bytes, SAVE_FILE_FUZZY);
|
|
|
-
|
|
|
- df.cancel();
|
|
|
-
|
|
|
- let Ok(savedata) = persistence::Savedata::deserialize_checked(useful_bytes) else {
|
|
|
- // If the file is corrupted, create a new one
|
|
|
- logging::warn("Savedata file corrupted; using defaults.");
|
|
|
- return Ok(());
|
|
|
- };
|
|
|
-
|
|
|
- let appdata = Appdata::from(savedata);
|
|
|
- siv.set_user_data(appdata);
|
|
|
-
|
|
|
- logging::info("Savedata file loaded.");
|
|
|
- Ok(())
|
|
|
-}
|
|
|
-
|
|
|
fn main() {
|
|
|
if cfg!(debug_assertions) {
|
|
|
logging::set_global_log_level(logging::LogLevel::Trace);
|
|
|
} else {
|
|
|
- logging::set_global_log_level(logging::LogLevel::Info);
|
|
|
+ logging::set_global_log_level(logging::LogLevel::Debug);
|
|
|
}
|
|
|
|
|
|
+ logging::set_global_log_path(Some(OsString::from_str("./client-log.txt").unwrap()));
|
|
|
+
|
|
|
utils::rng::shuffle_rng();
|
|
|
|
|
|
let mut siv = Cursive::default();
|
|
|
- siv.set_user_data(Appdata::new());
|
|
|
|
|
|
// TODO (low): Add a notice when the file is corrupted.
|
|
|
- let _ = load_appdata(&mut siv);
|
|
|
+ let appdata = match load_appdata() {
|
|
|
+ Ok(Some(x)) => x,
|
|
|
+ _ => Appdata::new(),
|
|
|
+ };
|
|
|
+
|
|
|
+ siv.set_user_data(appdata);
|
|
|
|
|
|
// Global hotkeys
|
|
|
siv.add_global_callback(event::Key::Backspace, |siv| {
|
|
|
@@ -162,7 +82,7 @@ fn main() {
|
|
|
let _ = actions::load_messages(siv);
|
|
|
}
|
|
|
|
|
|
- save_appdata(siv);
|
|
|
+ save_appdata(get_appdata(siv));
|
|
|
}))
|
|
|
.expect("Failed to send callback from background thread.");
|
|
|
|