unit DetectUnit;

interface

uses
	Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
	StdCtrls;

type
	TDetectThread = class;

	TDetectFrame = class(TFrame)
	protected function GetHost: String; virtual;
	published property Host: String read GetHost;

	published
		DeviceGroup: TGroupBox;
		HostCombo: TComboBox;
		RedetectButton: TButton;
		procedure RedetectButtonClick(Sender: TObject);
		procedure FrameResize(Sender: TObject);

	protected FOnDetectBegin: TNotifyEvent;
	published property OnDetectBegin: TNotifyEvent read FOnDetectBegin write FOnDetectBegin;

	protected FOnDetectComplete: TNotifyEvent;
	published property OnDetectComplete: TNotifyEvent read FOnDetectComplete write FOnDetectComplete;

	protected FOnDetectFailed: TNotifyEvent;
	published property OnDetectFailed: TNotifyEvent read FOnDetectFailed write FOnDetectFailed;

	protected
		DetectThread: TDetectThread;

		procedure HandleDetectComplete; virtual;
		procedure HandleDetectFailed; virtual;
		{ Private declarations }
	public
		procedure AfterConstruction; override;
		procedure BeforeDestruction; override;
		{ Public declarations }
	end;



	TDetectThread = class(TThread)
	public
		constructor CreateAndListen(NewListener: TDetectFrame);
		destructor Destroy; override;
		procedure Go; virtual;
	protected
		function GetNameList: String;
	public
		property NameList: String read GetNameList;
	protected
		hGoEvent, hKillEvent, hNameListMutex: THandle;
		Listener: TDetectFrame;
		FNameList: String;

		procedure Execute; override;
	end;



implementation

{$R *.dfm}

uses
	AIOeNET;

procedure CloseAndInvalidateHandle(var hObject: THandle);
var
	hBak: THandle;

begin
	hBak := hObject;
	hObject := INVALID_HANDLE_VALUE;
	CloseHandle(hBak);
end;

{ TDetectFrame }

procedure TDetectFrame.AfterConstruction;
begin
	inherited;

	Name := '';
        
	HostCombo.Align := alClient;
	RedetectButton.Align := alRight;

	DetectThread := TDetectThread.CreateAndListen(Self);
end;

procedure TDetectFrame.BeforeDestruction;
begin
	FreeAndNil(DetectThread);

	inherited;
end;

procedure TDetectFrame.FrameResize(Sender: TObject);
begin
	RedetectButton.Click;
end;

function TDetectFrame.GetHost: String;
begin
	Result := HostCombo.Text;	
end;

procedure TDetectFrame.HandleDetectComplete;
var
	DetectComplete: TNotifyEvent;

begin
	RedetectButton.Enabled := True;
	DeviceGroup.Cursor := crDefault;

	HostCombo.Items.Text := DetectThread.NameList;
	if HostCombo.Text = '' then HostCombo.ItemIndex := 0;

	DetectComplete := FOnDetectComplete;
	if Assigned(DetectComplete) then DetectComplete(Self);
end;

procedure TDetectFrame.HandleDetectFailed;
var
	DetectFailed: TNotifyEvent;

begin
	RedetectButton.Enabled := True;
	DeviceGroup.Cursor := crDefault;

	DetectFailed := FOnDetectFailed;
	if Assigned(DetectFailed) then DetectFailed(Self);
end;

procedure TDetectFrame.RedetectButtonClick(Sender: TObject);
var
	DetectBegin: TNotifyEvent;

begin
	HostCombo.Items.Text := '';
	HostCombo.Text := '';

	DeviceGroup.Cursor := crHourGlass;
	RedetectButton.Enabled := False;

	DetectThread.Go;

	DetectBegin := FOnDetectBegin;
	if Assigned(DetectBegin) then DetectBegin(Self);
end;

{ TDetectThread }

constructor TDetectThread.CreateAndListen(NewListener: TDetectFrame);
begin
	hGoEvent := CreateEvent(nil, False, False, nil);
	hKillEvent := CreateEvent(nil, False, False, nil);
	hNameListMutex := CreateMutex(nil, False, nil);

	Listener := NewListener;

	inherited Create(False);
end;

destructor TDetectThread.Destroy;
begin
	SetEvent(hKillEvent);

	inherited;

	CloseAndInvalidateHandle(hGoEvent);
	CloseAndInvalidateHandle(hKillEvent);
	CloseAndInvalidateHandle(hNameListMutex);
end;

procedure TDetectThread.Execute;
var
	MultipleObjects: array[0..1] of THandle;
	Status: TWinErrCode;
	NewNameList: String;
	NameListBaseL, NameListL: TUInt32;

begin
	NameListBaseL := 1024;

	repeat
		MultipleObjects[0] := hKillEvent;
		MultipleObjects[1] := hGoEvent;
		case WaitForMultipleObjects(Length(MultipleObjects), PWOHandleArray(@MultipleObjects[0]), False, INFINITE) of
			WAIT_OBJECT_0: Break;
			WAIT_OBJECT_0+1: ;
			else begin
				Synchronize(Listener.HandleDetectFailed);
				Continue;
			end;
		end;

		WaitForSingleObject(hNameListMutex, INFINITE);
			FNameList := '';
		ReleaseMutex(hNameListMutex);

		NewNameList := StringOfChar(#0, NameListBaseL);
		Status := ScanForDevices(Length(NewNameList), PChar(NewNameList), NameListL);
		if Status <> ERROR_SUCCESS then begin
			Synchronize(Listener.HandleDetectFailed);
			Continue;
		end;

		if NameListL > TUIntNative(Length(NewNameList)) then begin
			//Too many devices on this network for the string size. Permanently increase string size and try again.
			NameListBaseL := NameListL * 2;
			SetEvent(hGoEvent);
			Continue;
		end;

		SetLength(NewNameList, NameListL);

		WaitForSingleObject(hNameListMutex, INFINITE);
			FNameList := NewNameList;
		ReleaseMutex(hNameListMutex);

		Synchronize(Listener.HandleDetectComplete);
	until Terminated;
end;

function TDetectThread.GetNameList: String;
begin
	WaitForSingleObject(hNameListMutex, INFINITE);
		Result := FNameList;
	ReleaseMutex(hNameListMutex);
end;

procedure TDetectThread.Go;
begin
	SetEvent(hGoEvent);
end;

end.
