use std::fmt::Debug; use std::sync::Arc; use anyhow::Result; use chrono::{Duration, Utc}; use futures::lock::Mutex; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::logs::{LogEventType, log_event}; #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Asset { id: String, share_duration: u32, created_at: i64, expires_at: i64, mime: String, #[serde(skip)] content: Vec, uploader_ip: Option, } #[allow(dead_code)] impl Asset { pub fn new(share_duration: u32, mime: String, content: Vec, uploader_ip: Option) -> Self { let id = uuid::Uuid::new_v4().to_string(); let created_at = Utc::now().timestamp_millis(); let expires_at = created_at + Duration::minutes(share_duration as i64).num_milliseconds(); Asset { id, share_duration, created_at, expires_at, mime, content, uploader_ip, } } pub fn is_expired(&self) -> bool { Utc::now().timestamp_millis() > self.expires_at } pub fn id(&self) -> String { self.id.clone() } pub fn mime(&self) -> String { self.mime.clone() } pub fn content(&self) -> Vec { self.content.clone() } pub fn share_duration(&self) -> u32 { self.share_duration } pub fn created_at(&self) -> i64 { self.created_at } pub fn expires_at(&self) -> i64 { self.expires_at } pub fn mime_type(&self) -> &str { &self.mime } pub fn size_bytes(&self) -> usize { self.content.len() } pub fn uploader_ip(&self) -> Option<&str> { self.uploader_ip.as_deref() } pub fn to_bytes(&self) -> Result> { let bytes = serde_json::to_vec(self)?; Ok(bytes) } pub fn to_value(&self) -> Value { serde_json::to_value(self).unwrap_or(Value::Null) } // pub fn save(&self) -> Result { // let id = self.id.clone(); // let path = format!("{}{}", DATA_STORAGE, self.id); // std::fs::create_dir_all(DATA_STORAGE)?; // std::fs::write(&path, self.to_bytes()?)?; // Ok(id) // } } #[derive(Clone)] pub struct AssetTracker { assets: Arc>>, } #[allow(dead_code)] impl AssetTracker { pub fn new() -> Self { AssetTracker { assets: Arc::new(Mutex::new(Vec::new())), } } pub async fn add_asset(&self, asset: Asset) { print!("[{}] Adding asset: {}", chrono::Local::now().to_rfc3339(), asset.id()); self.assets.lock().await.push(asset); self.show_assets().await; } pub async fn remove_expired(&self) { let mut assets = self.assets.lock().await; let removed_assets = assets.extract_if(.., |asset| asset.is_expired()); for asset in removed_assets { println!("[{}] Removing asset: {}", chrono::Local::now().to_rfc3339(), asset.id()); log_event(LogEventType::AssetDeleted(asset.to_value())); } } pub async fn active_assets(&self) -> usize { self.assets.lock().await.len() } pub async fn stats_summary(&self) -> (usize, u64, usize, usize) { let assets = self.assets.lock().await; let mut active_assets = 0; let mut storage_bytes: u64 = 0; let mut image_count = 0; let mut text_count = 0; for asset in assets.iter() { if asset.is_expired() { continue; } active_assets += 1; storage_bytes += asset.size_bytes() as u64; if asset.mime().starts_with("image/") { image_count += 1; } else if asset.mime().starts_with("text/") { text_count += 1; } } (active_assets, storage_bytes, image_count, text_count) } pub async fn show_assets(&self) { for asset in self.assets.lock().await.iter() { println!( "Asset ID: {}, Expires At: {}, MIME: {}, Size: {} bytes", asset.id(), asset.expires_at(), asset.mime(), asset.size_bytes() ); } } pub async fn get_asset(&self, id: &str) -> Option { let assets = self.assets.lock().await; for asset in assets.iter().cloned() { if asset.id() == id { return Some(asset.clone()); } } None } } pub async fn clear_assets(assets: AssetTracker) -> Result<()> { assets.remove_expired().await; Ok(()) }