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"/>
|
||||
</Item6>
|
||||
</RequiredPackages>
|
||||
<Units Count="37">
|
||||
<Units Count="38">
|
||||
<Unit0>
|
||||
<Filename Value="coedit.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
|
@ -151,7 +151,6 @@
|
|||
<Unit3>
|
||||
<Filename Value="..\src\ce_dcd.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="ce_dcd"/>
|
||||
</Unit3>
|
||||
<Unit4>
|
||||
<Filename Value="..\src\ce_dlang.pas"/>
|
||||
|
@ -187,7 +186,6 @@
|
|||
<Unit11>
|
||||
<Filename Value="..\src\ce_interfaces.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="ce_interfaces"/>
|
||||
</Unit11>
|
||||
<Unit12>
|
||||
<Filename Value="..\src\ce_libman.pas"/>
|
||||
|
@ -199,7 +197,6 @@
|
|||
<ComponentName Value="CELibManEditorWidget"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="ce_libmaneditor"/>
|
||||
</Unit13>
|
||||
<Unit14>
|
||||
<Filename Value="..\src\ce_main.pas"/>
|
||||
|
@ -207,7 +204,6 @@
|
|||
<ComponentName Value="CEMainForm"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="ce_main"/>
|
||||
</Unit14>
|
||||
<Unit15>
|
||||
<Filename Value="..\src\ce_messages.pas"/>
|
||||
|
@ -215,7 +211,6 @@
|
|||
<ComponentName Value="CEMessagesWidget"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="ce_messages"/>
|
||||
</Unit15>
|
||||
<Unit16>
|
||||
<Filename Value="..\src\ce_miniexplorer.pas"/>
|
||||
|
@ -227,7 +222,6 @@
|
|||
<Unit17>
|
||||
<Filename Value="..\src\ce_mru.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="ce_mru"/>
|
||||
</Unit17>
|
||||
<Unit18>
|
||||
<Filename Value="..\src\ce_observer.pas"/>
|
||||
|
@ -253,12 +247,10 @@
|
|||
<ComponentName Value="CEProjectConfigurationWidget"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="ce_projconf"/>
|
||||
</Unit21>
|
||||
<Unit22>
|
||||
<Filename Value="..\src\ce_nativeproject.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="ce_nativeproject"/>
|
||||
</Unit22>
|
||||
<Unit23>
|
||||
<Filename Value="..\src\ce_projinspect.pas"/>
|
||||
|
@ -266,7 +258,6 @@
|
|||
<ComponentName Value="CEProjectInspectWidget"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="ce_projinspect"/>
|
||||
</Unit23>
|
||||
<Unit24>
|
||||
<Filename Value="..\src\ce_search.pas"/>
|
||||
|
@ -296,7 +287,6 @@
|
|||
<Unit28>
|
||||
<Filename Value="..\src\ce_symstring.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="ce_symstring"/>
|
||||
</Unit28>
|
||||
<Unit29>
|
||||
<Filename Value="..\src\ce_synmemo.pas"/>
|
||||
|
@ -308,7 +298,6 @@
|
|||
<ComponentName Value="CETodoListWidget"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="ce_todolist"/>
|
||||
</Unit30>
|
||||
<Unit31>
|
||||
<Filename Value="..\src\ce_tools.pas"/>
|
||||
|
@ -340,6 +329,10 @@
|
|||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
</Unit36>
|
||||
<Unit37>
|
||||
<Filename Value="..\src\ce_processes.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit37>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
|
|
|
@ -9,7 +9,7 @@ uses
|
|||
Interfaces, Forms, lazcontrols, runtimetypeinfocontrols,
|
||||
ce_sharedres, ce_observer, ce_libman, ce_tools, ce_dcd, ce_main,
|
||||
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}
|
||||
|
||||
|
|
|
@ -722,6 +722,20 @@ begin
|
|||
//
|
||||
// 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;
|
||||
str := TMemoryStream.Create;
|
||||
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