mirror of https://gitlab.com/basile.b/dexed.git
added TCEProcess, custom TProcess class for Coedit
- will fix the "split line" issue - will fix globally the 'OnTerminate' event not called (the timer is always enabled, so osx version is guaranteed not to have this issue) - will be used widely (no more TProcess or TASyncProcess).
This commit is contained in:
parent
670ab28b67
commit
b911e10e93
|
@ -135,7 +135,7 @@
|
||||||
<PackageName Value="LCL"/>
|
<PackageName Value="LCL"/>
|
||||||
</Item6>
|
</Item6>
|
||||||
</RequiredPackages>
|
</RequiredPackages>
|
||||||
<Units Count="37">
|
<Units Count="38">
|
||||||
<Unit0>
|
<Unit0>
|
||||||
<Filename Value="coedit.lpr"/>
|
<Filename Value="coedit.lpr"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
@ -151,7 +151,6 @@
|
||||||
<Unit3>
|
<Unit3>
|
||||||
<Filename Value="..\src\ce_dcd.pas"/>
|
<Filename Value="..\src\ce_dcd.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="ce_dcd"/>
|
|
||||||
</Unit3>
|
</Unit3>
|
||||||
<Unit4>
|
<Unit4>
|
||||||
<Filename Value="..\src\ce_dlang.pas"/>
|
<Filename Value="..\src\ce_dlang.pas"/>
|
||||||
|
@ -187,7 +186,6 @@
|
||||||
<Unit11>
|
<Unit11>
|
||||||
<Filename Value="..\src\ce_interfaces.pas"/>
|
<Filename Value="..\src\ce_interfaces.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="ce_interfaces"/>
|
|
||||||
</Unit11>
|
</Unit11>
|
||||||
<Unit12>
|
<Unit12>
|
||||||
<Filename Value="..\src\ce_libman.pas"/>
|
<Filename Value="..\src\ce_libman.pas"/>
|
||||||
|
@ -199,7 +197,6 @@
|
||||||
<ComponentName Value="CELibManEditorWidget"/>
|
<ComponentName Value="CELibManEditorWidget"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="ce_libmaneditor"/>
|
|
||||||
</Unit13>
|
</Unit13>
|
||||||
<Unit14>
|
<Unit14>
|
||||||
<Filename Value="..\src\ce_main.pas"/>
|
<Filename Value="..\src\ce_main.pas"/>
|
||||||
|
@ -207,7 +204,6 @@
|
||||||
<ComponentName Value="CEMainForm"/>
|
<ComponentName Value="CEMainForm"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="ce_main"/>
|
|
||||||
</Unit14>
|
</Unit14>
|
||||||
<Unit15>
|
<Unit15>
|
||||||
<Filename Value="..\src\ce_messages.pas"/>
|
<Filename Value="..\src\ce_messages.pas"/>
|
||||||
|
@ -215,7 +211,6 @@
|
||||||
<ComponentName Value="CEMessagesWidget"/>
|
<ComponentName Value="CEMessagesWidget"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="ce_messages"/>
|
|
||||||
</Unit15>
|
</Unit15>
|
||||||
<Unit16>
|
<Unit16>
|
||||||
<Filename Value="..\src\ce_miniexplorer.pas"/>
|
<Filename Value="..\src\ce_miniexplorer.pas"/>
|
||||||
|
@ -227,7 +222,6 @@
|
||||||
<Unit17>
|
<Unit17>
|
||||||
<Filename Value="..\src\ce_mru.pas"/>
|
<Filename Value="..\src\ce_mru.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="ce_mru"/>
|
|
||||||
</Unit17>
|
</Unit17>
|
||||||
<Unit18>
|
<Unit18>
|
||||||
<Filename Value="..\src\ce_observer.pas"/>
|
<Filename Value="..\src\ce_observer.pas"/>
|
||||||
|
@ -253,12 +247,10 @@
|
||||||
<ComponentName Value="CEProjectConfigurationWidget"/>
|
<ComponentName Value="CEProjectConfigurationWidget"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="ce_projconf"/>
|
|
||||||
</Unit21>
|
</Unit21>
|
||||||
<Unit22>
|
<Unit22>
|
||||||
<Filename Value="..\src\ce_nativeproject.pas"/>
|
<Filename Value="..\src\ce_nativeproject.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="ce_nativeproject"/>
|
|
||||||
</Unit22>
|
</Unit22>
|
||||||
<Unit23>
|
<Unit23>
|
||||||
<Filename Value="..\src\ce_projinspect.pas"/>
|
<Filename Value="..\src\ce_projinspect.pas"/>
|
||||||
|
@ -266,7 +258,6 @@
|
||||||
<ComponentName Value="CEProjectInspectWidget"/>
|
<ComponentName Value="CEProjectInspectWidget"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="ce_projinspect"/>
|
|
||||||
</Unit23>
|
</Unit23>
|
||||||
<Unit24>
|
<Unit24>
|
||||||
<Filename Value="..\src\ce_search.pas"/>
|
<Filename Value="..\src\ce_search.pas"/>
|
||||||
|
@ -296,7 +287,6 @@
|
||||||
<Unit28>
|
<Unit28>
|
||||||
<Filename Value="..\src\ce_symstring.pas"/>
|
<Filename Value="..\src\ce_symstring.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="ce_symstring"/>
|
|
||||||
</Unit28>
|
</Unit28>
|
||||||
<Unit29>
|
<Unit29>
|
||||||
<Filename Value="..\src\ce_synmemo.pas"/>
|
<Filename Value="..\src\ce_synmemo.pas"/>
|
||||||
|
@ -308,7 +298,6 @@
|
||||||
<ComponentName Value="CETodoListWidget"/>
|
<ComponentName Value="CETodoListWidget"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="ce_todolist"/>
|
|
||||||
</Unit30>
|
</Unit30>
|
||||||
<Unit31>
|
<Unit31>
|
||||||
<Filename Value="..\src\ce_tools.pas"/>
|
<Filename Value="..\src\ce_tools.pas"/>
|
||||||
|
@ -340,6 +329,10 @@
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
</Unit36>
|
</Unit36>
|
||||||
|
<Unit37>
|
||||||
|
<Filename Value="..\src\ce_processes.pas"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
</Unit37>
|
||||||
</Units>
|
</Units>
|
||||||
</ProjectOptions>
|
</ProjectOptions>
|
||||||
<CompilerOptions>
|
<CompilerOptions>
|
||||||
|
|
|
@ -9,7 +9,7 @@ uses
|
||||||
Interfaces, Forms, lazcontrols, runtimetypeinfocontrols,
|
Interfaces, Forms, lazcontrols, runtimetypeinfocontrols,
|
||||||
ce_sharedres, ce_observer, ce_libman, ce_tools, ce_dcd, ce_main,
|
ce_sharedres, ce_observer, ce_libman, ce_tools, ce_dcd, ce_main,
|
||||||
ce_writableComponent, ce_symstring, ce_staticmacro, ce_inspectors,
|
ce_writableComponent, ce_symstring, ce_staticmacro, ce_inspectors,
|
||||||
ce_editoroptions, ce_dockoptions, ce_shortcutseditor, ce_mru;
|
ce_editoroptions, ce_dockoptions, ce_shortcutseditor, ce_mru, ce_processes;
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|
|
@ -722,6 +722,20 @@ begin
|
||||||
//
|
//
|
||||||
// note: aList.LoadFromStream() does not work, lines can be split, which breaks message parsing (e.g filename detector).
|
// note: aList.LoadFromStream() does not work, lines can be split, which breaks message parsing (e.g filename detector).
|
||||||
//
|
//
|
||||||
|
{
|
||||||
|
Split lines:
|
||||||
|
------------
|
||||||
|
|
||||||
|
The problem comes from TAsynProcess.OnReadData. When the output is read in the
|
||||||
|
event, it does not always finish on a full line.
|
||||||
|
|
||||||
|
Resolution:
|
||||||
|
-----------
|
||||||
|
|
||||||
|
in TAsynProcess.OnReadData Accumulate avalaible output in a stream.
|
||||||
|
Detects last line terminator in the accumation.
|
||||||
|
Load TStrings from this stream range.
|
||||||
|
}
|
||||||
sum := 0;
|
sum := 0;
|
||||||
str := TMemoryStream.Create;
|
str := TMemoryStream.Create;
|
||||||
try
|
try
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
unit ce_processes;
|
||||||
|
|
||||||
|
{$I ce_defines.inc}
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Classes, SysUtils, ExtCtrls, process, asyncprocess;
|
||||||
|
|
||||||
|
type
|
||||||
|
|
||||||
|
{
|
||||||
|
The common process runner 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: TIdleTimer;
|
||||||
|
fDoneTerminated: boolean;
|
||||||
|
procedure checkTerminated(sender: TObject);
|
||||||
|
procedure setOnTerminate(value: TNotifyEvent);
|
||||||
|
procedure setOnReadData(value: TNotifyEvent);
|
||||||
|
procedure internalDoOnReadData(sender: TObject);
|
||||||
|
procedure internalDoOnTerminate(sender: TObject);
|
||||||
|
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;
|
||||||
|
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 := TIdleTimer.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
|
||||||
|
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
|
||||||
|
fillOutputStack;
|
||||||
|
if fRealOnReadData <> nil then
|
||||||
|
fRealOnReadData(self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCEProcess.internalDoOnTerminate(sender: TObject);
|
||||||
|
begin
|
||||||
|
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;
|
||||||
|
|
||||||
|
end.
|
||||||
|
|
Loading…
Reference in New Issue