1+ using FlowSynx . PluginCore ;
2+ using FlowSynx . PluginCore . Extensions ;
3+ using FlowSynx . PluginCore . Helpers ;
4+ using FlowSynx . Plugins . ExternalProcess . Models ;
5+ using System . Diagnostics ;
6+ using System . Text ;
7+
8+ namespace FlowSynx . Plugins . ExternalProcess ;
9+
10+ public class ExternalProcessPlugin : IPlugin
11+ {
12+ private IPluginLogger ? _logger ;
13+ private bool _isInitialized ;
14+
15+ public PluginMetadata Metadata => new ( )
16+ {
17+ Id = Guid . Parse ( "df657e78-10ae-4e61-beda-2e4f9b1b6a7a" ) ,
18+ Name = "ExternalProcess" ,
19+ CompanyName = "FlowSynx" ,
20+ Description = Resources . PluginDescription ,
21+ Version = new Version ( 1 , 0 , 0 ) ,
22+ Category = PluginCategory . Execution ,
23+ Authors = new List < string > { "FlowSynx" } ,
24+ Copyright = "© FlowSynx. All rights reserved." ,
25+ Icon = "flowsynx.png" ,
26+ ReadMe = "README.md" ,
27+ RepositoryUrl = "https://github.com/flowsynx/plugin-external-process" ,
28+ ProjectUrl = "https://flowsynx.io" ,
29+ Tags = new List < string > ( ) { "flowsynx" , "execution" , "external-process" , "plugin" } ,
30+ MinimumFlowSynxVersion = new Version ( 1 , 1 , 1 ) ,
31+ } ;
32+
33+ public PluginSpecifications ? Specifications { get ; set ; }
34+
35+ public Type SpecificationsType => typeof ( ExternalProcessPluginSpecifications ) ;
36+
37+ public IReadOnlyCollection < string > SupportedOperations => new List < string > ( ) ;
38+
39+ public Task Initialize ( IPluginLogger logger )
40+ {
41+ if ( ReflectionHelper . IsCalledViaReflection ( ) )
42+ throw new InvalidOperationException ( Resources . ReflectionBasedAccessIsNotAllowed ) ;
43+
44+ ArgumentNullException . ThrowIfNull ( logger ) ;
45+ _logger = logger ;
46+ _isInitialized = true ;
47+ return Task . CompletedTask ;
48+ }
49+
50+ public async Task < object ? > ExecuteAsync ( PluginParameters parameters , CancellationToken cancellationToken )
51+ {
52+ cancellationToken . ThrowIfCancellationRequested ( ) ;
53+
54+ if ( ReflectionHelper . IsCalledViaReflection ( ) )
55+ throw new InvalidOperationException ( Resources . ReflectionBasedAccessIsNotAllowed ) ;
56+
57+ if ( ! _isInitialized )
58+ throw new InvalidOperationException ( $ "Plugin '{ Metadata . Name } ' v{ Metadata . Version } is not initialized.") ;
59+
60+ var inputParameter = parameters . ToObject < InputParameter > ( ) ;
61+ return await ExecuteProcessAsync ( inputParameter , cancellationToken ) ;
62+ }
63+
64+ #region private methods
65+ private async Task < PluginContext > ExecuteProcessAsync (
66+ InputParameter inputParameter ,
67+ CancellationToken cancellationToken )
68+ {
69+ if ( _logger == null )
70+ {
71+ throw new InvalidOperationException ( "Plugin is not initialized. Call Initialize() first." ) ;
72+ }
73+
74+ _logger . LogInfo ( $ "Starting process: { inputParameter . FileName } { inputParameter . Arguments } ") ;
75+
76+ var processStartInfo = new ProcessStartInfo
77+ {
78+ FileName = inputParameter . FileName ,
79+ Arguments = inputParameter . Arguments ,
80+ WorkingDirectory = string . IsNullOrWhiteSpace ( inputParameter . WorkingDirectory )
81+ ? Environment . CurrentDirectory
82+ : inputParameter . WorkingDirectory ,
83+ RedirectStandardOutput = true ,
84+ RedirectStandardError = true ,
85+ UseShellExecute = false ,
86+ CreateNoWindow = ! inputParameter . ShowWindow
87+ } ;
88+
89+ var outputBuilder = new StringBuilder ( ) ;
90+ var errorBuilder = new StringBuilder ( ) ;
91+
92+ using var process = new Process { StartInfo = processStartInfo , EnableRaisingEvents = true } ;
93+
94+ process . OutputDataReceived += ( _ , e ) =>
95+ {
96+ if ( e . Data != null )
97+ {
98+ outputBuilder . AppendLine ( e . Data ) ;
99+ _logger . LogDebug ( $ "STDOUT: { e . Data } ") ;
100+ }
101+ } ;
102+
103+ process . ErrorDataReceived += ( _ , e ) =>
104+ {
105+ if ( e . Data != null )
106+ {
107+ errorBuilder . AppendLine ( e . Data ) ;
108+ _logger . LogError ( $ "STDERR: { e . Data } ") ;
109+ }
110+ } ;
111+
112+ var startTime = DateTime . UtcNow ;
113+
114+ process . Start ( ) ;
115+ process . BeginOutputReadLine ( ) ;
116+ process . BeginErrorReadLine ( ) ;
117+
118+ await Task . Run ( ( ) =>
119+ {
120+ while ( ! process . HasExited )
121+ {
122+ cancellationToken . ThrowIfCancellationRequested ( ) ;
123+ Thread . Sleep ( 100 ) ;
124+ }
125+ } , cancellationToken ) ;
126+
127+ await process . WaitForExitAsync ( cancellationToken ) ;
128+ var endTime = DateTime . UtcNow ;
129+
130+ var stdout = outputBuilder . ToString ( ) ;
131+ var stderr = errorBuilder . ToString ( ) ;
132+ var combinedOutput = stdout + stderr ;
133+
134+ if ( process . ExitCode != 0 )
135+ {
136+ _logger . LogError ( $ "Process failed with exit code { process . ExitCode } ") ;
137+ if ( inputParameter . FailOnNonZeroExit )
138+ throw new Exception ( $ "Process failed with exit code { process . ExitCode } . Error: { stderr } ") ;
139+ else
140+ _logger . LogWarning ( "Continuing despite process failure because FailOnNonZeroExit is false." ) ;
141+ }
142+ else
143+ {
144+ _logger . LogInfo ( $ "Process completed successfully with exit code { process . ExitCode } ") ;
145+ }
146+
147+ var context = new PluginContext ( Guid . NewGuid ( ) . ToString ( ) , "Execution" )
148+ {
149+ Format = "Text" ,
150+ Content = combinedOutput ,
151+ StructuredData = null
152+ } ;
153+ context . Metadata [ "ExitCode" ] = process . ExitCode ;
154+ context . Metadata [ "StartTimeUtc" ] = startTime ;
155+ context . Metadata [ "EndTimeUtc" ] = endTime ;
156+ context . Metadata [ "Duration" ] = endTime - startTime ;
157+ context . Metadata [ "WasSuccessful" ] = process . ExitCode == 0 ;
158+ context . Metadata [ "StdErrPresent" ] = ! string . IsNullOrWhiteSpace ( stderr ) ;
159+
160+ return context ;
161+ }
162+ #endregion
163+ }
0 commit comments