123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- {
- 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.
|