use crate::constants::{LAZY_LOAD_USERS_PER_TICK, MAX_CHAT_USER_IDS, MAX_USER_CACHE_SIZE}; use crate::types::{ChatId, UserId}; use std::collections::HashMap; use tdlib_rs::enums::{User, UserStatus}; use tdlib_rs::functions; use super::types::UserOnlineStatus; /// Простой LRU-кэш на основе HashMap + Vec для отслеживания порядка pub struct LruCache { map: HashMap, /// Порядок доступа: последний элемент — самый недавно использованный order: Vec, capacity: usize, } impl LruCache { pub fn new(capacity: usize) -> Self { Self { map: HashMap::with_capacity(capacity), order: Vec::with_capacity(capacity), capacity, } } /// Получить значение и обновить порядок доступа pub fn get(&mut self, key: &UserId) -> Option<&V> { if self.map.contains_key(key) { // Перемещаем ключ в конец (самый недавно использованный) self.order.retain(|k| k != key); self.order.push(*key); self.map.get(key) } else { None } } /// Получить значение без обновления порядка (для read-only доступа) pub fn peek(&self, key: &UserId) -> Option<&V> { self.map.get(key) } /// Вставить значение pub fn insert(&mut self, key: UserId, value: V) { if self.map.contains_key(&key) { // Обновляем существующее значение self.map.insert(key, value); self.order.retain(|k| *k != key); self.order.push(key); } else { // Если кэш полон, удаляем самый старый элемент if self.map.len() >= self.capacity { if let Some(oldest) = self.order.first().copied() { self.order.remove(0); self.map.remove(&oldest); } } self.map.insert(key, value); self.order.push(key); } } /// Проверить наличие ключа pub fn contains_key(&self, key: &UserId) -> bool { self.map.contains_key(key) } /// Количество элементов #[allow(dead_code)] pub fn len(&self) -> usize { self.map.len() } } /// Кеш пользователей и их данных pub struct UserCache { /// LRU-кэш usernames: user_id -> username pub user_usernames: LruCache, /// LRU-кэш имён: user_id -> display_name (first_name + last_name) pub user_names: LruCache, /// Связь chat_id -> user_id для приватных чатов pub chat_user_ids: HashMap, /// Очередь user_id для загрузки имён pub pending_user_ids: Vec, /// LRU-кэш онлайн-статусов пользователей: user_id -> status pub user_statuses: LruCache, client_id: i32, } impl UserCache { pub fn new(client_id: i32) -> Self { Self { user_usernames: LruCache::new(MAX_USER_CACHE_SIZE), user_names: LruCache::new(MAX_USER_CACHE_SIZE), chat_user_ids: HashMap::with_capacity(MAX_CHAT_USER_IDS), pending_user_ids: Vec::new(), user_statuses: LruCache::new(MAX_USER_CACHE_SIZE), client_id, } } /// Получить username пользователя pub fn get_username(&mut self, user_id: &UserId) -> Option<&String> { self.user_usernames.get(user_id) } /// Получить имя пользователя pub fn get_name(&mut self, user_id: &UserId) -> Option<&String> { self.user_names.get(user_id) } /// Получить user_id по chat_id pub fn get_user_id_by_chat(&self, chat_id: ChatId) -> Option { self.chat_user_ids.get(&chat_id).copied() } /// Получить статус пользователя по chat_id pub fn get_status_by_chat_id(&self, chat_id: ChatId) -> Option<&UserOnlineStatus> { let user_id = self.chat_user_ids.get(&chat_id)?; self.user_statuses.peek(user_id) } /// Обработать обновление пользователя pub fn handle_user_update(&mut self, user_enum: &User) { if let User::User(user) = user_enum { let user_id = user.id; // Сохраняем username if let Some(username) = user.usernames.as_ref().map(|u| u.editable_username.clone()) { self.user_usernames.insert(UserId::new(user_id), username); } // Сохраняем имя let display_name = format!("{} {}", user.first_name, user.last_name).trim().to_string(); self.user_names.insert(UserId::new(user_id), display_name); // Обновляем статус self.update_status(UserId::new(user_id), &user.status); } } /// Обработать обновление статуса пользователя pub fn update_status(&mut self, user_id: UserId, status: &UserStatus) { let online_status = match status { UserStatus::Online(_) => UserOnlineStatus::Online, UserStatus::Recently(_) => UserOnlineStatus::Recently, UserStatus::LastWeek(_) => UserOnlineStatus::LastWeek, UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth, UserStatus::Offline(s) => UserOnlineStatus::Offline(s.was_online), _ => return, }; self.user_statuses.insert(user_id, online_status); } /// Сохранить связь chat_id -> user_id pub fn register_private_chat(&mut self, chat_id: ChatId, user_id: UserId) { self.chat_user_ids.insert(chat_id, user_id); } /// Получить имя пользователя (асинхронно с загрузкой если нужно) pub async fn get_user_name(&self, user_id: UserId) -> String { // Сначала пытаемся получить из кэша if let Some(name) = self.user_names.peek(&user_id) { return name.clone(); } // Загружаем пользователя match functions::get_user(user_id.as_i64(), self.client_id).await { Ok(User::User(user)) => { let name = format!("{} {}", user.first_name, user.last_name).trim().to_string(); name } _ => format!("User {}", user_id.as_i64()), } } /// Обработать очередь отложенных user_ids (загрузка имён небольшими порциями) pub async fn process_pending_user_ids(&mut self) { if self.pending_user_ids.is_empty() { return; } // Берём первые N user_ids для загрузки let batch: Vec = self .pending_user_ids .drain(..self.pending_user_ids.len().min(LAZY_LOAD_USERS_PER_TICK)) .collect(); for user_id in batch { if self.user_names.contains_key(&user_id) { continue; // Уже в кэше } match functions::get_user(user_id.as_i64(), self.client_id).await { Ok(user_enum) => { self.handle_user_update(&user_enum); } Err(_) => { // Если не удалось загрузить, сохраняем placeholder self.user_names .insert(user_id, format!("User {}", user_id)); } } } } }