use std::
{
fs::FileTimes,
time::SystemTime,
path::PathBuf,
};
use tokio::fs;
use why2::
{
grid::Grid,
stream::RexStream,
};
use crate::
{
misc,
crypto,
consts,
options,
};
fn scope(hash: &[u8; 32]) -> Option<(String, PathBuf)>
{
let fingerprint = options::get_fingerprint();
if fingerprint.is_empty() { return None; }
let path = misc::get_image_cache_dir(&fingerprint).join(misc::hex(hash));
Some((fingerprint, path))
}
async fn evict(directory: &PathBuf)
{
let Ok(mut entries) = fs::read_dir(directory).await else { return };
let mut files: Vec<(SystemTime, u64, PathBuf)> = Vec::new();
let mut total: u64 = 0;
while let Ok(Some(entry)) = entries.next_entry().await
{
let Ok(metadata) = entry.metadata().await else { continue };
if !metadata.is_file() { continue; }
let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
total += metadata.len();
files.push((modified, metadata.len(), entry.path()));
}
if total <= consts::MAX_IMAGE_CACHE { return; }
files.sort_by_key(|(modified, ..)| *modified);
for (_, size, path) in files
{
if total <= consts::MAX_IMAGE_CACHE { break; }
if fs::remove_file(&path).await.is_ok() { total = total.saturating_sub(size); }
}
}
fn seal(fingerprint: &str, hash: &[u8; 32], data: &[u8]) -> Option<Vec<u8>>
{
let (key, nonce, mac_key) = crypto::cache_keys(fingerprint, hash);
let mut disk_stream: RexStream = RexStream::new(&Grid::from_key(&key).ok()?, Grid::from_flat(&nonce).ok()?).ok()?;
let mut sealed = disk_stream.update(&crypto::bytes_to_i64(data)).ok()?;
sealed.extend(disk_stream.finalize().ok()?);
let mut bytes = crypto::i64_to_bytes(&sealed);
bytes.truncate(data.len());
let tag = crypto::disk_seal(&mac_key, &bytes);
bytes.extend_from_slice(&tag);
Some(bytes)
}
pub async fn has(hash: &[u8; 32]) -> bool
{
let Some((_, path)) = scope(hash) else { return false };
fs::try_exists(&path).await.unwrap_or(false)
}
pub async fn load(hash: &[u8; 32]) -> Option<Vec<u8>>
{
let (fingerprint, path) = scope(hash)?;
let sealed = fs::read(&path).await.ok()?;
let (key, nonce, mac_key) = crypto::cache_keys(&fingerprint, hash);
let Some(ciphertext) = crypto::disk_open(&mac_key, &sealed) else
{
let _ = fs::remove_file(&path).await;
return None;
};
let mut disk_stream: RexStream = RexStream::new(&Grid::from_key(&key).ok()?, Grid::from_flat(&nonce).ok()?).ok()?;
let mut decrypted = disk_stream.update(&crypto::bytes_to_i64(ciphertext)).ok()?;
decrypted.extend(disk_stream.finalize().ok()?);
let mut image = crypto::i64_to_bytes(&decrypted);
image.truncate(ciphertext.len());
if let Ok(file) = std::fs::File::options().write(true).open(&path)
{
let _ = file.set_times(FileTimes::new().set_modified(SystemTime::now()));
}
Some(image)
}
pub async fn store(hash: &[u8; 32], data: &[u8])
{
let Some((fingerprint, path)) = scope(hash) else { return };
if fs::try_exists(&path).await.unwrap_or(false) { return; }
let Some(directory) = path.parent().map(PathBuf::from) else { return };
if fs::create_dir_all(&directory).await.is_err() { return; }
let Some(bytes) = seal(&fingerprint, hash, data) else { return };
if fs::write(&path, &bytes).await.is_err() { return; }
evict(&directory).await;
}