diff --git a/neural.lfm b/neural.lfm new file mode 100644 index 0000000..459b3ca --- /dev/null +++ b/neural.lfm @@ -0,0 +1,69 @@ +object MainForm: TMainForm + Left = 530 + Height = 611 + Top = 632 + Width = 891 + Caption = 'Neural Amp Cenrtral' + ClientHeight = 581 + ClientWidth = 891 + DesignTimePPI = 144 + Menu = MainMenu1 + LCLVersion = '2.2.6.0' + object MemConsole: TRichMemo + Left = 16 + Height = 344 + Top = 216 + Width = 865 + HideSelection = False + TabOrder = 0 + ZoomFactor = 1 + end + object NAMButton: TButton + Left = 256 + Height = 86 + Top = 88 + Width = 374 + Caption = 'NAM!!!' + Color = clForm + OnClick = NAMButtonClick + ParentFont = False + TabOrder = 1 + end + object MainMenu1: TMainMenu + Left = 51 + Top = 24 + object MenuItem2: TMenuItem + Caption = '&File' + object MenuItem7: TMenuItem + Caption = '-' + end + object MenuItem10: TMenuItem + Caption = '-' + end + object MenuItem11: TMenuItem + Caption = 'E&xit' + end + end + object MenuItem15: TMenuItem + Caption = '&Help' + object MenuItem18: TMenuItem + Caption = '&Online Help' + end + object MenuItem19: TMenuItem + Caption = '-' + end + object MenuItem20: TMenuItem + Caption = '&Licence Information' + end + object MenuItem21: TMenuItem + Caption = '&Check for Updates' + end + object MenuItem22: TMenuItem + Caption = '-' + end + object MenuItem23: TMenuItem + Caption = '&About' + end + end + end +end diff --git a/neural.pas b/neural.pas new file mode 100644 index 0000000..db22299 --- /dev/null +++ b/neural.pas @@ -0,0 +1,156 @@ +unit neural; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, FileUtil, process, Forms, Controls, Graphics, Dialogs, Menus, + StdCtrls, RTTICtrls, RichMemo, Zipper, Processutils; + +type + + { TMainForm } + + TMainForm = class(TForm) + MainMenu1: TMainMenu; + MemConsole: TRichMemo; + MenuItem10: TMenuItem; + MenuItem11: TMenuItem; + MenuItem15: TMenuItem; + MenuItem18: TMenuItem; + MenuItem19: TMenuItem; + MenuItem2: TMenuItem; + MenuItem20: TMenuItem; + MenuItem21: TMenuItem; + MenuItem22: TMenuItem; + MenuItem23: TMenuItem; + MenuItem7: TMenuItem; + NAMButton: TButton; + procedure NAMButtonClick(Sender: TObject); + + private + + public + procedure ProcessOutput(Sender:TProcessEx; output:string); + procedure ProcessError(Sender:TProcessEx; {%H-}IsException:boolean); + + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.lfm} + +{ TMainForm } + + + procedure TMainForm.NAMButtonClick(Sender: TObject); + + var + Proc: TProcessEx; + namrunner:TextFile; + begin + //StartProcessAndStreamStdioToMemo('NAM-Runner.bat',MemConsole) + AssignFile(namrunner, 'NAM-Runner060.bat'); + // Try + Rewrite(namrunner); + Writeln(namrunner,'');//Remember AnsiStrings are case sensitive + Writeln(namrunner,'@echo off'); + Writeln(namrunner,'set NAMNAME=neural-amp-modeler-0.6.0'); + Writeln(namrunner,'set NAMVER=0.6.0'); + Writeln(namrunner,'if exist "%~dp0\%NAMNAME%\installed.txt" ('); + Writeln(namrunner,'echo NAM already installed!'); + Writeln(namrunner,'GOTO NAMISINSTALLED'); + Writeln(namrunner,')'); + Writeln(namrunner,'echo This program is downloading and installing the complete NAM modelling environment and all prerequisites and runtimes.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo PLEASE BE PATIENT.'); + Writeln(namrunner,'echo SOME PARTS OF THIS INSTALLATION PROCESS CAN TAKE QUITE SOME TIME!'); + Writeln(namrunner,'echo DON''T CLOSE THIS WINDOW UNTIL YOU ARE ASKED TO DO IT.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo Downloading and extracting Python archive...'); + Writeln(namrunner,'curl -L https://github.com/winpython/winpython/releases/download/6.1.20230527/Winpython64-3.10.11.1dot.exe -o python.exe'); + Writeln(namrunner,'if exist "%~dp0\%NAMNAME%" rmdir /q "%~dp0\%NAMNAME%"'); + Writeln(namrunner,'python.exe -y'); + Writeln(namrunner,'@echo. |call %~dp0\WPy64-310111\scripts\make_winpython_movable.bat'); + Writeln(namrunner,'move /Y "%~dp0\WPy64-310111\python-3.10.11.amd64" "%NAMNAME%"'); + Writeln(namrunner,'echo Removing Python archive and unused files...'); + Writeln(namrunner,'del /f /s /q "%~dp0\WPy64-310111" 1>nul'); + Writeln(namrunner,'rmdir /s /q "%~dp0\WPy64-310111"'); + Writeln(namrunner,'del python.exe'); + Writeln(namrunner,'echo Done.'); + Writeln(namrunner,'cd %NAMNAME%'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'set PYTHONPATH=%~dp0\%NAMNAME%;%~dp0\%NAMNAME%\DLLs;%~dp0\%NAMNAME%\lib;%~dp0\%NAMNAME%\lib\plat-win;%~dp0\%NAMNAME%\lib\site-packages'); + Writeln(namrunner,'set PATH=%~dp0%NAMNAME%;%~dp0%NAMNAME%\Scripts;%PATH%'); + Writeln(namrunner,'echo Upgrading PIP...'); + Writeln(namrunner,'python.exe -m pip install --upgrade pip'); + Writeln(namrunner,'echo Done.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo Installing NAM...'); + Writeln(namrunner,'python -m pip install neural-amp-modeler==%NAMVER%'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo Installing torch gpu...'); + Writeln(namrunner,'pip3 install scipy==1.10.1'); + Writeln(namrunner,'pip3 install torch torchvision torchaudio --force-reinstall --index-url https://download.pytorch.org/whl/cu118'); + Writeln(namrunner,'echo Done.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'>"%~dp0%NAMNAME%\installed.txt" echo done'); + Writeln(namrunner,'echo NAM install done.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,':NAMISINSTALLED'); + Writeln(namrunner,'set PYTHONPATH=%~dp0\%NAMNAME%;%~dp0\%NAMNAME%\DLLs;%~dp0\%NAMNAME%\lib;%~dp0\%NAMNAME%\lib\plat-win;%~dp0\%NAMNAME%\lib\site-packages'); + Writeln(namrunner,'set PATH=%~dp0%NAMNAME%;%~dp0%NAMNAME%\Scripts;%PATH%'); + Writeln(namrunner,'python -c "from winpython import wppm;dist=wppm.Distribution(r''%~dp0\%NAMNAME%'');dist.patch_standard_packages(''pip'', to_movable=True)"'); + Writeln(namrunner,'nam'); + Writeln(namrunner,''); + Writeln(namrunner,'cd..'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'echo.'); + Writeln(namrunner,'::echo This window can now be closed.'); + Writeln(namrunner,'echo In case something went wrong or the installation got corrupted:'); + Writeln(namrunner,'echo You can simply delete the folder %NAMNAME% and try a reinstall.'); + Writeln(namrunner,'echo Note, that a reinstallation needs an internet connection.'); + Writeln(namrunner,'echo Thank you.'); + //Finally + CloseFile(namrunner); + //End; + + try + Proc := TProcessEx.Create(nil); + Proc.Executable := 'NAM-Runner060.bat'; + Proc.OnErrorM:=@(ProcessError); + Proc.OnOutputM:=@(ProcessOutput); + Proc.Execute(); + finally + Proc.Free; + end; + DeleteFile('NAM-Runner060.bat'); + end; + + + procedure TMainForm.ProcessError(Sender: TProcessEx; IsException: boolean); + begin + MemConsole.Lines.Append('Erreur ! ' + Sender.ExceptionInfo); + end; + + procedure TMainForm.ProcessOutput(Sender: TProcessEx; output : String); + begin + MemConsole.Lines.Text := MemConsole.Lines.Text + output; + // si vous avez des problème d'accent + //MemConsole.Lines.Text := MemConsole.Lines.Text + ConsoleToUtf8(output); + // pour scroll automatique + MemConsole.SelStart := Length(MemConsole.Lines.Text)-1; + MemConsole.SelLength:=0; + Application.ProcessMessages; + end; + +end. + diff --git a/neuralampcentral.ico b/neuralampcentral.ico new file mode 100644 index 0000000..10c5fc1 Binary files /dev/null and b/neuralampcentral.ico differ diff --git a/neuralampcentral.lpi b/neuralampcentral.lpi new file mode 100644 index 0000000..461807c --- /dev/null +++ b/neuralampcentral.lpi @@ -0,0 +1,125 @@ + + + + + + + + + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + <LongPathAware Value="True"/> + <TextName Value="photophobic martin"/> + <TextDesc Value=""/> + </XPManifest> + <Icon Value="0"/> + </General> + <VersionInfo> + <UseVersionInfo Value="True"/> + <AutoIncrementBuild Value="True"/> + <MinorVersionNr Value="1"/> + <RevisionNr Value="1"/> + <BuildNr Value="42"/> + <Attributes pvaPreRelease="True" pvaPrivateBuild="True"/> + <StringTable CompanyName="photophobic martin" FileDescription="Installs and runs NAM trainer" InternalName="Neural Amp Central"/> + </VersionInfo> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <OpenInFileMan Value="True"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="bgracontrols"/> + </Item> + <Item> + <PackageName Value="RunTimeTypeInfoControls"/> + </Item> + <Item> + <PackageName Value="LainzControls"/> + </Item> + <Item> + <PackageName Value="FCL"/> + </Item> + <Item> + <PackageName Value="richmemopackage"/> + </Item> + <Item> + <PackageName Value="python4lazarus_package"/> + </Item> + <Item> + <PackageName Value="rxnew"/> + </Item> + <Item> + <PackageName Value="atflatcontrols_package"/> + </Item> + <Item> + <PackageName Value="LCL"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="neuralampcentral.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="neural.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit> + <Unit> + <Filename Value="nachelper.pas"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="neuralampcentral"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <CodeGeneration> + <SmallerCode Value="True"/> + </CodeGeneration> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf2Set"/> + </Debugging> + <LinkSmart Value="True"/> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/neuralampcentral.lpr b/neuralampcentral.lpr new file mode 100644 index 0000000..8923674 --- /dev/null +++ b/neuralampcentral.lpr @@ -0,0 +1,26 @@ +program neuralampcentral; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + {$IFDEF HASAMIGA} + athreads, + {$ENDIF} + Interfaces, // this includes the LCL widgetset + Forms, runtimetypeinfocontrols, neural + { you can add units after this }; + +{$R *.res} + +begin + RequireDerivedFormResource:=True; + Application.Title:='Neural Amp Central'; + Application.Scaled:=True; + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. + diff --git a/neuralampcentral.lps b/neuralampcentral.lps new file mode 100644 index 0000000..ecb94c6 --- /dev/null +++ b/neuralampcentral.lps @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectSession> + <PathDelim Value="\"/> + <Version Value="12"/> + <BuildModes Active="Default"/> + <Units> + <Unit> + <Filename Value="neuralampcentral.lpr"/> + <IsPartOfProject Value="True"/> + <EditorIndex Value="2"/> + <TopLine Value="4"/> + <CursorPos X="44" Y="9"/> + <UsageCount Value="30"/> + <Loaded Value="True"/> + </Unit> + <Unit> + <Filename Value="neural.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + <IsVisibleTab Value="True"/> + <TopLine Value="136"/> + <CursorPos X="32" Y="152"/> + <UsageCount Value="30"/> + <Loaded Value="True"/> + <LoadedDesigner Value="True"/> + </Unit> + <Unit> + <Filename Value="nachelper.pas"/> + <IsPartOfProject Value="True"/> + <EditorIndex Value="-1"/> + <CursorPos X="85" Y="13"/> + <UsageCount Value="30"/> + </Unit> + <Unit> + <Filename Value="processutils.pas"/> + <EditorIndex Value="1"/> + <TopLine Value="136"/> + <CursorPos X="37" Y="51"/> + <UsageCount Value="15"/> + <Loaded Value="True"/> + </Unit> + </Units> + <JumpHistory HistoryIndex="28"> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="133" Column="104" TopLine="133"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="132" Column="104" TopLine="132"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="131" Column="104" TopLine="131"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="130" Column="104" TopLine="130"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="129" Column="104" TopLine="129"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="128" Column="104" TopLine="128"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="127" Column="104" TopLine="127"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="126" Column="104" TopLine="126"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="125" Column="104" TopLine="125"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="124" Column="104" TopLine="124"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="123" Column="104" TopLine="123"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="122" Column="104" TopLine="122"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="121" Column="104" TopLine="121"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="122" Column="104" TopLine="122"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="56" Column="23" TopLine="43"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="146" Column="56" TopLine="47"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="145" Column="56" TopLine="126"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="143" Column="22" TopLine="40"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="57" Column="4" TopLine="51"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="58" Column="4" TopLine="52"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="57" Column="4" TopLine="51"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="9" Column="63" TopLine="3"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="9" Column="40" TopLine="4"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="155" Column="18" TopLine="146"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="8" Column="32"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="162" Column="22" TopLine="146"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="143" Column="6" TopLine="136"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="142" Column="6" TopLine="135"/> + </Position> + <Position> + <Filename Value="neural.pas"/> + <Caret Line="30" Column="35" TopLine="17"/> + </Position> + </JumpHistory> + <RunParams> + <FormatVersion Value="2"/> + <Modes ActiveMode=""/> + </RunParams> + </ProjectSession> +</CONFIG> diff --git a/neuralampcentral.res b/neuralampcentral.res new file mode 100644 index 0000000..f15e049 Binary files /dev/null and b/neuralampcentral.res differ diff --git a/processutils.pas b/processutils.pas new file mode 100644 index 0000000..90d5ad4 --- /dev/null +++ b/processutils.pas @@ -0,0 +1,635 @@ +{ Process utility unit. Extends TProcess. +Not unicode-aware (change this when FPC becomes so). + +Copyright (C) 2012-2014 Ludo Brands, Reinier Olislagers + +This unit is licensed as modified LGPL or MIT, at your choice. Licenses below +} +{ +This library is free software; you can redistribute it and/or modify it +under the terms of the GNU Library General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at your +option) any later version with the following modification: + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent modules,and +to copy and distribute the resulting executable under terms of your choice, +provided that you also meet, for each linked independent module, the terms +and conditions of the license of that module. An independent module is a +module which is not derived from or based on this library. If you modify +this library, you may extend this exception to your version of the library, +but you are not obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + +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. See the GNU Library General Public License +for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, +Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +} +{ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +} +unit processutils; + +{$mode objfpc}{$H+} +{not $DEFINE DEBUGCONSOLE} //define debug to get writeln output of commands called + +interface + +uses + Classes, SysUtils, + process, + //UTF8Process, + strutils; + +const + // Internal error code/result codes: + PROC_INTERNALERROR=-1; // error while running process code in this unit + PROC_INTERNALEXCEPTION=-2; //exception while running process code in this unit + {$IFDEF MSWINDOWS} + PATHVARNAME = 'Path'; //Name for path environment variable + {$ELSE} + //Unix/Linux + PATHVARNAME = 'PATH'; + {$ENDIF MSWINDOWS} + +type + TProcessEx = class; //forward + TDumpFunc = procedure (Sender:TProcessEx; output:string); + TDumpMethod = procedure (Sender:TProcessEx; output:string) of object; + TErrorFunc = procedure (Sender:TProcessEx;IsException:boolean); + TErrorMethod = procedure (Sender:TProcessEx;IsException:boolean) of object; + + { TProcessEnvironment } + TProcessEnvironment = class(TObject) + private + FEnvironmentList:TStringList; + FCaseSensitive:boolean; + function GetVarIndex(VarName:string):integer; + public + // Get environment variable + function GetVar(VarName:string):string; + // Set environment variable + procedure SetVar(VarName,VarValue:string); + // List of all environment variables (name and value) + property EnvironmentList:TStringList read FEnvironmentList; + constructor Create; + destructor Destroy; override; + end; + + { TProcessEx } + + TProcessEx = class(TProcess) + //TProcessEx = class(TProcessUTF8) + private + FExceptionInfoStrings: TstringList; + FExitStatus: integer; //result code/exit status that executable returned with + FOnError: TErrorFunc; + FOnErrorM: TErrorMethod; + FOnOutput: TDumpFunc; + FOnOutputM: TDumpMethod; + FOutputStrings: TStringList; + FOutStream: TMemoryStream; + FProcessEnvironment:TProcessEnvironment; + function GetResultingCommand: string; + function GetExceptionInfo: string; + function GetOutputString: string; + function GetOutputStrings: TStringList; + function GetParametersString: String; + function GetProcessEnvironment: TProcessEnvironment; + procedure SetOnError(AValue: TErrorFunc); + procedure SetOnErrorM(AValue: TErrorMethod); + procedure SetOnOutput(AValue: TDumpFunc); + procedure SetOnOutputM(AValue: TDumpMethod); + public + // Run executable with parameters etc. Comes in place of inherited execute. + {%H-}procedure Execute; + // Executable+parameters. Use Executable and Parameters/ParametersString to assign + property ResultingCommand: string read GetResultingCommand; + // All environment variables, e.g. PATH + property Environment:TProcessEnvironment read GetProcessEnvironment; + property ExceptionInfo:string read GetExceptionInfo; + property ExceptionInfoStrings:TstringList read FExceptionInfoStrings; + // Return code/exit status that the process returned with. Often 0 for success. + property ExitStatus:integer read FExitStatus; + // Use callback to catch error messages + property OnError:TErrorFunc read FOnError write SetOnError; + // Use callback to catch error messages + property OnErrorM:TErrorMethod read FOnErrorM write SetOnErrorM; + property OnOutput:TDumpFunc read FOnOutput write SetOnOutput; + property OnOutputM:TDumpMethod read FOnOutputM write SetOnOutputM; + property OutputString:string read GetOutputString; + property OutputStrings:TStringList read GetOutputStrings; + constructor Create(AOwner : TComponent); override; + destructor Destroy; override; + end; + +// Convenience functions + +// Runs command, returns result code. Negative codes are processutils internal error codes +function ExecuteCommand(Commandline: string; Verbose:boolean): integer; overload; +// Runs command, returns result code. Negative codes are processutils internal error codes +function ExecuteCommand(Commandline: string; out Output:string; Verbose:boolean): integer; overload; +// Runs command, returns result code. Negative codes are processutils internal error codes +function ExecuteCommand(Commandline: string; Output : TStream; Verbose:boolean): integer; overload; +// Runs command, returns result code. Negative codes are processutils internal error codes +function ExecuteCommandInDir(Commandline, Directory: string; Verbose:boolean): integer; overload; +// Runs command, returns result code. Negative codes are processutils internal error codes +function ExecuteCommandInDir(Commandline, Directory: string; out Output:string; Verbose:boolean): integer; overload; +// Runs command, returns result code. Negative codes are processutils internal error codes +// PrependPath is prepended to existing path. If empty, keep current path +function ExecuteCommandInDir(Commandline, Directory: string; out Output:string; PrependPath: string; Verbose:boolean): integer; overload; +// Don't process comamndline +function ExecutePlainCommand(Commandline: string; out Output: string; Verbose: boolean): integer; +// Writes output to console +procedure DumpConsole(Sender:TProcessEx; output:string); + +implementation + +{$ifdef LCL} +uses + Forms,Controls; +{$endif} + +{ TProcessEx } + +function TProcessEx.GetOutputString: string; +begin + result:=OutputStrings.Text; +end; + +function TProcessEx.GetOutputStrings: TStringList; +begin + if (FOutputStrings.Count=0) and (FOutStream.Size>0) then + begin + FOutStream.Position := 0; + FOutputStrings.LoadFromStream(FOutStream); + FOutStream.Clear; + end; + result:=FOutputStrings; +end; + +function TProcessEx.GetParametersString: String; +begin + result:=AnsiReplaceStr(Parameters.text, LineEnding, ' '); +end; + +function TProcessEx.GetExceptionInfo: string; +begin + result:=FExceptionInfoStrings.Text; +end; + +function TProcessEx.GetResultingCommand: string; +var i:integer; +begin + //this is not the command as executed. The quotes are surrounding individual params. + //the actual quoting is platform dependent + //perhaps better to use another quoting character to make this clear to the user. + result:=Executable; + for i:=0 to Parameters.Count-1 do + result:=result+' "'+Parameters[i]+'"'; +end; + +function TProcessEx.GetProcessEnvironment: TProcessEnvironment; +begin + If not assigned(FProcessEnvironment) then + FProcessEnvironment:=TProcessEnvironment.Create; + result:=FProcessEnvironment; +end; + +procedure TProcessEx.SetOnError(AValue: TErrorFunc); +begin + if FOnError=AValue then Exit; + FOnError:=AValue; +end; + +procedure TProcessEx.SetOnErrorM(AValue: TErrorMethod); +begin + if FOnErrorM=AValue then Exit; + FOnErrorM:=AValue; +end; + +procedure TProcessEx.SetOnOutput(AValue: TDumpFunc); +begin + if FOnOutput=AValue then Exit; + FOnOutput:=AValue; +end; + +procedure TProcessEx.SetOnOutputM(AValue: TDumpMethod); +begin + if FOnOutputM=AValue then Exit; + FOnOutputM:=AValue; +end; + +procedure TProcessEx.Execute; +{$ifdef LCL} +var + i:integer; +{$endif} +function ReadOutput: boolean; +const + BufSize = 4096; +var + Buffer: array[0..BufSize - 1] of byte; + ReadBytes: integer; +begin + Result := False; + while Output.NumBytesAvailable > 0 do + begin + ReadBytes := Output.Read({%H-}Buffer, BufSize); + FOutStream.Write(Buffer, ReadBytes); + if Assigned(FOnOutput) then + FOnOutput(Self,copy(pchar(@buffer[0]),1,ReadBytes)); + if Assigned(FOnOutputM) then + FOnOutputM(Self,copy(pchar(@buffer[0]),1,ReadBytes)); + Result := True; + end; +end; + +begin + try + // "Normal" linux and DOS exit codes are in the range 0 to 255. + // Windows System Error Codes are 0 to 15999 + // Use negatives for internal errors. + FExitStatus:=PROC_INTERNALERROR; + FExceptionInfoStrings.Clear; + FOutputStrings.Clear; + FOutStream.Clear; + if Assigned(FProcessEnvironment) then + inherited Environment:=FProcessEnvironment.EnvironmentList; + Options := Options +[poUsePipes, poStderrToOutPut]; + if Assigned(FOnOutput) then + FOnOutput(Self,'Executing: '+ResultingCommand+' (working dir: '+ CurrentDirectory +')'+ LineEnding); + if Assigned(FOnOutputM) then + FOnOutputM(Self,'Executing: '+ResultingCommand+' (working dir: '+ CurrentDirectory +')'+ LineEnding); + + try + if CurrentDirectory<>'' then + begin + // Avoid unpredictable behaviour as well as + // OSX bug 26706 (fixed in FPC trunk) + if not(DirectoryExists(CurrentDirectory)) then + begin + FExitStatus:=PROC_INTERNALEXCEPTION; + FExceptionInfoStrings.Add('Invalid directory: '+CurrentDirectory); + FExitStatus:=PROC_INTERNALEXCEPTION; + if (Assigned(OnError) or Assigned(OnErrorM)) then + OnError(Self,false) + else + OnErrorM(Self,false); + exit; + end; + end; + {$ifdef LCL} + i:=0; + {$endif} + inherited Execute; + while Running do + begin + if not ReadOutput then + begin + {$ifdef LCL} + Sleep(10); + if (i<100) then Inc(i); + // process message queue after 50ms + if (i>5) then Application.ProcessMessages; + // set cursor after 1 second of execution time + if (i=99) then Application.MainForm.Cursor:=crHourGlass; + {$else} + Sleep(100); + {$endif} + end; + end; + ReadOutput; + {$ifdef LCL} + if Application.MainForm.Cursor=crHourGlass then + begin + Application.MainForm.Cursor:=crDefault; + Application.ProcessMessages; + end; + {$endif} + + FExitStatus:=inherited ExitStatus; + except + // Leave exitstatus as proc_internalerror + // This should handle calling non-existing application etc. + // Note also bug 22055 TProcess ExitStatus is zero when the called process Seg Faults + end; + + if (FExitStatus<>0) and (Assigned(OnError) or Assigned(OnErrorM)) then + if Assigned(OnError) then + OnError(Self,false) + else + OnErrorM(Self,false); + except + on E: Exception do + begin + FExceptionInfoStrings.Add('Exception calling '+Executable+' '+Parameters.Text); + FExceptionInfoStrings.Add('Details: '+E.ClassName+'/'+E.Message); + FExitStatus:=PROC_INTERNALEXCEPTION; + if (Assigned(OnError) or Assigned(OnErrorM)) then + OnError(Self,false) + else + OnErrorM(Self,false); + end; + end; +end; + +constructor TProcessEx.Create(AOwner : TComponent); +begin + inherited; + {$ifdef LCL} + Self.ShowWindow:=swoHIDE; + {$endif} + FExceptionInfoStrings:= TstringList.Create; + FOutputStrings:= TstringList.Create; + FOutStream := TMemoryStream.Create; +end; + +destructor TProcessEx.Destroy; +begin + FExceptionInfoStrings.Free; + FOutputStrings.Free; + FOutStream.Free; + If assigned(FProcessEnvironment) then + FProcessEnvironment.Free; + inherited Destroy; +end; + +{ TProcessEnvironment } + +function TProcessEnvironment.GetVarIndex(VarName: string): integer; +var + idx:integer; + + function ExtractVar(VarVal:string):string; + begin + result:=''; + if length(Varval)>0 then + begin + if VarVal[1] = '=' then //windows + delete(VarVal,1,1); + result:=trim(copy(VarVal,1,pos('=',VarVal)-1)); + if not FCaseSensitive then + result:=UpperCase(result); + end + end; + +begin + if (Length(VarName)=0) then + begin + result:=-1; + end + else + begin + if not FCaseSensitive then + VarName:=UpperCase(VarName); + idx:=0; + while idx<FEnvironmentList.Count do + begin + if VarName = ExtractVar(FEnvironmentList[idx]) then + break; + idx:=idx+1; + end; + if idx<FEnvironmentList.Count then + result:=idx + else + result:=-1; + end; +end; + +function TProcessEnvironment.GetVar(VarName: string): string; +var + idx:integer; + + function ExtractVal(VarVal:string):string; + begin + result:=''; + if length(Varval)>0 then + begin + if VarVal[1] = '=' then //windows + delete(VarVal,1,1); + result:=trim(copy(VarVal,pos('=',VarVal)+1,length(VarVal))); + end + end; + +begin + idx:=GetVarIndex(VarName); + if idx>=0 then + result:=ExtractVal(FEnvironmentList[idx]) + else + result:=''; +end; + +procedure TProcessEnvironment.SetVar(VarName, VarValue: string); +var + idx:integer; + s:string; +begin + if (Length(VarName)=0) OR (Length(VarValue)=0) then exit; + idx:=GetVarIndex(VarName); + s:=trim(Varname)+'='+trim(VarValue); + if idx>=0 then + FEnvironmentList[idx]:=s + else + FEnvironmentList.Add(s); +end; + +constructor TProcessEnvironment.Create; +var + i: integer; +begin + FEnvironmentList:=TStringList.Create; + {$ifdef WINDOWS} + FCaseSensitive:=false; + {$else} + FCaseSensitive:=true; + {$endif WINDOWS} + // GetEnvironmentVariableCount is 1 based + for i:=1 to GetEnvironmentVariableCount do + EnvironmentList.Add(trim(GetEnvironmentString(i))); +end; + +destructor TProcessEnvironment.Destroy; +begin + FEnvironmentList.Free; + inherited Destroy; +end; + +procedure DumpConsole(Sender:TProcessEx; output:string); +begin + write(output); +end; + +function ExecuteCommand(Commandline: string; Verbose: boolean): integer; +var + s:string=''; +begin + Result:=ExecuteCommandInDir(Commandline,'',s,Verbose); +end; + +function ExecuteCommand(Commandline: string; out Output: string; + Verbose: boolean): integer; +begin + Result:=ExecuteCommandInDir(Commandline,'',Output,Verbose); +end; + +function ExecuteCommand(Commandline: string; Output : TStream; + Verbose: boolean): integer; +begin + // to be done + //Result:=ExecuteCommandInDir(Commandline,'',Output,Verbose); +end; + + +function ExecuteCommandInDir(Commandline, Directory: string; Verbose: boolean + ): integer; +var + s:string=''; +begin + Result:=ExecuteCommandInDir(Commandline,Directory,s,Verbose); +end; + +function ExecuteCommandInDir(Commandline, Directory: string; + out Output: string; Verbose: boolean): integer; +begin + Result:=ExecuteCommandInDir(CommandLine,Directory,Output,'',Verbose); +end; + +function ExecuteCommandInDir(Commandline, Directory: string; + out Output: string; PrependPath: string; Verbose: boolean): integer; +var + OldPath: string; + PE:TProcessEx; + s:string; + + function GetFirstWord:string; + var + i:integer; + LastQuote:char=#0; + InQuote:boolean; + const + QUOTES = ['"','''']; + begin + Commandline:=trim(Commandline); + i:=1; + InQuote:=false; + while (i<=length(Commandline)) and (InQuote or (Commandline[i]>' ')) do + begin + // Check first and last quote: + if Commandline[i] in QUOTES then + if InQuote then + begin + if Commandline[i]=LastQuote then + begin + InQuote:=false; + delete(Commandline,i,1); + i:=i-1; + end; + end + else + begin + InQuote:=True; + LastQuote:=Commandline[i]; + delete(Commandline,i,1); + i:=i-1; + end; + i:=i+1; + end; + // Copy found word and remove it from remaining command line + result:=trim(copy(Commandline,1,i)); + delete(Commandline,1,i); + end; + +begin + PE:=TProcessEx.Create(nil); + try + if Directory<>'' then + PE.CurrentDirectory:=Directory; + + // Prepend specified PrependPath if needed: + if PrependPath<>'' then + begin + OldPath:=PE.Environment.GetVar(PATHVARNAME); + if OldPath<>'' then + PE.Environment.SetVar(PATHVARNAME, PrependPath+PathSeparator+OldPath) + else + PE.Environment.SetVar(PATHVARNAME, PrependPath); + end; + PE.Executable:=GetFirstWord; + s:=GetFirstWord; + while s<>'' do + begin + if s<>'emptystring' + then PE.Parameters.Add(s) + else PE.Parameters.Add('""'); + s:=GetFirstWord; + end; + PE.ShowWindow := swoHIDE; + if Verbose then + PE.OnOutput:=@DumpConsole; + {$IFDEF DEBUGCONSOLE} + writeln('ExecuteCommandInDir: executable '+PE.Executable); + writeln('ExecuteCommandInDir: params '+PE.Parameters.Text); + {$ENDIF DEBUGCONSOLE} + PE.Execute; + + Output:=PE.OutputString; + Result:=PE.ExitStatus; + {$IFDEF DEBUGCONSOLE} + writeln('ExecuteCommandInDir: exit status: '+IntToStr(Result)); + {$ENDIF DEBUGCONSOLE} + finally + PE.Free; + end; +end; + +function ExecutePlainCommand(Commandline: string; out Output: string; Verbose: boolean): integer; +var + PE:TProcessEx; + s:string; +begin + PE:=TProcessEx.Create(nil); + try + PE.CommandLine:=Commandline; + PE.ShowWindow := swoHIDE; + if Verbose then + PE.OnOutput:=@DumpConsole; + {$IFDEF DEBUGCONSOLE} + writeln('ExecuteCommandInDir: executable '+PE.Executable); + writeln('ExecuteCommandInDir: params '+PE.Parameters.Text); + {$ENDIF DEBUGCONSOLE} + PE.Execute; + Output:=PE.OutputString; + Result:=PE.ExitStatus; + {$IFDEF DEBUGCONSOLE} + writeln('ExecuteCommandInDir: exit status: '+IntToStr(Result)); + {$ENDIF DEBUGCONSOLE} + finally + PE.Free; + end; +end; + +end. +