Files
Deal/src/telegram-service/Deal.Telegram/Dialogs/DialogCatalog.cs
T
Rustam Khalimov 492950bdd0 Отформатировать списки параметров по код-стайлу
Больше двух параметров — каждый на отдельной строке (закрывающая
скобка в конце последнего); два и меньше — в одну строку. Правило
добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам
в 253 файлах.
2026-09-11 13:22:56 +03:00

214 lines
9.6 KiB
C#
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.
using System.Collections.ObjectModel;
namespace Deal.Telegram.Dialogs;
/// <summary>
/// Зеркало каталога диалогов тенанта в памяти telegram-service (план Task 10, Dialogs/DialogCatalog.cs;
/// Ruling 7: «сервис держит зеркало мониторинга в памяти», ядро — владелец списка мониторинга в своей БД).
///
/// Тенант хранит два набора id диалогов: полный каталог (из refresh_dialogs/realtime_sweep, L468519)
/// и подмножество с включённым мониторингом. Мониторинг актуализируется тремя путями:
/// * командой SetMonitor/SetMonitorAll ядра (обновление одной записи / всех сразу);
/// * ответом Ingress.SyncDialogs (ядро применило entries с autoMonitorNew — L244246 «_reload_monitored»);
/// * очисткой после Logout/отключения аккаунта (прототип L203: `_monitored.clear()`).
/// Все операции потокобезопасны; зеркало чисто в памяти — потерю при рестарте догоняет realtime_sweep.
/// </summary>
public sealed class DialogCatalog
{
private readonly object _gate = new();
private readonly Dictionary<string, HashSet<string>> _knownByTenant = new(StringComparer.Ordinal);
private readonly Dictionary<string, HashSet<string>> _monitoredByTenant = new(StringComparer.Ordinal);
/// <summary>
/// Обновляет полный каталог диалогов тенанта (entries refresh/realtime_sweep). Мониторинг при этом
/// не трогается — актуальный monitored-набор приходит ответом SyncDialogs (или командами SetMonitor).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogIds">Актуальные id каталога (entries списка диалогов).</param>
public void ReplaceKnown(string tenantId, IEnumerable<string> dialogIds)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
lock (_gate)
{
_knownByTenant[tenantId] = new HashSet<string>(dialogIds, StringComparer.Ordinal);
}
}
/// <summary>
/// Включает/выключает мониторинг одного диалога (SetMonitor RPC, set_monitor L536546). Команда
/// приходит от ядра после обновления его БД; зеркало повторяет решение без собственной проверки
/// «известности» — запись может появиться раньше каталога (включение после рестарта сервиса).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Id диалога.</param>
/// <param name="enabled">Мониторить (true) или выключить (false).</param>
public void SetMonitored(
string tenantId,
string dialogId,
bool enabled)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentException.ThrowIfNullOrWhiteSpace(dialogId);
lock (_gate)
{
HashSet<string> monitored = MonitoredSet(tenantId);
if (enabled)
{
monitored.Add(dialogId);
}
else
{
monitored.Remove(dialogId);
}
}
}
/// <summary>
/// Заменяет monitored-набор ответом SyncDialogs (актуальный список ядра после SyncFromTelegram —
/// авто-мониторинг новых/удаление отсутствующих, Ruling 7). Это авторитетный источник зеркала.
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <param name="monitoredIds">Id диалогов с включённым мониторингом.</param>
public void ReplaceMonitored(string tenantId, IEnumerable<string> monitoredIds)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
lock (_gate)
{
_monitoredByTenant[tenantId] = new HashSet<string>(monitoredIds, StringComparer.Ordinal);
}
}
/// <summary>
/// Включает/выключает мониторинг всех диалогов каталога (SetMonitorAll RPC, set_monitor_all
/// L548–567). При включении учитываются и уже мониторящиеся записи (каталог может отставать от БД
/// ядра после рестарта — «монитор» из ответа SyncDialogs приедет следующим циклом realtime_sweep).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <param name="enabled">Мониторить все (true) или снять мониторинг со всех (false).</param>
/// <returns>Сколько диалогов в каталоге тенанта (SetMonitorAllReply.count).</returns>
public int SetAllMonitored(string tenantId, bool enabled)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
lock (_gate)
{
HashSet<string> known = KnownSet(tenantId);
if (enabled)
{
HashSet<string> monitored = MonitoredSet(tenantId);
monitored.UnionWith(known);
}
else
{
_monitoredByTenant[tenantId] = new HashSet<string>(StringComparer.Ordinal);
}
return known.Count;
}
}
/// <summary>
/// Проверяет, мониторится ли диалог (фильтр realtime-событий L262 и догона sweep L429).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Id диалога.</param>
/// <returns>True — диалог в monitored-наборе тенанта.</returns>
public bool IsMonitored(string tenantId, string dialogId)
{
if (string.IsNullOrWhiteSpace(tenantId) || string.IsNullOrWhiteSpace(dialogId))
{
return false;
}
lock (_gate)
{
return _monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored) && monitored.Contains(dialogId);
}
}
/// <summary>
/// Сколько диалогов знает каталог тенанта (ответ monitor-all, count каталога).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <returns>Размер каталога; 0 — каталог ещё не синхронизирован (нет refresh/sweep).</returns>
public int KnownCount(string tenantId)
{
if (string.IsNullOrWhiteSpace(tenantId))
{
return 0;
}
lock (_gate)
{
return _knownByTenant.TryGetValue(tenantId, out HashSet<string>? known) ? known.Count : 0;
}
}
/// <summary>
/// Возвращает копию мониторящихся id тенанта (для логов/тестов).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
/// <returns>Набор мониторящихся id (пустой, если тенанта нет).</returns>
public IReadOnlyCollection<string> SnapshotMonitored(string tenantId)
{
if (string.IsNullOrWhiteSpace(tenantId))
{
return Array.Empty<string>();
}
lock (_gate)
{
return _monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored)
? new ReadOnlyCollection<string>(monitored.ToArray())
: Array.Empty<string>();
}
}
/// <summary>
/// Сбрасывает состояние тенанта (Logout/отключение аккаунта — прототип L203).
/// </summary>
/// <param name="tenantId">Id тенанта.</param>
public void Reset(string tenantId)
{
if (string.IsNullOrWhiteSpace(tenantId))
{
return;
}
lock (_gate)
{
_knownByTenant.Remove(tenantId);
_monitoredByTenant.Remove(tenantId);
}
}
// Полный каталог тенанта (создаёт пустой при первом обращении). Вызывается под _gate.
// tenantId: Id тенанта.
private HashSet<string> KnownSet(string tenantId)
{
if (!_knownByTenant.TryGetValue(tenantId, out HashSet<string>? known))
{
known = new HashSet<string>(StringComparer.Ordinal);
_knownByTenant[tenantId] = known;
}
return known;
}
// Monitored-набор тенанта (создаёт пустой при первом обращении). Вызывается под _gate.
// tenantId: Id тенанта.
private HashSet<string> MonitoredSet(string tenantId)
{
if (!_monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored))
{
monitored = new HashSet<string>(StringComparer.Ordinal);
_monitoredByTenant[tenantId] = monitored;
}
return monitored;
}
}