doublecmd/src/platform/uuniqueinstance.pas
2016-01-31 19:12:15 +00:00

432 lines
11 KiB
ObjectPascal

unit uUniqueInstance;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, SimpleIPC, uCmdLineParams, SynRegExpr;
type
TOnUniqueInstanceMessage = procedure (Sender: TObject; Params: TCommandLineParams) of object;
{ TUniqueInstance }
TUniqueInstance = class
private
FHandle: THandle;
FInstanceName: String;
FServerIPC: TSimpleIPCServer;
FClientIPC: TSimpleIPCClient;
FOnMessage: TOnUniqueInstanceMessage;
FServernameByUser: String;
{$IF DEFINED(UNIX)}
FMyProgramCreateSemaphore:Boolean;
{$ENDIF}
procedure OnNative(Sender: TObject);
procedure CreateServer;
procedure CreateClient;
function IsRunning: Boolean;
procedure DisposeMutex;
public
constructor Create(aInstanceName: String; aServernameByUser: String);
destructor Destroy; override;
function IsRunInstance: Boolean;
procedure SendParams;
procedure RunListen;
procedure StopListen;
function isAnotherDCRunningWhileIamRunning:boolean;
property OnMessage: TOnUniqueInstanceMessage read FOnMessage write FOnMessage;
property ServernameByUser: String read FServernameByUser;
end;
procedure InitInstance;
procedure InitInstanceWithName(aInstanceName: String; aServernameByUser: String);
function IsUniqueInstance: Boolean;
function WantsToBeClient: Boolean;
{en
Returns @true if current application instance is allowed to run.
Returns @false if current instance should not be run.
}
function IsInstanceAllowed: Boolean;
var
UniqueInstance: TUniqueInstance = nil;
FIsUniqueInstance: Boolean = False;
implementation
uses
{$IF DEFINED(MSWINDOWS)}
Windows,
{$ELSEIF DEFINED(UNIX)}
ipc, baseunix, uPipeServer,
{$ENDIF}
Forms, StrUtils, FileUtil, uGlobs, uDebug;
{$IF DEFINED(DARWIN)}
const
SEM_GETVAL = 5; // Return the value of semval (READ)
SEM_SETVAL = 8; // Set the value of semval to arg.val (ALTER)
{$ENDIF}
{ TUniqueInstance }
procedure TUniqueInstance.OnNative(Sender: TObject);
var
Params: TCommandLineParams;
begin
if Assigned(FOnMessage) then
begin
FServerIPC.MsgData.Seek(0, soFromBeginning);
FServerIPC.MsgData.ReadBuffer(Params, FServerIPC.MsgType);
FOnMessage(Self, Params);
end;
end;
procedure TUniqueInstance.CreateServer;
begin
if FServerIPC = nil then
begin
FServerIPC:= TSimpleIPCServer.Create(nil);
FServerIPC.OnMessage:= @OnNative;
end;
if FClientIPC <> nil then
FreeAndNil(FClientIPC);
end;
procedure TUniqueInstance.CreateClient;
begin
if FClientIPC = nil then
FClientIPC:= TSimpleIPCClient.Create(nil);
end;
function TUniqueInstance.IsRunning: Boolean;
{$IF DEFINED(MSWINDOWS)}
var
MutexName: AnsiString;
begin
Result:= False;
MutexName:= ExtractFileName(ParamStr(0));
FHandle:= OpenMutex(MUTEX_MODIFY_STATE, False, PAnsiChar(MutexName));
if FHandle = 0 then
FHandle:= CreateMutex(nil, True, PAnsiChar(MutexName))
else
begin
if WaitForSingleObject(FHandle, 0) <> WAIT_ABANDONED then
Result:= True;
end;
end;
{$ELSEIF DEFINED(UNIX)}
const
SEM_PERM = 6 shl 6 { 0600 };
var
semkey: TKey;
status: longint = 0;
arg: tsemun;
function id: byte;
var
UserID: LongRec;
begin
Result := 0;
UserID := LongRec(fpGetUID);
Result := Result xor UserID.Bytes[0];
Result := Result xor UserID.Bytes[1];
Result := Result xor UserID.Bytes[2];
Result := Result xor UserID.Bytes[3];
end;
function semlock(semid: longint): boolean;
// increase special Value in semaphore structure (value decreases automatically
// when program completed incorrectly)
var
p_buf: tsembuf;
begin
p_buf.sem_num := 0;
p_buf.sem_op := 1;
p_buf.sem_flg := SEM_UNDO;
Result:= semop(semid, @p_buf, 1) = 0;
end;
begin
Result := False;
semkey := ftok(PAnsiChar(ParamStr(0)), id);
// try create semapore for semkey
// If semflg specifies both IPC_CREAT and IPC_EXCL and a semaphore set already
// exists for semkey, then semget() return -1 and errno set to EEXIST
FHandle := semget(semkey, 1, SEM_PERM or IPC_CREAT or IPC_EXCL);
// if semaphore exists
if FHandle = -1 then
begin
// get semaphore id
FHandle := semget(semkey, 1, 0);
// get special Value from semaphore structure
status := semctl(FHandle, 0, SEM_GETVAL, arg);
if status = 1 then
// There is other running copy of the program
begin
Result := True;
// Not to release semaphore when exiting from the program
FMyProgramCreateSemaphore := false;
end
else
begin
// Other copy of the program has created a semaphore but has been completed incorrectly
// increase special Value in semaphore structure (value decreases automatically
// when program completed incorrectly)
semlock(FHandle);
// its one copy of program running, release semaphore when exiting from the program
FMyProgramCreateSemaphore := true;
end;
end
else
begin
// its one copy of program running, release semaphore when exiting from the program
FMyProgramCreateSemaphore := true;
// set special Value in semaphore structure to 0
arg.val := 0;
status := semctl(FHandle, 0, SEM_SETVAL, arg);
// increase special Value in semaphore structure (value decreases automatically
// when program completed incorrectly)
semlock(FHandle);
end;
end;
{$ENDIF}
procedure TUniqueInstance.DisposeMutex;
{$IF DEFINED(MSWINDOWS)}
begin
ReleaseMutex(FHandle);
end;
{$ELSEIF DEFINED(UNIX)}
var
arg: tsemun;
begin
// If my copy of the program created a semaphore then released it
if FMyProgramCreateSemaphore then
semctl(FHandle, 0, IPC_RMID, arg);
end;
{$ENDIF}
function TUniqueInstance.IsRunInstance: Boolean;
begin
CreateClient;
FClientIPC.ServerID:= FInstanceName;
Result:= IsRunning and FClientIPC.ServerRunning;
end;
procedure TUniqueInstance.SendParams;
var
Stream: TMemoryStream = nil;
begin
CreateClient;
FClientIPC.ServerID:= FInstanceName;
if not FClientIPC.ServerRunning then
Exit;
Stream:= TMemoryStream.Create;
Stream.WriteBuffer(CommandLineParams, SizeOf(TCommandLineParams));
try
FClientIPC.Connect;
Stream.Seek(0, soFromBeginning);
FClientIPC.SendMessage(SizeOf(TCommandLineParams), Stream);
finally
Stream.Free;
FClientIPC.Disconnect;
end;
end;
procedure TUniqueInstance.RunListen;
begin
CreateServer;
FServerIPC.ServerID:= FInstanceName;
FServerIPC.Global:= True;
FServerIPC.StartServer;
end;
procedure TUniqueInstance.StopListen;
begin
DisposeMutex;
if FServerIPC = nil then Exit;
FServerIPC.StopServer;
end;
function TUniqueInstance.isAnotherDCRunningWhileIamRunning:boolean;
var
LocalClientIPC: TSimpleIPCClient;
IndexInstance:integer;
function GetServerIdNameToCheck:string;
begin
result:=ApplicationName+'-';
if IndexInstance>1 then result:=result+IntToStr(IndexInstance);
{$IF DEFINED(UNIX)}
result := result + '-' + IntToStr(fpGetUID);
{$ENDIF}
end;
begin
Result:=True;
if IsRunning then
begin
FServerIPC.Active:=False;
try
LocalClientIPC:=TSimpleIPCClient.Create(nil);
IndexInstance:=1;
Result:=FALSE;
repeat
LocalClientIPC.ServerID:=GetServerIdNameToCheck;
Result:=LocalClientIPC.ServerRunning;
inc(IndexInstance);
until Result OR (IndexInstance>=10);
finally
FServerIPC.Active:=True;
end;
end;
end;
constructor TUniqueInstance.Create(aInstanceName: String; aServernameByUser: String);
begin
FServernameByUser := aServernameByUser;
FInstanceName:= aInstanceName + '-' + FServernameByUser;
{$IF DEFINED(UNIX)}
FInstanceName+= '-' + IntToStr(fpGetUID);
{$ENDIF}
end;
destructor TUniqueInstance.Destroy;
begin
if Assigned(FClientIPC) then
FreeAndNil(FClientIPC);
if Assigned(FServerIPC) then
FreeAndNil(FServerIPC);
inherited Destroy;
end;
{en
Initializes instance with given name (currently this is ApplicationName),
and user-provided servername (typically, an empty string).
If there is no already existing instance, then create it.
If there is already existing instance, and the current one is a client,
then send params to the server (i.e. to the existing instance)
If there is already existing instance, and the current one is not a client,
(i.e. gOnlyOneAppInstance is false and no --client/-c options were given),
then user-provided servername is altered: firstly, just add a trailing number '2'.
If there is already some trailing number, then increase it by 1, until
we found a servername that isn't busy yet, and then create instance
with this servername.
}
procedure InitInstanceWithName(aInstanceName: String; aServernameByUser: String);
{en
If a given servername doesn't contain a trailing number, then
add a trailing number '2'; otherwise increase existing number
and return resulting string.
}
function GetNextServername(CurServername: String): String;
var
SNameRegExp: TRegExpr;
CurNumber: Integer;
begin
SNameRegExp := TRegExpr.Create();
try
SNameRegExp.Expression := '(\d+)$';
if SNameRegExp.Exec(CurServername) then
begin
//-- there is existing trailing number, so, increase it by 1
CurNumber := StrToInt(SNameRegExp.Match[1]) + 1;
Result := ReplaceRegExpr(SNameRegExp.Expression, CurServername, IntToStr(CurNumber), False);
end
else
//-- there is no trailing number, so, add a trailing number '2'
Result := CurServername + '2';
finally
SNameRegExp.Free;
end;
end;
begin
FIsUniqueInstance := True;
//-- determine if the instance with the same name already exists
UniqueInstance := TUniqueInstance.Create(aInstanceName, aServernameByUser);
if UniqueInstance.IsRunInstance then
//-- it does exist, so, set flag that instance is not unique
FIsUniqueInstance := False;
//-- if instance is allowed (i.e. is not a client), then find unique
// servername
if IsInstanceAllowed then
begin
while UniqueInstance.IsRunInstance do
begin
UniqueInstance.Free;
aServernameByUser := GetNextServername(aServernameByUser);
UniqueInstance := TUniqueInstance.Create(aInstanceName, aServernameByUser);
end;
//-- unique servername is found, so, run it as a server and set
// FIsUniqueInstance flag
UniqueInstance.RunListen;
FIsUniqueInstance := True
end
else
//-- if this instance is not allowed (i.e. it's a client), then send params to the server.
UniqueInstance.SendParams;
end;
{en
Initialize instance with an application name and user-provided servername
(see detailed comment for InitInstanceWithName)
}
procedure InitInstance;
begin
InitInstanceWithName(ApplicationName, CommandLineParams.Servername);
end;
function IsUniqueInstance: Boolean;
begin
Result := FIsUniqueInstance;
end;
function WantsToBeClient: Boolean;
begin
Result := (gOnlyOneAppInstance or CommandLineParams.Client);
end;
function IsInstanceAllowed: Boolean;
begin
Result := (not WantsToBeClient) or IsUniqueInstance;
end;
finalization
if Assigned(UniqueInstance) then
begin
UniqueInstance.StopListen;
FreeAndNil(UniqueInstance);
end;
end.