fix: implement dynamic message history loading with retry logic

Проблема:
- При открытии чата видно только последнее сообщение
- TDLib возвращал 1 сообщение при первом запросе
- Не было retry логики для ожидания синхронизации с сервером

Решение:
1. Динамическая загрузка с retry (до 20 попыток на чанк)
2. Загрузка всей доступной истории (без лимита)
3. Retry при получении малого количества сообщений
4. Корректная чанковая загрузка по 50 сообщений

Алгоритм:
- При открытии чата: get_chat_history(i32::MAX) - загружает всё
- Чанками по 50: TDLIB_MESSAGE_LIMIT
- Retry если получено < 50 при первой загрузке
- Остановка если 3 раза подряд пусто
- Порядок: старые чанки вставляются в начало (splice)
- При скролле: load_older_messages_if_needed() подгружает автоматически

Изменения:
src/tdlib/messages.rs:
- Убрана фиксированная задержка 100ms после open_chat
- Добавлен счетчик consecutive_empty_results
- Retry логика без искусственных sleep()
- Проверка: если получено мало - продолжить попытки

src/input/main_input.rs:
- limit: 100 → i32::MAX (без ограничений)
- timeout: 10s → 30s

tests/chat_list.rs:
- test_chat_history_chunked_loading: проверка 100, 120, 200 сообщений
- test_chat_history_loads_all_without_limit: загрузка 200 без лимита
- test_load_older_messages_pagination: подгрузка при скролле

Все тесты: 104/104 

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-02-04 03:56:44 +03:00
parent 222a21770c
commit 72c4a886fa
4 changed files with 120 additions and 25 deletions

View File

@@ -1124,9 +1124,10 @@ async fn open_chat_and_load_data<T: TdClientTrait>(app: &mut App<T>, chat_id: i6
app.status_message = Some("Загрузка сообщений...".to_string());
app.message_scroll_offset = 0;
// Загружаем все доступные сообщения (без лимита)
match with_timeout_msg(
Duration::from_secs(10),
app.td_client.get_chat_history(ChatId::new(chat_id), 100),
Duration::from_secs(30),
app.td_client.get_chat_history(ChatId::new(chat_id), i32::MAX),
"Таймаут загрузки сообщений",
)
.await

View File

@@ -97,29 +97,26 @@ impl MessageManager {
}
}
/// Загружает историю сообщений чата с автоматической чанковой подгрузкой.
/// Загружает историю сообщений чата с динамической подгрузкой.
///
/// Автоматически подгружает сообщения чанками по [`TDLIB_MESSAGE_LIMIT`] штук,
/// пока не будет достигнут запрошенный `limit` или пока не кончатся сообщения.
/// Загружает сообщения чанками, ожидая пока TDLib синхронизирует их с сервера.
/// Продолжает загрузку пока не будет достигнут `limit` или пока TDLib отдает сообщения.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `limit` - Желаемое количество сообщений (может быть > [`TDLIB_MESSAGE_LIMIT`])
/// * `limit` - Желаемое минимальное количество сообщений (для заполнения экрана)
///
/// # Returns
///
/// * `Ok(Vec<MessageInfo>)` - Список сообщений (от старых к новым, до `limit` штук)
/// * `Ok(Vec<MessageInfo>)` - Список сообщений (от старых к новым)
/// * `Err(String)` - Ошибка загрузки
///
/// # Examples
///
/// ```ignore
/// let messages = msg_manager.get_chat_history(
/// ChatId::new(123),
/// 50
/// ).await?;
/// println!("Loaded {} messages", messages.len());
/// // Загрузить достаточно сообщений для экрана высотой 30 строк
/// let messages = msg_manager.get_chat_history(chat_id, 30).await?;
/// ```
pub async fn get_chat_history(
&mut self,
@@ -132,8 +129,7 @@ impl MessageManager {
// Это сообщает TDLib что пользователь открыл чат и нужно загрузить историю
let _ = functions::open_chat(chat_id.as_i64(), self.client_id).await;
// Даём TDLib время на синхронизацию (загрузку истории с сервера)
sleep(Duration::from_millis(100)).await;
// Открываем чат - TDLib начнет синхронизацию автоматически
// НЕ устанавливаем current_chat_id здесь!
// Он будет установлен снаружи ПОСЛЕ сохранения истории
@@ -141,7 +137,8 @@ impl MessageManager {
let mut all_messages = Vec::new();
let mut from_message_id = 0i64; // 0 = начинаем с последних сообщений
let max_retries_per_chunk = 3;
let max_attempts_per_chunk = 20; // Максимум попыток на чанк
let mut consecutive_empty_results = 0; // Счетчик пустых результатов подряд
// Загружаем чанками по TDLIB_MESSAGE_LIMIT пока не достигнем limit
while (all_messages.len() as i32) < limit {
@@ -150,8 +147,8 @@ impl MessageManager {
let mut chunk_loaded = false;
// Пробуем загрузить чанк несколько раз (TDLib может подгружать с сервера)
for attempt in 1..=max_retries_per_chunk {
// Пробуем загрузить чанк (TDLib подгружает с сервера по мере готовности)
for attempt in 1..=max_attempts_per_chunk {
let result = functions::get_chat_history(
chat_id.as_i64(),
from_message_id,
@@ -175,15 +172,28 @@ impl MessageManager {
}
};
let received_count = messages_obj.messages.len();
// Если получили пустой результат
if messages_obj.messages.is_empty() {
// Если это первая загрузка и не последняя попытка - пробуем еще раз
if all_messages.is_empty() && attempt < max_retries_per_chunk {
sleep(Duration::from_millis(200)).await;
continue;
consecutive_empty_results += 1;
// Если несколько раз подряд пусто - прерываем
if consecutive_empty_results >= 3 {
break;
}
// Иначе прерываем - больше нет сообщений
break;
// Пробуем еще раз
continue;
}
// Получили сообщения - сбрасываем счетчик
consecutive_empty_results = 0;
// Если это первая загрузка и получили мало сообщений - продолжаем попытки
// TDLib может подгружать данные с сервера постепенно
if all_messages.is_empty() &&
received_count < (chunk_size as usize) &&
attempt < max_attempts_per_chunk {
continue;
}
// Конвертируем сообщения (от новых к старым, потом реверсим)
@@ -230,7 +240,7 @@ impl MessageManager {
break;
}
}
Ok(all_messages)
}