mirror of https://gitlab.com/basile.b/dexed.git
265 lines
6.9 KiB
Plaintext
265 lines
6.9 KiB
Plaintext
unit ce_processes;
|
|
|
|
{$I ce_defines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, ExtCtrls, process, asyncprocess;
|
|
|
|
type
|
|
|
|
{
|
|
The stanndard process wrapper used in Coedit.
|
|
|
|
This class solves several issues encountered when using TProcess and TAsyncProcess:
|
|
|
|
- OnTerminate event is never called under Linux.
|
|
Here a timer perdiodically check the process and call the event accordingly.
|
|
- TAsyncProcess.OnReadData event is not usable to read full output lines.
|
|
Here the output is accumulated in a TMemoryStream which allows to keep data
|
|
at the left of an unterminated line when a buffer is available.
|
|
|
|
The member Output is not usable anymore. Instead:
|
|
|
|
- getFullLines() can be used in OnReadData or after the execution to fill
|
|
a string list.
|
|
- OutputStack can be used to read the raw output. It allows to seek, which
|
|
overcomes another limitation of the basic process classes.
|
|
}
|
|
TCEProcess = class(TASyncProcess)
|
|
private
|
|
fRealOnTerminate: TNotifyEvent;
|
|
fRealOnReadData: TNotifyEvent;
|
|
fOutputStack: TMemoryStream;
|
|
fTerminateChecker: TTimer;
|
|
fDoneTerminated: boolean;
|
|
fHasRead: boolean;
|
|
procedure checkTerminated(sender: TObject);
|
|
procedure setOnTerminate(value: TNotifyEvent);
|
|
procedure setOnReadData(value: TNotifyEvent);
|
|
protected
|
|
procedure internalDoOnReadData(sender: TObject); virtual;
|
|
procedure internalDoOnTerminate(sender: TObject); virtual;
|
|
published
|
|
property OnTerminate write setOnTerminate;
|
|
property OnReadData write setOnReadData;
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
procedure execute; override;
|
|
// reads TProcess.OUtput in OutputStack
|
|
procedure fillOutputStack;
|
|
// fills list with the full lines contained in OutputStack
|
|
procedure getFullLines(list: TStrings; consume: boolean = true);
|
|
// access to a flexible copy of TProcess.Output
|
|
property OutputStack: TMemoryStream read fOutputStack;
|
|
// indicates if an output buffer is read
|
|
property hasRead: boolean read fHasRead;
|
|
end;
|
|
|
|
{
|
|
OnReadData is only called if no additional buffers are passed
|
|
during a timeout.
|
|
}
|
|
TCEAutoBufferedProcess = class(TCEProcess)
|
|
private
|
|
fNewBufferChecker: TTimer;
|
|
fNewBufferTimeOut: Integer;
|
|
fPreviousSize: Integer;
|
|
procedure newBufferCheckerChecks(sender: TObject);
|
|
procedure setTimeout(value: integer);
|
|
protected
|
|
procedure internalDoOnReadData(sender: TObject); override;
|
|
procedure internalDoOnTerminate(sender: TObject); override;
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
procedure execute; override;
|
|
property timeOut: integer read fNewBufferTimeOut write setTimeout;
|
|
end;
|
|
|
|
procedure killProcess(var proc: TCEProcess);
|
|
|
|
implementation
|
|
|
|
procedure killProcess(var proc: TCEProcess);
|
|
begin
|
|
if proc = nil then
|
|
exit;
|
|
if proc.Running then
|
|
proc.Terminate(0);
|
|
proc.Free;
|
|
proc := nil;
|
|
end;
|
|
|
|
constructor TCEProcess.create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FOutputStack := TMemoryStream.Create;
|
|
FTerminateChecker := TTimer.Create(nil);
|
|
FTerminateChecker.Interval := 50;
|
|
fTerminateChecker.OnTimer := @checkTerminated;
|
|
fTerminateChecker.Enabled := false;
|
|
//fTerminateChecker.AutoEnabled:= true;
|
|
TAsyncProcess(self).OnTerminate := @internalDoOnTerminate;
|
|
TAsyncProcess(self).OnReadData := @internalDoOnReadData;
|
|
end;
|
|
|
|
destructor TCEProcess.destroy;
|
|
begin
|
|
FTerminateChecker.Free;
|
|
FOutputStack.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCEProcess.Execute;
|
|
begin
|
|
fHasRead := false;
|
|
fOutputStack.Clear;
|
|
fDoneTerminated := false;
|
|
TAsyncProcess(self).OnReadData := @internalDoOnReadData;
|
|
TAsyncProcess(self).OnTerminate := @internalDoOnTerminate;
|
|
fTerminateChecker.Enabled := true;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCEProcess.fillOutputStack;
|
|
var
|
|
sum, cnt: Integer;
|
|
begin
|
|
if not (poUsePipes in Options) then
|
|
exit;
|
|
sum := fOutputStack.Size;
|
|
while (Output <> nil) and (NumBytesAvailable > 0) do
|
|
begin
|
|
fOutputStack.SetSize(sum + 1024);
|
|
cnt := Output.Read((fOutputStack.Memory + sum)^, 1024);
|
|
sum += cnt;
|
|
end;
|
|
fOutputStack.SetSize(sum);
|
|
end;
|
|
|
|
procedure TCEProcess.getFullLines(list: TStrings; consume: boolean = true);
|
|
var
|
|
stored: Integer;
|
|
lastTerm: Integer;
|
|
toread: Integer;
|
|
buff: Byte = 0;
|
|
str: TMemoryStream;
|
|
begin
|
|
if not Running then
|
|
begin
|
|
list.LoadFromStream(fOutputStack);
|
|
if consume then
|
|
fOutputStack.Clear;
|
|
end else
|
|
begin
|
|
lastTerm := fOutputStack.Position;
|
|
stored := fOutputStack.Position;
|
|
while fOutputStack.Read(buff, 1) = 1 do
|
|
if buff = 10 then lastTerm := fOutputStack.Position;
|
|
fOutputStack.Position := stored;
|
|
if lastTerm <> stored then
|
|
begin
|
|
str := TMemoryStream.Create;
|
|
try
|
|
toread := lastTerm - stored;
|
|
str.SetSize(toRead);
|
|
fOutputStack.Read(str.Memory^, toread);
|
|
list.LoadFromStream(str);
|
|
finally
|
|
str.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEProcess.setOnTerminate(value: TNotifyEvent);
|
|
begin
|
|
fRealOnTerminate := value;
|
|
TAsyncProcess(self).OnTerminate := @internalDoOnTerminate;
|
|
end;
|
|
|
|
procedure TCEProcess.setOnReadData(value: TNotifyEvent);
|
|
begin
|
|
fRealOnReadData := value;
|
|
TAsyncProcess(self).OnReadData := @internalDoOnReadData;
|
|
end;
|
|
|
|
procedure TCEProcess.internalDoOnReadData(sender: TObject);
|
|
begin
|
|
fHasRead := true;
|
|
fillOutputStack;
|
|
if fRealOnReadData <> nil then
|
|
fRealOnReadData(self);
|
|
end;
|
|
|
|
procedure TCEProcess.internalDoOnTerminate(sender: TObject);
|
|
begin
|
|
fHasRead := false;
|
|
fTerminateChecker.Enabled := false;
|
|
if fDoneTerminated then exit;
|
|
fDoneTerminated := true;
|
|
//
|
|
fillOutputStack;
|
|
if fRealOnTerminate <> nil then
|
|
fRealOnTerminate(self);
|
|
end;
|
|
|
|
procedure TCEProcess.checkTerminated(sender: TObject);
|
|
begin
|
|
if Running then
|
|
exit;
|
|
fTerminateChecker.Enabled := false;
|
|
internalDoOnTerminate(self);
|
|
end;
|
|
|
|
constructor TCEAutoBufferedProcess.create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
fNewBufferTimeOut := 1000;
|
|
fNewBufferChecker := TTimer.Create(self);
|
|
fNewBufferChecker.Enabled:= false;
|
|
fNewBufferChecker.Interval:= fNewBufferTimeOut;
|
|
fNewBufferChecker.OnTimer:= @newBufferCheckerChecks;
|
|
end;
|
|
|
|
procedure TCEAutoBufferedProcess.setTimeout(value: integer);
|
|
begin
|
|
if fNewBufferTimeOut = value then
|
|
exit;
|
|
fNewBufferTimeOut := value;
|
|
fNewBufferChecker.Interval:= fNewBufferTimeOut;
|
|
end;
|
|
|
|
procedure TCEAutoBufferedProcess.execute;
|
|
begin
|
|
fPreviousSize := fOutputStack.Size;
|
|
fNewBufferChecker.Enabled:=true;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCEAutoBufferedProcess.newBufferCheckerChecks(sender: TObject);
|
|
begin
|
|
if fOutputStack.Size = fPreviousSize then
|
|
begin
|
|
if assigned(fRealOnReadData) then
|
|
fRealOnReadData(self);
|
|
end;
|
|
fPreviousSize := fOutputStack.Size;
|
|
end;
|
|
|
|
procedure TCEAutoBufferedProcess.internalDoOnReadData(sender: TObject);
|
|
begin
|
|
fillOutputStack;
|
|
end;
|
|
|
|
procedure TCEAutoBufferedProcess.internalDoOnTerminate(sender: TObject);
|
|
begin
|
|
fNewBufferChecker.Enabled:=false;
|
|
inherited;
|
|
end;
|
|
|
|
end.
|
|
|