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>
This commit is contained in:
Uladzimir Karpenka
2026-06-08 12:36:43 +03:00
co-authored by Claude Opus 4.8
parent 783c8976c9
commit 7f7f429515
4 changed files with 368 additions and 212 deletions
+65 -175
View File
@@ -6,21 +6,10 @@ interface
uses
Classes, SysUtils, FlatButton, FlatEdit, FlatListBox, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, IniFiles,
BoardUtils, PlatformUtils;
const
DEVICE_CFG_NAME = 'hpsdr_devices.ini';
StdCtrls, ExtCtrls, ComCtrls,
BoardUtils, PlatformUtils, DeviceStore;
type
// Запись о сохранённом устройстве
TSavedDevice = record
Name: string; // пользовательское имя
IPAddress: string;
BoardType: Integer;
AutoStart: Boolean; // запускать автоматически при старте
end;
// Результат диалога
TDeviceDialogResult = record
Accepted: Boolean;
@@ -31,17 +20,11 @@ type
{ TDeviceDialog }
TDeviceDialog = class(TForm)
private
// Сохранённые устройства
FSavedDevices: array of TSavedDevice;
FSavedCount: Integer;
// Общее хранилище устройств (saved+discovered) — единый источник правды,
// владелец контроллер; диалог только рендерит и редактирует через него.
FStore: TDeviceStore;
FResult: TDeviceDialogResult;
// Discovered devices (IP strings)
FDiscoveredIPs: array of string;
FDiscoveredNames: array of string;
FDiscoveredBoardTypes: array of Integer;
FDiscoveredCount: Integer;
// UI
PanelTop: TPanel;
PanelBottom: TPanel;
@@ -70,8 +53,7 @@ type
procedure BuildUI;
procedure ApplyTheme;
procedure LoadSaved;
procedure SaveSaved;
procedure SetStore(AStore: TDeviceStore);
procedure RefreshSavedList;
procedure BtnDiscoverClick(Sender: TObject);
@@ -95,8 +77,10 @@ type
procedure SetTheme(const T: TAppTheme);
// Добавить найденное устройство (вызывается из MainForm при discovery)
procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer = 0);
procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
procedure ClearDiscovered;
procedure NoDevicesFound; // UI-сообщение «нет устройств» в список найденных
// Автозапуск: возвращает IP если есть устройство с AutoStart=True
function GetAutoStartIP: string;
@@ -105,9 +89,11 @@ type
// Получить/сбросить результат
procedure ClearResult;
// Общее хранилище устройств — назначается владельцем (MainForm/контроллер)
// до показа диалога; диалог рендерит и редактирует через него.
property Store: TDeviceStore read FStore write SetStore;
property DialogResult: TDeviceDialogResult read FResult;
property OnDiscover: TNotifyEvent read FOnDiscover write FOnDiscover;
property SavedCount: Integer read FSavedCount;
end;
implementation
@@ -145,14 +131,17 @@ begin
Font.Size := 8;
Font.Color := CLR_TEXT;
FSavedCount := 0;
FDiscoveredCount := 0;
FStore := nil; // назначается владельцем через property Store до показа
FResult.Accepted := False;
BuildUI;
SetTheme(DarkTheme);
LoadSaved;
RefreshSavedList;
end;
procedure TDeviceDialog.SetStore(AStore: TDeviceStore);
begin
FStore := AStore;
if FStore <> nil then RefreshSavedList;
end;
function TDeviceDialog.MakeBtn(AParent: TWinControl; const Cap: string;
@@ -348,80 +337,26 @@ begin
Invalidate;
end;
procedure TDeviceDialog.LoadSaved;
var
Ini: TIniFile;
I, N: Integer;
Section: string;
begin
FSavedCount := 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(FSavedDevices, N);
for I := 0 to N - 1 do
begin
Section := 'Device' + IntToStr(I);
FSavedDevices[I].Name := Ini.ReadString (Section, 'Name', 'HPSDR');
FSavedDevices[I].IPAddress := Ini.ReadString (Section, 'IP', '');
FSavedDevices[I].BoardType := Ini.ReadInteger(Section, 'BoardType', 0);
FSavedDevices[I].AutoStart := Ini.ReadBool (Section, 'AutoStart', False);
Inc(FSavedCount);
end;
finally
Ini.Free;
end;
end;
procedure TDeviceDialog.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', FSavedDevices[I].Name);
Ini.WriteString (Section, 'IP', FSavedDevices[I].IPAddress);
Ini.WriteInteger(Section, 'BoardType', FSavedDevices[I].BoardType);
Ini.WriteBool (Section, 'AutoStart', FSavedDevices[I].AutoStart);
end;
finally
Ini.Free;
end;
end;
procedure TDeviceDialog.RefreshSavedList;
var
I: Integer;
S: string;
begin
LstSaved.Items.Clear;
for I := 0 to FSavedCount - 1 do
begin
S := FSavedDevices[I].Name + ' [' + FSavedDevices[I].IPAddress + ']';
if FSavedDevices[I].BoardType > 0 then
S := S + ' ' + BoardTypeName(FSavedDevices[I].BoardType);
if FSavedDevices[I].AutoStart then
S := '* ' + S;
LstSaved.Items.Add(S);
end;
if FStore = nil then Exit;
for I := 0 to FStore.SavedCount - 1 do
LstSaved.Items.Add(FStore.SavedDisplay(I));
end;
procedure TDeviceDialog.LstSavedClick(Sender: TObject);
var
Idx: Integer;
Dev: TSavedDevice;
begin
Idx := LstSaved.ItemIndex;
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
EdName.Text := FSavedDevices[Idx].Name;
EdIP.Text := FSavedDevices[Idx].IPAddress;
if (FStore = nil) or (Idx < 0) or (Idx >= FStore.SavedCount) then Exit;
Dev := FStore.Saved(Idx);
EdName.Text := Dev.Name;
EdIP.Text := Dev.IPAddress;
end;
procedure TDeviceDialog.LstSavedDblClick(Sender: TObject);
@@ -444,29 +379,20 @@ end;
procedure TDeviceDialog.ClearDiscovered;
begin
FDiscoveredCount := 0;
SetLength(FDiscoveredIPs, 0);
SetLength(FDiscoveredNames, 0);
SetLength(FDiscoveredBoardTypes, 0);
if FStore <> nil then FStore.ClearDiscovered;
LstFound.Items.Clear;
end;
procedure TDeviceDialog.AddDiscovered(const IP, DisplayName: string; BoardType: Integer = 0);
procedure TDeviceDialog.AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
var
Idx: Integer;
S: string;
S: string;
begin
if FStore = nil then Exit;
if (LstFound.Items.Count = 1) and (LstFound.Items[0] = 'Searching...') then
LstFound.Items.Clear;
Idx := FDiscoveredCount;
Inc(FDiscoveredCount);
SetLength(FDiscoveredIPs, FDiscoveredCount);
SetLength(FDiscoveredNames, FDiscoveredCount);
SetLength(FDiscoveredBoardTypes, FDiscoveredCount);
FDiscoveredIPs[Idx] := IP;
FDiscoveredNames[Idx] := DisplayName;
FDiscoveredBoardTypes[Idx] := BoardType;
FStore.AddDiscovered(IP, DisplayName, BoardType, MAC);
S := DisplayName;
if BoardType > 0 then
@@ -474,44 +400,34 @@ begin
LstFound.Items.Add(S);
end;
procedure TDeviceDialog.NoDevicesFound;
begin
ClearDiscovered;
LstFound.Items.Add('-- no device found --');
end;
procedure TDeviceDialog.BtnAddClick(Sender: TObject);
var
Idx: Integer;
begin
if FStore = nil then Exit;
if Trim(EdIP.Text) = '' then
begin
ShowMessage('Enter IP address');
Exit;
end;
Idx := FSavedCount;
Inc(FSavedCount);
SetLength(FSavedDevices, FSavedCount);
FSavedDevices[Idx].Name := Trim(EdName.Text);
if FSavedDevices[Idx].Name = '' then
FSavedDevices[Idx].Name := 'HPSDR';
FSavedDevices[Idx].IPAddress := Trim(EdIP.Text);
FSavedDevices[Idx].BoardType := 0;
FSavedDevices[Idx].AutoStart := False;
SaveSaved;
Idx := FStore.AddSaved(EdName.Text, EdIP.Text, 0);
RefreshSavedList;
LstSaved.ItemIndex := Idx;
end;
procedure TDeviceDialog.BtnRemoveClick(Sender: TObject);
var
Idx, I: Integer;
Idx: Integer;
begin
Idx := LstSaved.ItemIndex;
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
for I := Idx to FSavedCount - 2 do
FSavedDevices[I] := FSavedDevices[I + 1];
Dec(FSavedCount);
SetLength(FSavedDevices, FSavedCount);
SaveSaved;
if (FStore = nil) or (Idx < 0) or (Idx >= FStore.SavedCount) then Exit;
FStore.RemoveSaved(Idx);
RefreshSavedList;
EdName.Text := '';
EdIP.Text := '';
@@ -519,45 +435,34 @@ end;
procedure TDeviceDialog.BtnSetAutoClick(Sender: TObject);
var
Idx, I: Integer;
Idx: Integer;
begin
Idx := LstSaved.ItemIndex;
if (Idx < 0) or (Idx >= FSavedCount) then
if (FStore = nil) or (Idx < 0) or (Idx >= FStore.SavedCount) then
begin
ShowMessage('Select a device first');
Exit;
end;
// Только одно устройство может быть AutoStart
for I := 0 to FSavedCount - 1 do
FSavedDevices[I].AutoStart := (I = Idx);
SaveSaved;
FStore.SetAutoStart(Idx);
RefreshSavedList;
LstSaved.ItemIndex := Idx;
end;
procedure TDeviceDialog.BtnAddFoundClick(Sender: TObject);
var
Idx: Integer;
Idx, NewIdx: Integer;
D: TDiscoveredDevice;
begin
Idx := LstFound.ItemIndex;
if (Idx < 0) or (Idx >= FDiscoveredCount) then
if (FStore = nil) or (Idx < 0) or (Idx >= FStore.DiscoveredCount) then
begin
ShowMessage('Select a discovered device first');
Exit;
end;
EdIP.Text := FDiscoveredIPs[Idx];
EdName.Text := FDiscoveredNames[Idx];
BtnAddClick(nil);
// Обновляем BoardType только что добавленной записи
if FSavedCount > 0 then
begin
FSavedDevices[FSavedCount - 1].BoardType := FDiscoveredBoardTypes[Idx];
SaveSaved;
RefreshSavedList;
LstSaved.ItemIndex := FSavedCount - 1;
end;
D := FStore.Discovered(Idx);
NewIdx := FStore.AddSaved(D.DisplayName, D.IPAddress, D.BoardType);
RefreshSavedList;
LstSaved.ItemIndex := NewIdx;
end;
procedure TDeviceDialog.BtnConnectClick(Sender: TObject);
@@ -566,20 +471,21 @@ var
Idx: Integer;
begin
IP := '';
if FStore = nil then Exit;
// Приоритет: выбранное сохранённое > выбранное найденное > ручной IP
Idx := LstSaved.ItemIndex;
if (Idx >= 0) and (Idx < FSavedCount) then
if (Idx >= 0) and (Idx < FStore.SavedCount) then
begin
IP := FSavedDevices[Idx].IPAddress;
IP := FStore.Saved(Idx).IPAddress;
FResult.SavedIdx := Idx;
end
else
begin
Idx := LstFound.ItemIndex;
if (Idx >= 0) and (Idx < FDiscoveredCount) then
if (Idx >= 0) and (Idx < FStore.DiscoveredCount) then
begin
IP := FDiscoveredIPs[Idx];
IP := FStore.Discovered(Idx).IPAddress;
FResult.SavedIdx := -1;
end
else if Trim(EdIP.Text) <> '' then
@@ -614,37 +520,21 @@ begin
end;
function TDeviceDialog.GetAutoStartIP: string;
var
I: Integer;
begin
Result := '';
for I := 0 to FSavedCount - 1 do
if FSavedDevices[I].AutoStart then
begin
Result := FSavedDevices[I].IPAddress;
Exit;
end;
if FStore <> nil then Result := FStore.AutoStartIP
else Result := '';
end;
function TDeviceDialog.GetAutoStartBoardType: Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to FSavedCount - 1 do
if FSavedDevices[I].AutoStart then
begin
Result := FSavedDevices[I].BoardType;
Exit;
end;
if FStore <> nil then Result := FStore.AutoStartBoardType
else Result := 0;
end;
function TDeviceDialog.GetSavedBoardType(Idx: Integer): Integer;
begin
if (Idx >= 0) and (Idx < FSavedCount) then
Result := FSavedDevices[Idx].BoardType
else
Result := 0;
if FStore <> nil then Result := FStore.SavedBoardType(Idx)
else Result := 0;
end;
end.