Больше двух параметров — каждый на отдельной строке (закрывающая скобка в конце последнего); два и меньше — в одну строку. Правило добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам в 253 файлах.
214 lines
9.6 KiB
C#
214 lines
9.6 KiB
C#
using System.Collections.ObjectModel;
|
||
|
||
namespace Deal.Telegram.Dialogs;
|
||
|
||
/// <summary>
|
||
/// Зеркало каталога диалогов тенанта в памяти telegram-service (план Task 10, Dialogs/DialogCatalog.cs;
|
||
/// Ruling 7: «сервис держит зеркало мониторинга в памяти», ядро — владелец списка мониторинга в своей БД).
|
||
///
|
||
/// Тенант хранит два набора id диалогов: полный каталог (из refresh_dialogs/realtime_sweep, L468–519)
|
||
/// и подмножество с включённым мониторингом. Мониторинг актуализируется тремя путями:
|
||
/// * командой SetMonitor/SetMonitorAll ядра (обновление одной записи / всех сразу);
|
||
/// * ответом Ingress.SyncDialogs (ядро применило entries с autoMonitorNew — L244–246 «_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 L536–546). Команда
|
||
/// приходит от ядра после обновления его БД; зеркало повторяет решение без собственной проверки
|
||
/// «известности» — запись может появиться раньше каталога (включение после рестарта сервиса).
|
||
/// </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;
|
||
}
|
||
}
|