Refactor TDLib facade and local time handling

This commit is contained in:
Mikhail Kilin
2026-05-17 17:58:29 +03:00
parent e09b83be69
commit 2e510dc932
38 changed files with 1025 additions and 862 deletions

View File

@@ -1,38 +1,57 @@
//! Trait definition for TdClient to enable dependency injection
//!
//! This trait allows tests to use FakeTdClient instead of real TDLib client.
#![allow(dead_code)]
use crate::tdlib::{AuthState, FolderInfo, MessageInfo, ProfileInfo, UserCache, UserOnlineStatus};
use crate::types::{ChatId, MessageId, UserId};
use async_trait::async_trait;
use std::borrow::Cow;
use std::path::PathBuf;
use tdlib_rs::enums::{ChatAction, Update};
use super::ChatInfo;
/// Trait for TDLib client operations
///
/// This trait defines the interface for both real and fake TDLib clients,
/// enabling dependency injection and easier testing.
#[allow(dead_code)]
/// Auth operations.
#[async_trait]
pub trait TdClientTrait: Send {
// ============ Auth methods ============
pub trait AuthClient: Send {
async fn send_phone_number(&self, phone: String) -> Result<(), String>;
async fn send_code(&self, code: String) -> Result<(), String>;
async fn send_password(&self, password: String) -> Result<(), String>;
}
// ============ Chat methods ============
/// Chat list and profile operations.
#[async_trait]
pub trait ChatClient: Send {
async fn load_chats(&mut self, limit: i32) -> Result<(), String>;
async fn load_folder_chats(&mut self, folder_id: i32, limit: i32) -> Result<(), String>;
async fn leave_chat(&self, chat_id: ChatId) -> Result<(), String>;
async fn get_profile_info(&self, chat_id: ChatId) -> Result<ProfileInfo, String>;
// ============ Chat actions ============
fn chats(&self) -> &[ChatInfo];
fn folders(&self) -> &[FolderInfo];
fn main_chat_list_position(&self) -> i32;
fn set_main_chat_list_position(&mut self, position: i32);
fn update_chats<F>(&mut self, updater: F)
where
F: FnOnce(&mut Vec<ChatInfo>);
fn update_folders<F>(&mut self, updater: F)
where
F: FnOnce(&mut Vec<FolderInfo>);
}
/// Ephemeral chat actions such as typing status.
#[async_trait]
pub trait ChatActionClient: Send {
async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction);
fn clear_stale_typing_status(&mut self) -> bool;
fn typing_status(&self) -> Option<&(UserId, String, std::time::Instant)>;
fn set_typing_status(&mut self, status: Option<(UserId, String, std::time::Instant)>);
}
// ============ Message methods ============
/// Message history, search, and mutation operations.
#[async_trait]
pub trait MessageClient: Send {
async fn get_chat_history(
&mut self,
chat_id: ChatId,
@@ -82,15 +101,38 @@ pub trait TdClientTrait: Send {
async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String>;
fn current_chat_messages(&self) -> Cow<'_, [MessageInfo]>;
fn current_chat_id(&self) -> Option<ChatId>;
fn current_pinned_message(&self) -> Option<MessageInfo>;
fn push_message(&mut self, msg: MessageInfo);
fn clear_current_chat_messages(&mut self);
fn set_current_chat_messages(&mut self, messages: Vec<MessageInfo>);
fn update_current_chat_messages<F>(&mut self, updater: F)
where
F: FnOnce(&mut Vec<MessageInfo>);
fn set_current_chat_id(&mut self, chat_id: Option<ChatId>);
fn set_current_pinned_message(&mut self, msg: Option<MessageInfo>);
fn pending_view_messages(&self) -> &[(ChatId, Vec<MessageId>)];
fn enqueue_pending_view_messages(&mut self, chat_id: ChatId, message_ids: Vec<MessageId>);
async fn fetch_missing_reply_info(&mut self);
async fn process_pending_view_messages(&mut self);
}
// ============ User methods ============
/// User cache and user-status operations.
#[async_trait]
pub trait UserClient: Send {
fn get_user_status_by_chat_id(&self, chat_id: ChatId) -> Option<&UserOnlineStatus>;
fn pending_user_ids(&self) -> &[UserId];
fn user_cache(&self) -> &UserCache;
fn update_user_cache<F>(&mut self, updater: F)
where
F: FnOnce(&mut UserCache);
async fn process_pending_user_ids(&mut self);
}
// ============ Reaction methods ============
/// Message reaction operations.
#[async_trait]
pub trait ReactionClient: Send {
async fn get_message_available_reactions(
&self,
chat_id: ChatId,
@@ -103,52 +145,78 @@ pub trait TdClientTrait: Send {
message_id: MessageId,
reaction: String,
) -> Result<(), String>;
}
// ============ File methods ============
/// File download operations.
#[async_trait]
pub trait FileClient: Send {
async fn download_file(&self, file_id: i32) -> Result<String, String>;
async fn download_voice_note(&self, file_id: i32) -> Result<String, String>;
}
// ============ Getters (immutable) ============
/// Shared client state that does not belong to one feature area.
#[async_trait]
pub trait ClientState: Send {
fn client_id(&self) -> i32;
async fn get_me(&self) -> Result<i64, String>;
fn auth_state(&self) -> &AuthState;
fn chats(&self) -> &[ChatInfo];
fn folders(&self) -> &[FolderInfo];
fn current_chat_messages(&self) -> Vec<MessageInfo>;
fn current_chat_id(&self) -> Option<ChatId>;
fn current_pinned_message(&self) -> Option<MessageInfo>;
fn typing_status(&self) -> Option<&(UserId, String, std::time::Instant)>;
fn pending_view_messages(&self) -> &[(ChatId, Vec<MessageId>)];
fn pending_user_ids(&self) -> &[UserId];
fn main_chat_list_position(&self) -> i32;
fn user_cache(&self) -> &UserCache;
fn network_state(&self) -> super::types::NetworkState;
}
// ============ Setters (mutable) ============
fn chats_mut(&mut self) -> &mut Vec<ChatInfo>;
fn folders_mut(&mut self) -> &mut Vec<FolderInfo>;
fn current_chat_messages_mut(&mut self) -> &mut Vec<MessageInfo>;
fn clear_current_chat_messages(&mut self);
fn set_current_chat_messages(&mut self, messages: Vec<MessageInfo>);
fn set_current_chat_id(&mut self, chat_id: Option<ChatId>);
fn set_current_pinned_message(&mut self, msg: Option<MessageInfo>);
fn set_typing_status(&mut self, status: Option<(UserId, String, std::time::Instant)>);
fn enqueue_pending_view_messages(&mut self, chat_id: ChatId, message_ids: Vec<MessageId>);
fn pending_user_ids_mut(&mut self) -> &mut Vec<UserId>;
fn set_main_chat_list_position(&mut self, position: i32);
fn user_cache_mut(&mut self) -> &mut UserCache;
// ============ Notification methods ============
/// Notification configuration operations.
pub trait NotificationClient: Send {
fn configure_notifications(&mut self, config: &crate::config::NotificationsConfig);
fn sync_notification_muted_chats(&mut self);
}
// ============ Account switching ============
/// Account switching operations.
#[async_trait]
pub trait AccountClient: Send {
/// Recreates the client with a new database path (for account switching).
///
/// For real TdClient: closes old client, creates new one, inits TDLib parameters.
/// For FakeTdClient: no-op.
async fn recreate_client(&mut self, db_path: PathBuf) -> Result<(), String>;
}
// ============ Update handling ============
/// TDLib update routing.
pub trait UpdateClient: Send {
fn handle_update(&mut self, update: Update);
}
/// Facade trait for TDLib client operations
///
/// This trait defines the interface for both real and fake TDLib clients,
/// enabling dependency injection and easier testing.
#[allow(dead_code)]
pub trait TdClientTrait:
AuthClient
+ ChatClient
+ ChatActionClient
+ MessageClient
+ UserClient
+ ReactionClient
+ FileClient
+ ClientState
+ NotificationClient
+ AccountClient
+ UpdateClient
+ Send
{
}
impl<T> TdClientTrait for T where
T: AuthClient
+ ChatClient
+ ChatActionClient
+ MessageClient
+ UserClient
+ ReactionClient
+ FileClient
+ ClientState
+ NotificationClient
+ AccountClient
+ UpdateClient
+ Send
{
}