mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
fix(tci): пара «клиент+приёмник» только по факту передачи; перебор имён .part
[P1] FTxClient/FTxRx писались ДО Invoke, и команда, которая ничего не сделала,
всё равно их перебивала. Клиент, уже передающий с приёмника 1, шлёт
trx:0,true,tci — передатчик занят, SyncSetTRX не делает ничего (Started=False),
а маркеры ИДУЩЕЙ передачи с этого мига уходят под номером 0. MSHV такие блоки
отбрасывает (network.cpp:231), то есть передача просто замолкает. Теперь пара
назначается после Invoke и только при Started — тем же признаком, по которому
назначается хозяин эфира. Тот же гейт закрывает близнеца: trx:<N>,true без
',tci' поверх своей же передачи больше не снимает источник модуляции. Снятие
(',false') работает как прежде.
[P2] Имя временного файла (pid + счётчик) уникально внутри процесса, но не
между запусками: «.part», оставшийся от прошлой жизни (публиковать было
нечем), плюс повторно выданный системой pid дают EEXIST на создании — и
задание пропадало молча. TCICreateTempNear перебирает до 64 имён, но только
пока ошибка — «имя занято»: нет прав или каталога перебором не лечится.
Стенд test/tci: 219 проверок (было 217). Новое — занятое имя «.part» записи не
теряет (стенд занимает ровно то имя, которое возьмёт писатель) и пустая
команда TRX не меняет номер приёмника в маркерах. Негативный контроль на обе
правки. Попутно в тесте поправлены два комментария, описывавшие прежнюю
реализацию публикации.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+61
-8
@@ -595,6 +595,20 @@ begin
|
||||
Result := W.Pending = 0;
|
||||
end;
|
||||
|
||||
function NextTempName(const Name: string): string;
|
||||
// Имя «.part», следующее за данным: у писателя счётчик один на процесс, и
|
||||
// стенду нужно занять именно то имя, которое он возьмёт после нашего вызова.
|
||||
var i, p1, p2: Integer;
|
||||
begin
|
||||
p2 := Pos('.part', Name);
|
||||
p1 := p2;
|
||||
for i := p2 - 1 downto 1 do
|
||||
if Name[i] = '-' then begin p1 := i; Break; end;
|
||||
Result := Copy(Name, 1, p1) +
|
||||
IntToStr(StrToIntDef(Copy(Name, p1 + 1, p2 - p1 - 1), 0) + 1) +
|
||||
'.part';
|
||||
end;
|
||||
|
||||
function PartFiles(const Path: string): Integer;
|
||||
// Сколько временных файлов писателя осталось рядом с целью.
|
||||
var SR: TSearchRec;
|
||||
@@ -624,7 +638,7 @@ var
|
||||
Tk: TTCIRecTake;
|
||||
Big: TTCIPcm;
|
||||
Refused, k, Bad: Integer;
|
||||
Path2: string;
|
||||
Path2, Squat: string;
|
||||
begin
|
||||
WriteLn('C. Рекордер линейного выхода');
|
||||
|
||||
@@ -831,9 +845,10 @@ begin
|
||||
finally
|
||||
FS.Free;
|
||||
end;
|
||||
// ★Данные пишутся во временный файл и переименовываются поверх занятого
|
||||
// имени: по имени файла клиент считает запись готовой, и незаконченного
|
||||
// содержимого он там видеть не должен. После удачи «.part» не остаётся.
|
||||
// ★Данные пишутся во временный файл, а под целевым именем он появляется
|
||||
// одним вызовом ядра и уже целым: по имени файла клиент считает запись
|
||||
// готовой, и незаконченного содержимого он там видеть не должен. После
|
||||
// удачи «.part» рядом не остаётся.
|
||||
Check('WAV: временный файл убран', PartFiles(Path) = 0,
|
||||
IntToStr(PartFiles(Path)));
|
||||
|
||||
@@ -849,15 +864,15 @@ begin
|
||||
TCIRecBudgetUsed = Was, IntToStr(TCIRecBudgetUsed - Was));
|
||||
|
||||
// ★Существующий файл не трогаем: имя приходит из сети, и fmCreate затирал
|
||||
// бы любой доступный процессу файл. Пишем поверх заведомо другой длиной —
|
||||
// файл обязан остаться прежним.
|
||||
// бы любой доступный процессу файл. Публикация идёт без замены
|
||||
// (renameat2(RENAME_NOREPLACE) → link), так что занятое имя = отказ. Пишем
|
||||
// поверх заведомо другой длиной — файл обязан остаться прежним.
|
||||
Tk := MakeTake(Copy(Data, 0, 10), 0);
|
||||
W.Enqueue(Path, Tk, 48000);
|
||||
Drained(W, 5000);
|
||||
FS := TFileStream.Create(Path, fmOpenRead);
|
||||
try
|
||||
// Публикация идёт link(2)/MoveFileW без замены: занятое имя = отказ.
|
||||
Check('WAV: существующий файл не перезаписан',
|
||||
Check('WAV: существующий файл не перезаписан',
|
||||
FS.Size = 44 + 2000 * 2, IntToStr(FS.Size));
|
||||
finally
|
||||
FS.Free;
|
||||
@@ -866,6 +881,21 @@ begin
|
||||
DeleteFile(Path);
|
||||
end;
|
||||
|
||||
// ★Имя «.part» уникально внутри процесса (pid + счётчик), но не между
|
||||
// запусками: файл, оставшийся от прошлой жизни (публиковать было нечем),
|
||||
// плюс повторно выданный системой pid дают EEXIST на создании — и задание
|
||||
// пропадало бы молча. Занимаем ровно то имя, которое писатель возьмёт
|
||||
// следующим, и убеждаемся, что запись всё равно легла.
|
||||
DeleteFile(Path);
|
||||
Squat := NextTempName(TCIRecTempName(Path));
|
||||
FS := TFileStream.Create(Squat, fmCreate);
|
||||
FS.Free;
|
||||
W.Enqueue(Path, MakeTake(Data, 0), 48000);
|
||||
Drained(W, 5000);
|
||||
Check('WAV: занятое имя «.part» не теряет запись', FileExists(Path));
|
||||
DeleteFile(Squat);
|
||||
DeleteFile(Path);
|
||||
|
||||
// ★Запись не удалась на полпути — по имени не остаётся НИЧЕГО. Прежний код
|
||||
// не смотрел на результат записи вовсе (FileWrite и THandleStream.Write при
|
||||
// ошибке возвращают 0 и не поднимают исключения), и на полном диске
|
||||
@@ -1788,6 +1818,29 @@ begin
|
||||
Check('TX слайса: маркеры TX_CHRONO идут', n > 0, IntToStr(n));
|
||||
Check('TX слайса: маркер назван номером приёмника (MSHV фильтрует)',
|
||||
(n > 0) and (BadRx = 0), IntToStr(BadRx));
|
||||
|
||||
// ★Команда, которая ничего не сделала, не должна перебивать пару
|
||||
// «клиент + приёмник»: передатчик занят своей же передачей, SyncSetTRX
|
||||
// на неё не отвечает ничем — а если запомнить номер заранее, маркеры
|
||||
// идущей передачи уедут под чужим номером, и клиент замолчит.
|
||||
C.SendText('trx:0,true,tci;');
|
||||
C.WaitText('trx:', 1000);
|
||||
C.Pump(300);
|
||||
n := 0;
|
||||
BadRx := 0;
|
||||
while C.NextFrame(Op, Pay) do
|
||||
if (Op = $02) and (Length(Pay) >= SizeOf(H)) then
|
||||
begin
|
||||
Move(Pay[0], H, SizeOf(H));
|
||||
if H.StreamType = LongWord(Ord(tstTXChrono)) then
|
||||
begin
|
||||
Inc(n);
|
||||
if H.Receiver <> 1 then Inc(BadRx);
|
||||
end;
|
||||
end;
|
||||
Check('TX слайса: пустая команда не сменила номер приёмника',
|
||||
(n > 0) and (BadRx = 0), IntToStr(BadRx));
|
||||
|
||||
C.SendText('trx:1,false;');
|
||||
C.WaitText('trx:', 1000);
|
||||
Ctrl.SetSliceSlotAutoTx(Ctrl.SliceSlotOf(SliceId), False);
|
||||
|
||||
Reference in New Issue
Block a user