Files
ewsdr/DeviceStore.pas
T
Uladzimir KarpenkaandClaude Opus 4.8 7f7f429515 Phase 3 (batch 31a): shared TDeviceStore for saved+discovered devices
Introduce DeviceStore.pas (TDeviceStore) as the single source of truth for
saved devices (hpsdr_devices.ini CRUD + autostart) and the current discovery
list. The controller owns one instance (created/freed in its ctor/dtor);
desktop and (later) web frontends edit/render through it so the lists never
diverge.

Refactor TDeviceDialog to use a TDeviceStore reference instead of its own
arrays + ini code. MainForm wires FDeviceDialog.Store to the controller's
store, routes discovery results (DoAddDevice) and the preload-rate lookup
through it (TDiscoveredDevice now carries the MAC), and drops the now-unused
FDevices/TDeviceItem. Behavior-preserving for the desktop dialog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:36:43 +03:00

280 lines
7.8 KiB
ObjectPascal

unit DeviceStore;
{$mode objfpc}{$H+}
// Общее хранилище устройств — единый источник правды для десктоп-диалога
// (DeviceForm) и web-оверлея. Владеет сохранёнными устройствами (persist в
// hpsdr_devices.ini) и текущим списком найденных при discovery.
// Контроллер (TRadioController) держит один экземпляр; фронтенды работают
// через него, поэтому списки не расходятся.
interface
uses
Classes, SysUtils, IniFiles, BoardUtils, PlatformUtils;
const
DEVICE_CFG_NAME = 'hpsdr_devices.ini';
type
// Запись о сохранённом устройстве
TSavedDevice = record
Name: string; // пользовательское имя
IPAddress: string;
BoardType: Integer;
AutoStart: Boolean; // запускать автоматически при старте
end;
// Найденное устройство (результат discovery)
TDiscoveredDevice = record
IPAddress: string;
DisplayName: string;
BoardType: Integer;
MAC: array[0..5] of Byte; // нужен для preload-rate lookup (LoadDevice по MAC)
end;
{ TDeviceStore }
TDeviceStore = class
private
FSaved: array of TSavedDevice;
FSavedCount: Integer;
FDiscovered: array of TDiscoveredDevice;
FDiscoveredCount: Integer;
public
constructor Create;
// ---- Сохранённые устройства ----
procedure LoadSaved;
procedure SaveSaved;
function SavedCount: Integer;
function Saved(Idx: Integer): TSavedDevice;
// Добавляет запись (Name пустое → 'HPSDR'); возвращает индекс новой записи.
function AddSaved(const AName, IP: string; BoardType: Integer): Integer;
procedure RemoveSaved(Idx: Integer);
procedure SetAutoStart(Idx: Integer); // эксклюзивно: только одно autostart
function AutoStartIP: string;
function AutoStartBoardType: Integer;
function SavedBoardType(Idx: Integer): Integer;
function SavedDisplay(Idx: Integer): string; // строка для списка (с * и board)
// ---- Найденные (discovery) ----
procedure ClearDiscovered;
procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
function DiscoveredCount: Integer;
function Discovered(Idx: Integer): TDiscoveredDevice;
// IP найденного устройства по IP (для preload-rate lookup и т.п.)
function FindDiscoveredByIP(const IP: string): Integer;
end;
implementation
{ TDeviceStore }
constructor TDeviceStore.Create;
begin
inherited Create;
FSavedCount := 0;
FDiscoveredCount := 0;
LoadSaved;
end;
procedure TDeviceStore.LoadSaved;
var
Ini: TIniFile;
I, N: Integer;
Section: string;
begin
FSavedCount := 0;
SetLength(FSaved, 0);
if not FileExists(GetAppCfgDir + DEVICE_CFG_NAME) then Exit;
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
try
N := Ini.ReadInteger('Devices', 'Count', 0);
SetLength(FSaved, N);
for I := 0 to N - 1 do
begin
Section := 'Device' + IntToStr(I);
FSaved[I].Name := Ini.ReadString (Section, 'Name', 'HPSDR');
FSaved[I].IPAddress := Ini.ReadString (Section, 'IP', '');
FSaved[I].BoardType := Ini.ReadInteger(Section, 'BoardType', 0);
FSaved[I].AutoStart := Ini.ReadBool (Section, 'AutoStart', False);
Inc(FSavedCount);
end;
finally
Ini.Free;
end;
end;
procedure TDeviceStore.SaveSaved;
var
Ini: TIniFile;
I: Integer;
Section: string;
begin
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
try
Ini.WriteInteger('Devices', 'Count', FSavedCount);
for I := 0 to FSavedCount - 1 do
begin
Section := 'Device' + IntToStr(I);
Ini.WriteString (Section, 'Name', FSaved[I].Name);
Ini.WriteString (Section, 'IP', FSaved[I].IPAddress);
Ini.WriteInteger(Section, 'BoardType', FSaved[I].BoardType);
Ini.WriteBool (Section, 'AutoStart', FSaved[I].AutoStart);
end;
finally
Ini.Free;
end;
end;
function TDeviceStore.SavedCount: Integer;
begin
Result := FSavedCount;
end;
function TDeviceStore.Saved(Idx: Integer): TSavedDevice;
begin
if (Idx >= 0) and (Idx < FSavedCount) then
Result := FSaved[Idx]
else
begin
Result.Name := ''; Result.IPAddress := '';
Result.BoardType := 0; Result.AutoStart := False;
end;
end;
function TDeviceStore.AddSaved(const AName, IP: string; BoardType: Integer): Integer;
begin
Result := FSavedCount;
Inc(FSavedCount);
SetLength(FSaved, FSavedCount);
if Trim(AName) <> '' then FSaved[Result].Name := Trim(AName)
else FSaved[Result].Name := 'HPSDR';
FSaved[Result].IPAddress := Trim(IP);
FSaved[Result].BoardType := BoardType;
FSaved[Result].AutoStart := False;
SaveSaved;
end;
procedure TDeviceStore.RemoveSaved(Idx: Integer);
var
I: Integer;
begin
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
for I := Idx to FSavedCount - 2 do
FSaved[I] := FSaved[I + 1];
Dec(FSavedCount);
SetLength(FSaved, FSavedCount);
SaveSaved;
end;
procedure TDeviceStore.SetAutoStart(Idx: Integer);
var
I: Integer;
begin
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
// Только одно устройство может быть AutoStart
for I := 0 to FSavedCount - 1 do
FSaved[I].AutoStart := (I = Idx);
SaveSaved;
end;
function TDeviceStore.AutoStartIP: string;
var
I: Integer;
begin
Result := '';
for I := 0 to FSavedCount - 1 do
if FSaved[I].AutoStart then
begin
Result := FSaved[I].IPAddress;
Exit;
end;
end;
function TDeviceStore.AutoStartBoardType: Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to FSavedCount - 1 do
if FSaved[I].AutoStart then
begin
Result := FSaved[I].BoardType;
Exit;
end;
end;
function TDeviceStore.SavedBoardType(Idx: Integer): Integer;
begin
if (Idx >= 0) and (Idx < FSavedCount) then
Result := FSaved[Idx].BoardType
else
Result := 0;
end;
function TDeviceStore.SavedDisplay(Idx: Integer): string;
begin
Result := '';
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
Result := FSaved[Idx].Name + ' [' + FSaved[Idx].IPAddress + ']';
if FSaved[Idx].BoardType > 0 then
Result := Result + ' ' + BoardTypeName(FSaved[Idx].BoardType);
if FSaved[Idx].AutoStart then
Result := '* ' + Result;
end;
procedure TDeviceStore.ClearDiscovered;
begin
FDiscoveredCount := 0;
SetLength(FDiscovered, 0);
end;
procedure TDeviceStore.AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
var
Idx, J: Integer;
begin
Idx := FDiscoveredCount;
Inc(FDiscoveredCount);
SetLength(FDiscovered, FDiscoveredCount);
FDiscovered[Idx].IPAddress := IP;
FDiscovered[Idx].DisplayName := DisplayName;
FDiscovered[Idx].BoardType := BoardType;
FillChar(FDiscovered[Idx].MAC, SizeOf(FDiscovered[Idx].MAC), 0);
for J := 0 to High(MAC) do
if J <= 5 then FDiscovered[Idx].MAC[J] := MAC[J];
end;
function TDeviceStore.DiscoveredCount: Integer;
begin
Result := FDiscoveredCount;
end;
function TDeviceStore.Discovered(Idx: Integer): TDiscoveredDevice;
begin
if (Idx >= 0) and (Idx < FDiscoveredCount) then
Result := FDiscovered[Idx]
else
begin
Result.IPAddress := ''; Result.DisplayName := ''; Result.BoardType := 0;
end;
end;
function TDeviceStore.FindDiscoveredByIP(const IP: string): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to FDiscoveredCount - 1 do
if SameText(FDiscovered[I].IPAddress, IP) then
begin
Result := I;
Exit;
end;
end;
end.