Files
telegram-tui/src/tdlib/users.rs
Mikhail Kilin 644e36597d fixes
2026-01-31 03:48:50 +03:00

207 lines
7.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<V> {
map: HashMap<UserId, V>,
/// Порядок доступа: последний элемент — самый недавно использованный
order: Vec<UserId>,
capacity: usize,
}
impl<V: Clone> LruCache<V> {
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<String>,
/// LRU-кэш имён: user_id -> display_name (first_name + last_name)
pub user_names: LruCache<String>,
/// Связь chat_id -> user_id для приватных чатов
pub chat_user_ids: HashMap<ChatId, UserId>,
/// Очередь user_id для загрузки имён
pub pending_user_ids: Vec<UserId>,
/// LRU-кэш онлайн-статусов пользователей: user_id -> status
pub user_statuses: LruCache<UserOnlineStatus>,
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<UserId> {
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<UserId> = 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));
}
}
}
}
}