use std::
{
fs,
collections::HashSet,
sync::{ LazyLock, Mutex },
};
use why2::consts as why2_consts;
use crate::
{
misc,
crypto,
consts::{ self, SharedKeys },
network::codes::
{
MessageColors,
StoredMessage,
},
};
static HISTORY: LazyLock<Mutex<Vec<StoredMessage>>> = LazyLock::new(|| Mutex::new(load())); static KEYS: LazyLock<SharedKeys> = LazyLock::new(crypto::history_keys);
fn path() -> String {
super::config_path(consts::SERVER_MESSAGES_FILE)
}
fn load() -> Vec<StoredMessage> {
let Ok(bytes) = fs::read(path()) else { return Vec::new() };
let Some(plaintext) = crypto::decrypt_packet::
<{ why2_consts::DEFAULT_GRID_WIDTH }, { why2_consts::DEFAULT_GRID_HEIGHT }>(bytes, &KEYS)
else { return Vec::new() };
wincode::config::deserialize::<Vec<StoredMessage>, _>(&plaintext, consts::PACKET_CONFIG).unwrap_or_default()
}
pub fn store(username: &str, text: &str, colors: &MessageColors) {
push(StoredMessage
{
username: username.to_string(),
text: text.to_string(),
colors: colors.clone(),
image: None,
});
}
pub fn store_image(username: &str, filename: &str, hash: &[u8; 32])
{
push(StoredMessage
{
username: username.to_string(),
text: filename.to_string(),
colors: MessageColors { username_color: None, message_color: None },
image: Some(*hash),
});
}
fn push(message: StoredMessage) {
let limit: usize = super::read_config("max_persistent_messages");
if limit == 0 { return; }
let mut history = HISTORY.lock().unwrap();
history.push(message);
let over = history.len().saturating_sub(limit);
let dropped: Vec<[u8; 32]> = history.drain(..over).filter_map(|message| message.image).collect();
let orphans: Vec<[u8; 32]> = dropped.into_iter()
.filter(|hash| !history.iter().any(|message| message.image.as_ref() == Some(hash)))
.collect();
let bytes = wincode::config::serialize(&*history, consts::PACKET_CONFIG).expect("Encoding message history failed");
let sealed = crypto::encrypt_packet::<{ why2_consts::DEFAULT_GRID_WIDTH }, { why2_consts::DEFAULT_GRID_HEIGHT }>(&bytes, &KEYS);
fs::write(path(), sealed).expect("Saving message history failed");
drop(history);
for hash in orphans { let _ = fs::remove_file(misc::get_image_dir().join(misc::hex(&hash))); }
}
pub fn has_image(hash: &[u8; 32]) -> bool {
HISTORY.lock().unwrap().iter().any(|message| message.image.as_ref() == Some(hash))
}
pub fn sweep_images()
{
let Ok(directory) = fs::read_dir(misc::get_image_dir()) else { return };
let files: Vec<_> = directory.flatten().map(|entry| entry.path()).collect();
if files.is_empty() { return; }
let kept: HashSet<String> = HISTORY.lock().unwrap().iter()
.filter_map(|message| message.image.as_ref().map(|hash| misc::hex(hash)))
.collect();
for file in files
{
let named = file.file_name().and_then(|name| name.to_str())
.map(|name| kept.contains(name)).unwrap_or(false);
if !named { let _ = fs::remove_file(&file); }
}
}
pub fn all() -> Vec<StoredMessage> {
HISTORY.lock().unwrap().clone()
}