mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
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>
280 lines
7.8 KiB
ObjectPascal
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.
|