mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
fix(dxcluster): резолв имени, гейт логина, TTL и мелочи UI по итогам ревизии
Сеть и резолв (DXClusterClient): - Резолв ушёл в отдельный поток: системный резолвер блокирующий и не прерывается, а сокета в этот момент ещё нет — Stop из UI-потока висел на DNS-таймауте. Сессия ждёт квантами по 200 мс, просыпаясь на FStopEvent; Stop теперь отрабатывает за 100-200 мс в любой фазе. - Реестр запросов: по одному резолверу на имя, не больше DX_MAX_RESOLVERS. Не дождавшись, сессия оставляет запрос в реестре и на следующей попытке цепляется к нему же (иначе зависший резолвер плодил бы вечные потоки). Слотов несколько, чтобы смена адреса работала поверх зависшего прежнего; литеральный IP разбирается до реестра — ввод адреса руками обязан работать всегда. Запись живёт по счётчику ссылок, исключение в резолвере не оставляет слот занятым. - getaddrinfo вместо netdb.ResolveHostByName: тот ходит в DNS сам и /etc/hosts не читает вовсе (getent находит localhost, ResolveHostByName — нет), т.е. локальный алиас кластера не работал. Заодно реентерабельно. - WSAStartup перенесён в initialization, WSACleanup убран: Stop не ждёт резолвер, а тот может сидеть в gethostbyname. Логин (DXClusterClient): - Команды пользователя больше не уходят в незавершённый логин: очередь разбирается только после post-login, SendCommand говорит в лог, что команда ждёт. - ONLINE не по таймеру, а по существу: LoginSettled требует ответа сервера после учётки (с потолком молчания), пароль ждёт своего приглашения. Подтверждение ставится ПОСЛЕ разбора куска, а не на приход байтов — иначе исход логина зависел от границ TCP-пакетов. - Приглашения и отказы: строгий детектор (текст, заканчивающийся двоеточием) и для отправки пароля, и для вердикта — по вхождению слова пароль улетал командой от строки приветствия. Повтор приглашения пароля или позывного = отказ авторизации (dxsError, без реконнекта); опоздавшее приглашение после слепой отправки позывного отказом не считается. Эхо уже отвеченного приглашения гасится окном в одну строку. - Ошибка отправки post-login рвёт сессию, а не только цикл команд; неотправленная очередь возвращается на следующее соединение. UI и данные: - QSY по споту крутит активный VFO, а не всегда A (MainForm). - TTL спотов чистится тиком независимо от видимости оверлея (DXSpotStore .Purge + ServiceDXCluster). - Выделение в окне списка держится по позывному И частоте: один позывной живёт на разных диапазонах (DXClusterForm). - Кнопка DX правит и отложенную копию настроек, иначе debounce SETUP возвращал прежнее состояние подписей (MainForm). Проверено на фейковом кластере: приглашения с CRLF и без, границы TCP-пакетов, отказ по паролю и по позывному, опоздавшее приглашение, ловушки ложного срабатывания. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -5168,9 +5168,16 @@ begin
|
||||
if FDXPending and (MilliSecondsBetween(Now, FDXPendingAt) >= DX_SETTINGS_QUIET_MS) then
|
||||
CommitDXClusterSettings;
|
||||
|
||||
if FDXSpotOverlay = nil then Exit;
|
||||
if SecondsBetween(Now, FDXAgeTickAt) < DX_AGE_TICK_SEC then Exit;
|
||||
FDXAgeTickAt := Now;
|
||||
|
||||
// TTL — свойство базы, а не картинки. Стор чистится только внутри Add и
|
||||
// снимков, поэтому с выключенными подписями и молчащим (или отключённым)
|
||||
// кластером просроченные споты не выбрасывал бы никто: новых Add нет, версия
|
||||
// стора не меняется, окно списка снимок не берёт, тик оверлея выключен.
|
||||
if FDXStore <> nil then FDXStore.Purge;
|
||||
|
||||
if FDXSpotOverlay = nil then Exit;
|
||||
if not FDXSpotOverlay.Active then Exit;
|
||||
// Тик получают оверлеи всех панов: возраст спота от пана не зависит.
|
||||
for i := 0 to MAX_PANS - 1 do
|
||||
@@ -5198,6 +5205,10 @@ begin
|
||||
FDXCfg.ShowSpots := not FDXCfg.ShowSpots;
|
||||
ApplyDXClusterSettings(FDXCfg);
|
||||
FController.FSettings.SaveDXClusterSettings(FDXCfg);
|
||||
// Правки SETUP ждут тишины в СВОЕЙ копии конфига. Не поправив её, отложенный
|
||||
// коммит записал бы старый FDXPendingCfg целиком и молча вернул прежнее
|
||||
// состояние тумблера — на экране новое, в файле старое.
|
||||
if FDXPending then FDXPendingCfg.ShowSpots := FDXCfg.ShowSpots;
|
||||
// Открытый SETUP держит СВОЮ копию конфига: без синхронизации галка «Show
|
||||
// spots» осталась бы старой, и первая же правка любого поля на этой странице
|
||||
// вернула бы подписи обратно.
|
||||
@@ -5240,10 +5251,15 @@ end;
|
||||
|
||||
procedure TMainForm.OnDXTuneSpot(FreqHz: Double; Mode: TDXMode);
|
||||
// QSY по споту: клик по подписи на главном пане или двойной клик в окне списка.
|
||||
// Крутим ТОТ VFO, который сейчас активен, — ровно как обычный клик по спектру
|
||||
// (DoSpectrumClick): иначе при активном B спот уводил бы неактивный A.
|
||||
var NewMode: Integer;
|
||||
begin
|
||||
if FreqHz <= 0 then Exit;
|
||||
ApplyVfoA(Round(FreqHz));
|
||||
if FController.FActiveVfo = 0 then
|
||||
ApplyVfoA(Round(FreqHz))
|
||||
else
|
||||
FController.SetVfoB(Round(FreqHz)); // рендер через OnControllerState(rfVfoB)
|
||||
NewMode := DXSpotRadioMode(FreqHz, Mode);
|
||||
if (NewMode >= 0) and (NewMode <> FController.FMode) then
|
||||
FController.SetMode(NewMode);
|
||||
|
||||
Reference in New Issue
Block a user