Repository URL to install this package:
|
Version:
3.2.0 ▾
|
{
This file is part of the Free Component Library (FCL)
Copyright (c) 2015 by Ondrej Pokorny
Unit implementing Single Instance functionality.
The order of message processing is not deterministic (if there are more
pending messages, the server won't process them in the order they have
been sent to the server.
SendRequest and PostRequest+PeekResponse sequences from 1 client are
blocking and processed in correct order.
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
unit AdvancedSingleInstance;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, AdvancedIPC, singleinstance;
type
TSingleInstanceReceivedCustomMessage = procedure(Sender: TBaseSingleInstance; MsgID: Integer; MsgType: Integer; MsgData: TStream) of object;
TAdvancedSingleInstance = class(TBaseSingleInstance)
private
FGlobal: Boolean;
FID: string;
FServer: TIPCServer;
FClient: TIPCClient;
FOnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage;
procedure SetGlobal(const aGlobal: Boolean);
procedure SetID(const aID: string);
protected
procedure DoServerReceivedCustomRequest(const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
function GetIsClient: Boolean; override;
function GetIsServer: Boolean; override;
function GetStartResult: TSingleInstanceStart; override;
public
constructor Create(aOwner: TComponent); override;
public
function Start: TSingleInstanceStart; override;
procedure Stop; override;
procedure ServerCheckMessages; override;
procedure ClientPostParams; override;
public
function ClientPostCustomRequest(const aMsgType: Integer; const aStream: TStream): Integer;
function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream): Boolean; overload;
function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream; out outRequestID: Integer): Boolean; overload;
procedure ServerPostCustomResponse(const aRequestID: Integer; const aMsgType: Integer; const aStream: TStream);
function ClientPeekCustomResponse(const aStream: TStream; out outMsgType: Integer): Boolean;
public
property ID: string read FID write SetID;
property Global: Boolean read FGlobal write SetGlobal;
property OnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage read FOnServerReceivedCustomRequest write FOnServerReceivedCustomRequest;
end;
implementation
Resourcestring
SErrSetSingleInstanceIDStarted = 'You cannot change the single instance ID when it''s been started.';
SErrSetSingleInstanceGlobalStarted = 'You cannot change the single instance global property when it''s been started.';
SErrStartSingleInstanceStarted = 'You cannot start single instance when it''s been already started.';
SErrSingleInstanceStartResultNotAvailable = 'Single instance hasn''t been started yet.';
SErrSingleInstanceNotClient = 'Current instance is not a client.';
SErrSingleInstanceNotServer = 'Current instance is not a server.';
Const
MSGTYPE_CHECK = -1;
MSGTYPE_CHECKRESPONSE = -2;
MSGTYPE_PARAMS = -3;
MSGTYPE_WAITFORINSTANCES = -4;
{ TAdvancedSingleInstance }
constructor TAdvancedSingleInstance.Create(aOwner: TComponent);
var
xID: RawByteString;
I: Integer;
begin
inherited Create(aOwner);
xID := 'SI_'+ExtractFileName(ParamStr(0));
for I := 1 to Length(xID) do
case xID[I] of
'a'..'z', 'A'..'Z', '0'..'9', '_': begin end;
else
xID[I] := '_';
end;
ID := xID;
end;
function TAdvancedSingleInstance.ClientPeekCustomResponse(
const aStream: TStream; out outMsgType: Integer): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.PeekResponse(aStream, outMsgType, TimeOutMessages);
end;
function TAdvancedSingleInstance.ClientPostCustomRequest(
const aMsgType: Integer; const aStream: TStream): Integer;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.PostRequest(aMsgType, aStream);
end;
procedure TAdvancedSingleInstance.ClientPostParams;
var
xSL: TStringList;
xStringStream: TStringStream;
I: Integer;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
xSL := TStringList.Create;
try
for I := 0 to ParamCount do
xSL.Add(ParamStr(I));
xStringStream := TStringStream.Create(xSL.DelimitedText);
try
xStringStream.Position := 0;
FClient.PostRequest(MSGTYPE_PARAMS, xStringStream);
finally
xStringStream.Free;
end;
finally
xSL.Free;
end;
end;
function TAdvancedSingleInstance.ClientSendCustomRequest(
const aMsgType: Integer; const aStream: TStream): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.SendRequest(aMsgType, aStream, TimeOutMessages);
end;
function TAdvancedSingleInstance.ClientSendCustomRequest(
const aMsgType: Integer; const aStream: TStream; out
outRequestID: Integer): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.SendRequest(aMsgType, aStream, TimeOutMessages, outRequestID);
end;
procedure TAdvancedSingleInstance.DoServerReceivedCustomRequest(
const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
begin
if Assigned(FOnServerReceivedCustomRequest) then
FOnServerReceivedCustomRequest(Self, aMsgID, aMsgType, aStream);
end;
function TAdvancedSingleInstance.GetIsClient: Boolean;
begin
Result := Assigned(FClient);
end;
function TAdvancedSingleInstance.GetIsServer: Boolean;
begin
Result := Assigned(FServer);
end;
function TAdvancedSingleInstance.GetStartResult: TSingleInstanceStart;
begin
if not(Assigned(FServer) or Assigned(FClient)) then
raise ESingleInstance.Create(SErrSingleInstanceStartResultNotAvailable);
Result := inherited GetStartResult;
end;
procedure TAdvancedSingleInstance.ServerCheckMessages;
var
xMsgID: Integer;
xMsgType: Integer;
xStream: TStream;
xStringStream: TStringStream;
begin
if not Assigned(FServer) then
raise ESingleInstance.Create(SErrSingleInstanceNotServer);
if not FServer.PeekRequest(xMsgID, xMsgType) then
Exit;
case xMsgType of
MSGTYPE_CHECK:
begin
FServer.DeleteRequest(xMsgID);
FServer.PostResponse(xMsgID, MSGTYPE_CHECKRESPONSE, nil);
end;
MSGTYPE_PARAMS:
begin
xStringStream := TStringStream.Create('');
try
FServer.ReadRequest(xMsgID, xStringStream);
DoServerReceivedParams(xStringStream.DataString);
finally
xStringStream.Free;
end;
end;
MSGTYPE_WAITFORINSTANCES:
FServer.DeleteRequest(xMsgID);
else
xStream := TMemoryStream.Create;
try
FServer.ReadRequest(xMsgID, xStream);
DoServerReceivedCustomRequest(xMsgID, xMsgType, xStream);
finally
xStream.Free;
end;
end;
end;
procedure TAdvancedSingleInstance.ServerPostCustomResponse(
const aRequestID: Integer; const aMsgType: Integer;
const aStream: TStream);
begin
if not Assigned(FServer) then
raise ESingleInstance.Create(SErrSingleInstanceNotServer);
FServer.PostResponse(aRequestID, aMsgType, aStream);
end;
procedure TAdvancedSingleInstance.SetGlobal(const aGlobal: Boolean);
begin
if FGlobal = aGlobal then Exit;
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrSetSingleInstanceGlobalStarted);
FGlobal := aGlobal;
end;
procedure TAdvancedSingleInstance.SetID(const aID: string);
begin
if FID = aID then Exit;
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrSetSingleInstanceIDStarted);
FID := aID;
end;
function TAdvancedSingleInstance.Start: TSingleInstanceStart;
{$IFNDEF MSWINDOWS}
procedure UnixWorkaround(var bServerStarted: Boolean);
var
xWaitRequestID, xLastCount, xNewCount: Integer;
xClient: TIPCClient;
begin
//file locking workaround for UNIX systems -> the server can be started twice if 2 processes are started in parallel
//wait some time to see other clients
FServer.StopServer(False);
xClient := TIPCClient.Create(Self);
try
xClient.ServerID := FID;
xClient.Global := FGlobal;
xWaitRequestID := xClient.PostRequest(MSGTYPE_WAITFORINSTANCES, nil);
xLastCount := -1;
xNewCount := FServer.GetPendingRequestCount;
while xLastCount <> xNewCount do
begin
xLastCount := xNewCount;
Sleep(TimeOutWaitForInstances);
xNewCount := FServer.GetPendingRequestCount;
end;
finally
FreeAndNil(xClient);
end;
//find highest client that will be the server
if FServer.FindHighestPendingRequestId = xWaitRequestID then
begin
bServerStarted := FServer.StartServer(False);
end else
begin
//something went wrong, there are not-deleted waiting requests
//use random sleep as workaround and try to restart the server
Randomize;
Sleep(Random(($3F+PtrInt(GetProcessID)) and $3F));//limit to $3F (63)
bServerStarted := FServer.StartServer(False) and (FServer.GetPendingRequestCount > 0);
end;
end;
{$ENDIF}
var
xStream: TStream;
xMsgType: Integer;
xServerStarted: Boolean;
begin
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrStartSingleInstanceStarted);
FServer := TIPCServer.Create(Self);
FServer.ServerID := FID;
FServer.Global := FGlobal;
xServerStarted := FServer.StartServer(False);
if xServerStarted then
begin//this is single instance -> be server
Result := siServer;
{$IFNDEF MSWINDOWS}
UnixWorkaround(xServerStarted);
{$ENDIF}
end;
if not xServerStarted then
begin//instance found -> be client
FreeAndNil(FServer);
FClient := TIPCClient.Create(Self);
FClient.ServerID := FID;
FClient.Global := FGlobal;
FClient.PostRequest(MSGTYPE_CHECK, nil);
xStream := TMemoryStream.Create;
try
if FClient.PeekResponse(xStream, xMsgType, TimeOutMessages) then
Result := siClient
else
Result := siNotResponding;
finally
xStream.Free;
end;
end;
SetStartResult(Result);
end;
procedure TAdvancedSingleInstance.Stop;
begin
FreeAndNil(FServer);
FreeAndNil(FClient);
end;
initialization
DefaultSingleInstanceClass:=TAdvancedSingleInstance;
end.