mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-06 01:38:01 +02:00
798 lines
24 KiB
ObjectPascal
798 lines
24 KiB
ObjectPascal
{
|
|
This file is part of the Free Component Library (FCL)
|
|
Copyright (c) 2015 by Ondrej Pokorny
|
|
|
|
Unit implementing two-way (request/response) IPC between 1 server and more
|
|
clients, based on files.
|
|
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.
|
|
|
|
***
|
|
This unit is a duplicate of AdvancedIPC.pp from FPC (added in 3.1.1).
|
|
It should be removed when support of older FPC versions is dropped.
|
|
|
|
**********************************************************************}
|
|
|
|
unit LazAdvancedIPC;
|
|
|
|
{$mode objfpc}
|
|
{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNIX}
|
|
baseunix,
|
|
{$endif}
|
|
sysutils, Classes, LazFileUtils;
|
|
|
|
const
|
|
HEADER_VERSION = 2;
|
|
|
|
type
|
|
TMessageType = LongInt;
|
|
TMessageHeader = packed record
|
|
HeaderVersion: Byte;
|
|
FileLock: Byte;//0 = unlocked, 1 = locked
|
|
MsgType: TMessageType;
|
|
MsgLen: Integer;
|
|
MsgVersion: Integer;
|
|
end;
|
|
|
|
TFileHandle = Classes.THandle;
|
|
|
|
TReleaseHandleStream = class(THandleStream)
|
|
public
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
TIPCBase = class(TComponent)
|
|
private
|
|
FGlobal: Boolean;
|
|
FFileName: string;
|
|
FServerID: string;
|
|
FMessageVersion: Integer;
|
|
protected
|
|
class function ServerIDToFileName(const aServerID: string; const aGlobal: Boolean): string;
|
|
function GetResponseFileName(const aRequestID: Integer): string;
|
|
function GetResponseFileName(const aRequestFileName: string): string;
|
|
function GetPeekedRequestFileName(const aRequestID: Integer): string;
|
|
function GetPeekedRequestFileName(const aRequestFileName: string): string;
|
|
function GetRequestPrefix: string;
|
|
function GetRequestFileName(const aRequestID: Integer): string;
|
|
function RequestFileNameToID(const aFileName: string): Integer;
|
|
function RequestExists(const aRequestFileName: string): Boolean;
|
|
|
|
procedure SetServerID(const aServerID: string); virtual;
|
|
procedure SetGlobal(const aGlobal: Boolean); virtual;
|
|
|
|
function CanReadMessage(const aFileName: string; out outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer): Boolean;
|
|
procedure DoPostMessage(const aFileName: string; const aMsgType: TMessageType; const aStream: TStream); overload;
|
|
procedure DoPostMessage(const aFileStream: TFileStream; const aMsgType: TMessageType; const aStream: TStream); overload;
|
|
function DoReadMessage(const aFileName: string; const aStream: TStream; out outMsgType: TMessageType): Boolean;
|
|
|
|
property FileName: string read FFileName;
|
|
public
|
|
class procedure FindRunningServers(const aServerIDPrefix: string;
|
|
const outServerIDs: TStrings; const aGlobal: Boolean = False);
|
|
class function ServerRunning(const aServerID: string; const aGlobal: Boolean = False): Boolean; overload;
|
|
public
|
|
//ServerID: name/ID of the server. Use only ['a'..'z', 'A'..'Z', '0'..'9', '_'] characters
|
|
property ServerID: string read FServerID write SetServerID;
|
|
//Global: if true, processes from different users can communicate; false, processes only from current user can communicate
|
|
property Global: Boolean read FGlobal write SetGlobal;
|
|
//MessageVersion: only messages with the same MessageVersion can be delivered between server/client
|
|
property MessageVersion: Integer read FMessageVersion write FMessageVersion;
|
|
end;
|
|
|
|
TIPCClient = class(TIPCBase)
|
|
private
|
|
FLastRequestID: Integer;
|
|
|
|
function CreateUniqueRequest(out outFileStream: TFileStream): Integer;
|
|
function DoPeekResponse(const aResponseFileName: string; const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
public
|
|
//post request to server, do not wait until request is peeked; returns request ID
|
|
function PostRequest(const aMsgType: TMessageType; const aStream: TStream): Integer;
|
|
//send request to server, wait until request is peeked; returns True if request was peeked within the aTimeOut limit
|
|
function SendRequest(const aMsgType: TMessageType; const aStream: TStream; const aTimeOut: Integer): Boolean;
|
|
function SendRequest(const aMsgType: TMessageType; const aStream: TStream; const aTimeOut: Integer; out outRequestID: Integer): Boolean;
|
|
//peek a response from last request from this client
|
|
function PeekResponse(const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
|
|
function PeekResponse(const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
//peek a response from request by ID
|
|
function PeekResponse(const aRequestID: Integer; const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
|
|
function PeekResponse(const aRequestID: Integer; const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
//delete last request from this client, returns true if request file existed and was deleted
|
|
function DeleteRequest: Boolean; overload;
|
|
//delete request by ID, returns true if request existed file and was deleted
|
|
function DeleteRequest(const aRequestID: Integer): Boolean; overload;
|
|
//check if server is running
|
|
function ServerRunning: Boolean; overload;
|
|
end;
|
|
|
|
TIPCServer = class(TIPCBase)
|
|
private
|
|
FFileHandle: TFileHandle;
|
|
FActive: Boolean;
|
|
|
|
function FindFirstRequest(out outFileName: string; out outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer): Integer;
|
|
|
|
protected
|
|
procedure SetServerID(const aServerID: string); override;
|
|
procedure SetGlobal(const aGlobal: Boolean); override;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
public
|
|
//peek request and read the message into a stream
|
|
function PeekRequest(const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
|
|
function PeekRequest(const aStream: TStream; out outRequestID: Integer; out outMsgType: TMessageType): Boolean; overload;
|
|
function PeekRequest(const aStream: TStream; out outRequestID: Integer; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
//only peek request, you have to read/delete the request manually with ReadRequest/DeleteRequest
|
|
function PeekRequest(out outMsgType: TMessageType): Boolean; overload;
|
|
function PeekRequest(out outRequestID: Integer; out outMsgType: TMessageType): Boolean; overload;
|
|
function PeekRequest(out outRequestID: Integer; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
//read a peeked request (that hasn't been read yet)
|
|
function ReadRequest(const aRequestID: Integer; const aStream: TStream): Boolean;
|
|
//delete a peeked request (that hasn't been read yet), returns true if request file existed and was deleted
|
|
function DeleteRequest(const aRequestID: Integer): Boolean;
|
|
|
|
//post response to a request
|
|
procedure PostResponse(const aRequestID: Integer; const aMsgType: TMessageType; const aStream: TStream);
|
|
|
|
//find the highest request ID from all pending requests
|
|
function FindHighestPendingRequestId: Integer;
|
|
//get the pending request count
|
|
function GetPendingRequestCount: Integer;
|
|
|
|
//start server: returns true if unique and started
|
|
function StartServer(const aDeletePendingRequests: Boolean = True): Boolean;
|
|
//stop server: returns true if stopped
|
|
function StopServer(const aDeletePendingRequests: Boolean = True): Boolean;
|
|
|
|
//delete all pending requests and responses
|
|
procedure DeletePendingRequests;
|
|
public
|
|
//true if server runs (was started)
|
|
property Active: Boolean read FActive;
|
|
end;
|
|
|
|
EICPException = class(Exception);
|
|
|
|
resourcestring
|
|
SErrInvalidServerID = 'Invalid server ID "%s". Please use only alphanumerical characters and underlines.';
|
|
SErrSetGlobalActive = 'You cannot change the global property when the server is active.';
|
|
SErrSetServerIDActive = 'You cannot change the server ID when the server is active.';
|
|
|
|
implementation
|
|
|
|
type
|
|
TIPCSearchRec = TRawByteSearchRec;
|
|
|
|
const
|
|
{$IFDEF UNIX}
|
|
GLOBAL_RIGHTS = S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP or S_IROTH or S_IWOTH;
|
|
{$ELSE}
|
|
GLOBAL_RIGHTS = 0;
|
|
{$ENDIF}
|
|
|
|
var
|
|
CreateUniqueRequestCritSec: TRTLCriticalSection;
|
|
|
|
{ TIPCBase }
|
|
|
|
function TIPCBase.CanReadMessage(const aFileName: string; out
|
|
outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer
|
|
): Boolean;
|
|
var
|
|
xFileHandle: TFileHandle;
|
|
xHeader: TMessageHeader;
|
|
begin
|
|
outStream := nil;
|
|
outMsgType := -1;
|
|
outMsgLen := 0;
|
|
Result := FileExists(aFileName);
|
|
if not Result then
|
|
Exit;
|
|
|
|
xFileHandle := FileOpen(aFileName, fmOpenRead or fmShareExclusive);
|
|
Result := xFileHandle <> feInvalidHandle;
|
|
if not Result then
|
|
Exit;
|
|
|
|
outStream := TReleaseHandleStream.Create(xFileHandle);
|
|
|
|
Result := (outStream.Size >= SizeOf(xHeader));
|
|
if not Result then
|
|
begin
|
|
FreeAndNil(outStream);
|
|
Exit;
|
|
end;
|
|
|
|
outStream.ReadBuffer(xHeader{%H-}, SizeOf(xHeader));
|
|
Result :=
|
|
(xHeader.HeaderVersion = HEADER_VERSION) and (xHeader.FileLock = 0) and
|
|
(xHeader.MsgVersion = MessageVersion) and
|
|
(outStream.Size = Int64(SizeOf(xHeader))+Int64(xHeader.MsgLen));
|
|
if not Result then
|
|
begin
|
|
FreeAndNil(outStream);
|
|
Exit;
|
|
end;
|
|
outMsgType := xHeader.MsgType;
|
|
outMsgLen := xHeader.MsgLen;
|
|
end;
|
|
|
|
function TIPCBase.DoReadMessage(const aFileName: string;
|
|
const aStream: TStream; out outMsgType: TMessageType): Boolean;
|
|
var
|
|
xStream: TStream;
|
|
xMsgLen: Integer;
|
|
begin
|
|
aStream.Size := 0;
|
|
xStream := nil;
|
|
try
|
|
Result := CanReadMessage(aFileName, xStream, outMsgType, xMsgLen);
|
|
if Result then
|
|
begin
|
|
if xMsgLen > 0 then
|
|
aStream.CopyFrom(xStream, xMsgLen);
|
|
FreeAndNil(xStream);
|
|
aStream.Position := 0;
|
|
DeleteFile(aFileName);
|
|
end;
|
|
finally
|
|
xStream.Free;
|
|
end;
|
|
end;
|
|
|
|
function TIPCBase.RequestExists(const aRequestFileName: string): Boolean;
|
|
begin
|
|
Result :=
|
|
(FileExists(aRequestFileName) or
|
|
FileExists(GetResponseFileName(aRequestFileName)) or
|
|
FileExists(GetPeekedRequestFileName(aRequestFileName)));
|
|
end;
|
|
|
|
class function TIPCBase.ServerRunning(const aServerID: string;
|
|
const aGlobal: Boolean): Boolean;
|
|
var
|
|
xServerFileHandle: TFileHandle;
|
|
xFileName: String;
|
|
begin
|
|
xFileName := ServerIDToFileName(aServerID, aGlobal);
|
|
Result := FileExists(xFileName);
|
|
if Result then
|
|
begin//+ check -> we should not be able to access the file
|
|
xServerFileHandle := FileCreate(xFileName, fmOpenReadWrite or fmShareExclusive, GLOBAL_RIGHTS);
|
|
Result := (xServerFileHandle=feInvalidHandle);
|
|
if not Result then
|
|
FileClose(xServerFileHandle);
|
|
end;
|
|
end;
|
|
|
|
class function TIPCBase.ServerIDToFileName(const aServerID: string;
|
|
const aGlobal: Boolean): string;
|
|
begin
|
|
Result := GetTempDir(aGlobal)+aServerID;
|
|
end;
|
|
|
|
procedure TIPCBase.SetGlobal(const aGlobal: Boolean);
|
|
begin
|
|
if FGlobal = aGlobal then Exit;
|
|
|
|
FGlobal := aGlobal;
|
|
FFileName := ServerIDToFileName(FServerID, FGlobal);
|
|
end;
|
|
|
|
procedure TIPCBase.DoPostMessage(const aFileName: string;
|
|
const aMsgType: TMessageType; const aStream: TStream);
|
|
var
|
|
xStream: TFileStream;
|
|
begin
|
|
xStream := TFileStream.Create(aFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
|
|
try
|
|
DoPostMessage(xStream, aMsgType, aStream);
|
|
finally
|
|
xStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TIPCBase.DoPostMessage(const aFileStream: TFileStream;
|
|
const aMsgType: TMessageType; const aStream: TStream);
|
|
var
|
|
xHeader: TMessageHeader;
|
|
begin
|
|
xHeader.HeaderVersion := HEADER_VERSION;
|
|
xHeader.FileLock := 1;//locking
|
|
xHeader.MsgType := aMsgType;
|
|
if Assigned(aStream) then
|
|
xHeader.MsgLen := aStream.Size-aStream.Position
|
|
else
|
|
xHeader.MsgLen := 0;
|
|
xHeader.MsgVersion := MessageVersion;
|
|
|
|
aFileStream.WriteBuffer(xHeader, SizeOf(xHeader));
|
|
if Assigned(aStream) and (aStream.Size-aStream.Position > 0) then
|
|
aFileStream.CopyFrom(aStream, aStream.Size-aStream.Position);
|
|
|
|
aFileStream.Position := 0;//unlocking
|
|
xHeader.FileLock := 0;
|
|
aFileStream.WriteBuffer(xHeader, SizeOf(xHeader));
|
|
aFileStream.Seek(0, soEnd);
|
|
end;
|
|
|
|
function TIPCBase.RequestFileNameToID(const aFileName: string): Integer;
|
|
begin
|
|
//the function prevents all responses/temp files to be handled
|
|
//only valid response files are returned
|
|
if (Length(aFileName) > 9) and (aFileName[Length(aFileName)-8] = '-') then
|
|
Result := StrToIntDef('$'+Copy(aFileName, Length(aFileName)-7, 8), -1)
|
|
else
|
|
Result := -1;
|
|
end;
|
|
|
|
class procedure TIPCBase.FindRunningServers(const aServerIDPrefix: string;
|
|
const outServerIDs: TStrings; const aGlobal: Boolean);
|
|
var
|
|
xRec: TIPCSearchRec;
|
|
begin
|
|
if FindFirstUTF8(ServerIDToFileName(aServerIDPrefix+AllFilesMask, aGlobal), faAnyFile, xRec) = 0 then
|
|
begin
|
|
repeat
|
|
if (Pos('-', xRec.Name) = 0) and//file that we found is a pending message
|
|
ServerRunning(xRec.Name, aGlobal)
|
|
then
|
|
outServerIDs.Add(xRec.Name);
|
|
until FindNextUTF8(xRec) <> 0;
|
|
end;
|
|
FindCloseUTF8(xRec);
|
|
end;
|
|
|
|
function TIPCBase.GetPeekedRequestFileName(const aRequestID: Integer): string;
|
|
begin
|
|
Result := GetPeekedRequestFileName(GetRequestFileName(aRequestID));
|
|
end;
|
|
|
|
function TIPCBase.GetPeekedRequestFileName(const aRequestFileName: string
|
|
): string;
|
|
begin
|
|
Result := aRequestFileName+'-t';
|
|
end;
|
|
|
|
function TIPCBase.GetRequestFileName(const aRequestID: Integer): string;
|
|
begin
|
|
Result := GetRequestPrefix+IntToHex(aRequestID, 8);
|
|
end;
|
|
|
|
function TIPCBase.GetRequestPrefix: string;
|
|
begin
|
|
Result := FFileName+'-';
|
|
end;
|
|
|
|
function TIPCBase.GetResponseFileName(const aRequestID: Integer): string;
|
|
begin
|
|
Result := GetResponseFileName(GetRequestFileName(aRequestID));
|
|
end;
|
|
|
|
function TIPCBase.GetResponseFileName(const aRequestFileName: string): string;
|
|
begin
|
|
Result := aRequestFileName+'-r';
|
|
end;
|
|
|
|
procedure TIPCBase.SetServerID(const aServerID: string);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if FServerID = aServerID then Exit;
|
|
|
|
for I := 1 to Length(aServerID) do
|
|
if not (aServerID[I] in ['a'..'z', 'A'..'Z', '0'..'9', '_']) then
|
|
raise EICPException.CreateFmt(SErrInvalidServerID , [aServerID]);
|
|
|
|
FServerID := aServerID;
|
|
|
|
FFileName := ServerIDToFileName(FServerID, FGlobal);
|
|
end;
|
|
|
|
{ TIPCClient }
|
|
|
|
constructor TIPCClient.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
|
|
FLastRequestID := -1;
|
|
end;
|
|
|
|
function TIPCClient.DeleteRequest(const aRequestID: Integer): Boolean;
|
|
var
|
|
xRequestFileName: string;
|
|
begin
|
|
xRequestFileName := GetRequestFileName(aRequestID);
|
|
Result := DeleteFile(xRequestFileName);
|
|
if (aRequestID = FLastRequestID) and not FileExists(xRequestFileName) then
|
|
FLastRequestID := -1;
|
|
end;
|
|
|
|
function TIPCClient.DeleteRequest: Boolean;
|
|
begin
|
|
if FLastRequestID >= 0 then
|
|
Result := DeleteRequest(FLastRequestID)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TIPCClient.DoPeekResponse(const aResponseFileName: string;
|
|
const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer
|
|
): Boolean;
|
|
var
|
|
xStart: QWord;
|
|
begin
|
|
aStream.Size := 0;
|
|
Result := False;
|
|
xStart := GetTickCount64;
|
|
repeat
|
|
if DoReadMessage(aResponseFileName, aStream, outMsgType) then
|
|
Exit(True)
|
|
else if aTimeOut > 20 then
|
|
Sleep(10);
|
|
until (GetTickCount64-xStart > aTimeOut);
|
|
end;
|
|
|
|
function TIPCClient.CreateUniqueRequest(out outFileStream: TFileStream): Integer;
|
|
var
|
|
xFileName: string;
|
|
begin
|
|
outFileStream := nil;
|
|
EnterCriticalsection(CreateUniqueRequestCritSec);
|
|
try
|
|
Randomize;
|
|
repeat
|
|
//if Randomize/Random is started from 2 processes at exactly same moment, it returns the same number! -> prevent duplicates by xor GetProcessId
|
|
//the result must be of range 0..$7FFFFFFF (High(Integer))
|
|
Result := Integer((PtrInt(Random($7FFFFFFF)) xor {%H-}PtrInt(GetProcessID)) and $7FFFFFFF);
|
|
xFileName := GetRequestFileName(Result);
|
|
until not RequestExists(xFileName);
|
|
|
|
outFileStream := TFileStream.Create(xFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
|
|
finally
|
|
LeaveCriticalsection(CreateUniqueRequestCritSec);
|
|
end;
|
|
end;
|
|
|
|
function TIPCClient.PeekResponse(const aRequestID: Integer;
|
|
const aStream: TStream; out outMsgType: TMessageType): Boolean;
|
|
begin
|
|
Result := DoReadMessage(GetResponseFileName(aRequestID), aStream, outMsgType);
|
|
end;
|
|
|
|
function TIPCClient.PeekResponse(const aRequestID: Integer;
|
|
const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer
|
|
): Boolean;
|
|
begin
|
|
Result := DoPeekResponse(GetResponseFileName(aRequestID), aStream, outMsgType, aTimeOut);
|
|
end;
|
|
|
|
function TIPCClient.PeekResponse(const aStream: TStream; out
|
|
outMsgType: TMessageType): Boolean;
|
|
begin
|
|
Result := DoReadMessage(GetResponseFileName(FLastRequestID), aStream, outMsgType);
|
|
end;
|
|
|
|
function TIPCClient.PeekResponse(const aStream: TStream; out
|
|
outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
begin
|
|
Result := DoPeekResponse(GetResponseFileName(FLastRequestID), aStream, outMsgType, aTimeOut);
|
|
end;
|
|
|
|
function TIPCClient.PostRequest(const aMsgType: TMessageType;
|
|
const aStream: TStream): Integer;
|
|
var
|
|
xRequestFileStream: TFileStream;
|
|
begin
|
|
xRequestFileStream := nil;
|
|
try
|
|
Result := CreateUniqueRequest(xRequestFileStream);
|
|
DoPostMessage(xRequestFileStream, aMsgType, aStream);
|
|
finally
|
|
xRequestFileStream.Free;
|
|
end;
|
|
FLastRequestID := Result;
|
|
end;
|
|
|
|
function TIPCClient.SendRequest(const aMsgType: TMessageType;
|
|
const aStream: TStream; const aTimeOut: Integer): Boolean;
|
|
var
|
|
xRequestID: Integer;
|
|
begin
|
|
Result := SendRequest(aMsgType, aStream, aTimeOut, xRequestID);
|
|
end;
|
|
|
|
function TIPCClient.SendRequest(const aMsgType: TMessageType;
|
|
const aStream: TStream; const aTimeOut: Integer; out outRequestID: Integer
|
|
): Boolean;
|
|
var
|
|
xStart: QWord;
|
|
xRequestFileName: string;
|
|
begin
|
|
outRequestID := PostRequest(aMsgType, aStream);
|
|
Result := False;
|
|
|
|
xRequestFileName := GetRequestFileName(outRequestID);
|
|
xStart := GetTickCount64;
|
|
repeat
|
|
if not FileExists(xRequestFileName) then
|
|
Exit(True)
|
|
else if aTimeOut > 20 then
|
|
Sleep(10);
|
|
until (GetTickCount64-xStart > aTimeOut);
|
|
end;
|
|
|
|
function TIPCClient.ServerRunning: Boolean;
|
|
begin
|
|
Result := ServerRunning(ServerID, Global);
|
|
end;
|
|
|
|
{ TReleaseHandleStream }
|
|
|
|
destructor TReleaseHandleStream.Destroy;
|
|
begin
|
|
FileClose(Handle);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TIPCServer }
|
|
|
|
procedure TIPCServer.DeletePendingRequests;
|
|
var
|
|
xRec: TIPCSearchRec;
|
|
xDir: string;
|
|
begin
|
|
xDir := ExtractFilePath(FFileName);
|
|
if FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
begin
|
|
try
|
|
repeat
|
|
DeleteFile(xDir+xRec.Name);
|
|
until FindNextUTF8(xRec) <> 0;
|
|
finally
|
|
FindCloseUTF8(xRec);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TIPCServer.DeleteRequest(const aRequestID: Integer): Boolean;
|
|
begin
|
|
Result := DeleteFile(GetPeekedRequestFileName(aRequestID));
|
|
end;
|
|
|
|
constructor TIPCServer.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
|
|
FFileHandle := feInvalidHandle;
|
|
end;
|
|
|
|
destructor TIPCServer.Destroy;
|
|
begin
|
|
if Active then
|
|
StopServer;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TIPCServer.FindFirstRequest(out outFileName: string; out
|
|
outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer
|
|
): Integer;
|
|
var
|
|
xRec: TIPCSearchRec;
|
|
begin
|
|
outFileName := '';
|
|
outStream := nil;
|
|
outMsgType := -1;
|
|
outMsgLen := 0;
|
|
Result := -1;
|
|
if FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
begin
|
|
try
|
|
repeat
|
|
Result := RequestFileNameToID(xRec.Name);
|
|
if Result >= 0 then
|
|
begin
|
|
outFileName := GetRequestFileName(Result);
|
|
if not CanReadMessage(outFileName, outStream, outMsgType, outMsgLen) then
|
|
Result := -1;
|
|
end;
|
|
until (Result >= 0) or (FindNextUTF8(xRec) <> 0);
|
|
finally
|
|
FindCloseUTF8(xRec);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TIPCServer.FindHighestPendingRequestId: Integer;
|
|
var
|
|
xRec: TIPCSearchRec;
|
|
xRequestID: LongInt;
|
|
begin
|
|
Result := -1;
|
|
if FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
begin
|
|
try
|
|
repeat
|
|
xRequestID := RequestFileNameToID(xRec.Name);
|
|
if xRequestID > Result then
|
|
Result := xRequestID;
|
|
until FindNextUTF8(xRec) <> 0;
|
|
finally
|
|
FindCloseUTF8(xRec);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TIPCServer.GetPendingRequestCount: Integer;
|
|
var
|
|
xRec: TIPCSearchRec;
|
|
begin
|
|
Result := 0;
|
|
if FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
begin
|
|
try
|
|
repeat
|
|
if RequestFileNameToID(xRec.Name) >= 0 then
|
|
Inc(Result);
|
|
until FindNextUTF8(xRec) <> 0;
|
|
finally
|
|
FindCloseUTF8(xRec);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TIPCServer.PeekRequest(out outRequestID: Integer; out
|
|
outMsgType: TMessageType): Boolean;
|
|
var
|
|
xStream: TStream;
|
|
xMsgLen: Integer;
|
|
xMsgFileName: string;
|
|
begin
|
|
outMsgType := -1;
|
|
xMsgFileName := '';
|
|
xStream := nil;
|
|
try
|
|
outRequestID := FindFirstRequest(xMsgFileName, xStream, outMsgType, xMsgLen);
|
|
Result := outRequestID >= 0;
|
|
if Result then
|
|
begin
|
|
FreeAndNil(xStream);
|
|
RenameFile(xMsgFileName, GetPeekedRequestFileName(xMsgFileName));
|
|
end;
|
|
finally
|
|
xStream.Free;
|
|
end;
|
|
end;
|
|
|
|
function TIPCServer.PeekRequest(out outRequestID: Integer; out
|
|
outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
var
|
|
xStart: QWord;
|
|
begin
|
|
Result := False;
|
|
xStart := GetTickCount64;
|
|
repeat
|
|
if PeekRequest(outRequestID, outMsgType) then
|
|
Exit(True)
|
|
else if aTimeOut > 20 then
|
|
Sleep(10);
|
|
until (GetTickCount64-xStart > aTimeOut);
|
|
end;
|
|
|
|
function TIPCServer.PeekRequest(out outMsgType: TMessageType): Boolean;
|
|
var
|
|
xRequestID: Integer;
|
|
begin
|
|
Result := PeekRequest(xRequestID, outMsgType);
|
|
end;
|
|
|
|
function TIPCServer.PeekRequest(const aStream: TStream; out outRequestID: Integer;
|
|
out outMsgType: TMessageType): Boolean;
|
|
begin
|
|
Result := PeekRequest(outRequestID, outMsgType);
|
|
if Result then
|
|
Result := ReadRequest(outRequestID, aStream);
|
|
end;
|
|
|
|
function TIPCServer.PeekRequest(const aStream: TStream; out outRequestID: Integer;
|
|
out outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
begin
|
|
Result := PeekRequest(outRequestID, outMsgType, aTimeOut);
|
|
if Result then
|
|
Result := ReadRequest(outRequestID, aStream);
|
|
end;
|
|
|
|
function TIPCServer.PeekRequest(const aStream: TStream; out
|
|
outMsgType: TMessageType): Boolean;
|
|
var
|
|
xRequestID: Integer;
|
|
begin
|
|
Result := PeekRequest(aStream, xRequestID, outMsgType);
|
|
end;
|
|
|
|
procedure TIPCServer.PostResponse(const aRequestID: Integer;
|
|
const aMsgType: TMessageType; const aStream: TStream);
|
|
begin
|
|
DoPostMessage(GetResponseFileName(aRequestID), aMsgType, aStream);
|
|
end;
|
|
|
|
function TIPCServer.ReadRequest(const aRequestID: Integer; const aStream: TStream
|
|
): Boolean;
|
|
var
|
|
xMsgType: TMessageType;
|
|
begin
|
|
Result := DoReadMessage(GetPeekedRequestFileName(aRequestID), aStream, xMsgType);
|
|
end;
|
|
|
|
procedure TIPCServer.SetGlobal(const aGlobal: Boolean);
|
|
begin
|
|
if Active then
|
|
raise EICPException.Create(SErrSetGlobalActive);
|
|
|
|
inherited SetGlobal(aGlobal);
|
|
end;
|
|
|
|
procedure TIPCServer.SetServerID(const aServerID: string);
|
|
begin
|
|
if Active then
|
|
raise EICPException.Create(SErrSetServerIDActive);
|
|
|
|
inherited SetServerID(aServerID);
|
|
end;
|
|
|
|
function TIPCServer.StartServer(const aDeletePendingRequests: Boolean): Boolean;
|
|
begin
|
|
if Active then
|
|
Exit(True);
|
|
|
|
FFileHandle := FileCreate(FFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
|
|
Result := (FFileHandle<>feInvalidHandle);
|
|
FActive := Result;
|
|
if Result and aDeletePendingRequests then
|
|
DeletePendingRequests;
|
|
end;
|
|
|
|
function TIPCServer.StopServer(const aDeletePendingRequests: Boolean): Boolean;
|
|
begin
|
|
if not Active then
|
|
Exit(True);
|
|
|
|
if FFileHandle<>feInvalidHandle then
|
|
FileClose(FFileHandle);
|
|
Result := DeleteFile(FFileName);
|
|
|
|
if aDeletePendingRequests then
|
|
DeletePendingRequests;
|
|
|
|
FActive := False;
|
|
end;
|
|
|
|
initialization
|
|
InitCriticalSection(CreateUniqueRequestCritSec);
|
|
|
|
finalization
|
|
DoneCriticalsection(CreateUniqueRequestCritSec);
|
|
|
|
end.
|