1- namespace ManuHub . Ytdlp . NET . Core ;
1+ using System . Text ;
2+
3+ namespace ManuHub . Ytdlp . NET . Core ;
24
35public sealed class DownloadRunner
46{
@@ -7,7 +9,8 @@ public sealed class DownloadRunner
79 private readonly ILogger _logger ;
810
911 public event EventHandler < string > ? OnProgress ;
10- public event EventHandler < string > ? OnErrorMessage ;
12+ public event EventHandler < string > ? OnOutput ;
13+ public event EventHandler < string > ? OnError ;
1114 public event EventHandler < CommandCompletedEventArgs > ? OnCommandCompleted ;
1215
1316 public DownloadRunner ( ProcessFactory factory , ProgressParser parser , ILogger logger )
@@ -33,71 +36,105 @@ void Complete(bool success, string message)
3336
3437 try
3538 {
36- var tcs = new TaskCompletionSource < bool > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
39+ if ( ! process . Start ( ) )
40+ throw new YtdlpException ( "Failed to start yt-dlp process." ) ;
3741
38- // ✅ Attach BEFORE Start (fix race condition )
39- process . Exited += ( _ , _ ) => tcs . TrySetResult ( true ) ;
42+ if ( tuneProcess )
43+ ProcessFactory . Tune ( process ) ;
4044
41- process . OutputDataReceived += ( s , e ) =>
45+ // ---------------------------
46+ // STDOUT reader
47+ // ---------------------------
48+ var stdoutTask = Task . Run ( async ( ) =>
4249 {
43- if ( e . Data == null ) return ;
50+ using var reader = new StreamReader (
51+ process . StandardOutput . BaseStream ,
52+ Encoding . UTF8 ,
53+ detectEncodingFromByteOrderMarks : false ,
54+ bufferSize : 8192 ,
55+ leaveOpen : true ) ;
4456
45- try
46- {
47- _progressParser . ParseProgress ( e . Data ) ;
48- OnProgress ? . Invoke ( this , e . Data ) ;
49- }
50- catch ( Exception ex )
57+ while ( ! ct . IsCancellationRequested )
5158 {
52- _logger . Log ( LogType . Error , $ "Parse error: { ex . Message } ") ;
59+ var readTask = reader . ReadLineAsync ( ) ;
60+
61+ var completedTask = await Task . WhenAny ( readTask , Task . Delay ( Timeout . Infinite , ct ) ) ;
62+
63+ if ( completedTask != readTask )
64+ break ;
65+
66+ var line = await readTask ;
67+ if ( line == null )
68+ break ;
69+
70+ try
71+ {
72+ _progressParser . ParseProgress ( line ) ;
73+ OnProgress ? . Invoke ( this , line ) ;
74+ OnOutput ? . Invoke ( this , line ) ;
75+ }
76+ catch ( Exception ex )
77+ {
78+ _logger . Log ( LogType . Error , $ "Parse error: { ex . Message } ") ;
79+ }
5380 }
54- } ;
81+ } , ct ) ;
5582
56- process . ErrorDataReceived += ( s , e ) =>
83+ // ---------------------------
84+ // STDERR reader
85+ // ---------------------------
86+ var stderrTask = Task . Run ( async ( ) =>
5787 {
58- if ( e . Data == null ) return ;
88+ using var reader = new StreamReader (
89+ process . StandardError . BaseStream ,
90+ Encoding . UTF8 ,
91+ detectEncodingFromByteOrderMarks : false ,
92+ bufferSize : 8192 ,
93+ leaveOpen : true ) ;
5994
60- OnErrorMessage ? . Invoke ( this , e . Data ) ;
61- _logger . Log ( LogType . Error , e . Data ) ;
62- } ;
95+ while ( ! ct . IsCancellationRequested )
96+ {
97+ var readTask = reader . ReadLineAsync ( ) ;
6398
64- if ( ! process . Start ( ) )
65- throw new YtdlpException ( "Failed to start yt-dlp process." ) ;
99+ var completedTask = await Task . WhenAny ( readTask , Task . Delay ( Timeout . Infinite , ct ) ) ;
66100
67- if ( tuneProcess )
68- ProcessFactory . Tune ( process ) ;
101+ if ( completedTask != readTask )
102+ break ;
103+
104+ var line = await readTask ;
105+ if ( line == null )
106+ break ;
69107
70- // ✅ Start reading AFTER handlers
71- process . BeginOutputReadLine ( ) ;
72- process . BeginErrorReadLine ( ) ;
108+ OnError ? . Invoke ( this , line ) ;
109+ _logger . Log ( LogType . Error , line ) ;
110+ }
111+ } , ct ) ;
73112
74- // 🔥 Cancellation
113+ // ---------------------------
114+ // Cancellation handling
115+ // ---------------------------
75116 using var registration = ct . Register ( ( ) =>
76117 {
77118 if ( ! process . HasExited )
78119 {
79- _logger . Log ( LogType . Info , "Cancellation requested → killing process tree" ) ;
120+ _logger . Log ( LogType . Info ,
121+ "Cancellation requested → killing process tree" ) ;
122+
80123 ProcessFactory . SafeKill ( process , _logger ) ;
81124 }
82125 } ) ;
83126
84- // Wait for exit OR cancellation
85- await Task . WhenAny ( tcs . Task , Task . Delay ( Timeout . Infinite , ct ) ) ;
127+ // ---------------------------
128+ // Wait for ALL
129+ // ---------------------------
130+ await Task . WhenAll ( stdoutTask , stderrTask , process . WaitForExitAsync ( ct ) ) ;
86131
87- // Ensure process is dead
132+ // ---------------------------
133+ // Final safety kill
134+ // ---------------------------
88135 if ( ! process . HasExited )
89136 ProcessFactory . SafeKill ( process ) ;
90137
91- try
92- {
93- await process . WaitForExitAsync ( ct ) ;
94- }
95- catch ( OperationCanceledException )
96- {
97- if ( ! process . HasExited )
98- ProcessFactory . SafeKill ( process ) ;
99- }
100-
101138 var success = process . ExitCode == 0 && ! ct . IsCancellationRequested ;
102139
103140 var message = success
@@ -117,9 +154,116 @@ void Complete(bool success, string message)
117154 {
118155 var msg = $ "Error executing yt-dlp: { ex . Message } ";
119156 _logger . Log ( LogType . Error , msg ) ;
120- OnErrorMessage ? . Invoke ( this , msg ) ;
157+ OnError ? . Invoke ( this , msg ) ;
121158
122159 throw new YtdlpException ( msg , ex ) ;
123160 }
124161 }
162+
163+ // Working code - old method
164+ //public async Task RunAsync(string arguments, CancellationToken ct, bool tuneProcess = true)
165+ //{
166+ // using var process = _factory.Create(arguments);
167+
168+ // int completed = 0;
169+
170+ // void Complete(bool success, string message)
171+ // {
172+ // if (Interlocked.Exchange(ref completed, 1) == 0)
173+ // {
174+ // OnCommandCompleted?.Invoke(this, new CommandCompletedEventArgs(success, message));
175+ // }
176+ // }
177+
178+ // try
179+ // {
180+ // var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
181+
182+ // // ✅ Attach BEFORE Start (fix race condition)
183+ // process.Exited += (_, _) => tcs.TrySetResult(true);
184+
185+ // process.OutputDataReceived += (s, e) =>
186+ // {
187+ // if (e.Data == null) return;
188+
189+ // try
190+ // {
191+ // _progressParser.ParseProgress(e.Data);
192+ // OnProgress?.Invoke(this, e.Data);
193+ // }
194+ // catch (Exception ex)
195+ // {
196+ // _logger.Log(LogType.Error, $"Parse error: {ex.Message}");
197+ // }
198+ // };
199+
200+ // process.ErrorDataReceived += (s, e) =>
201+ // {
202+ // if (e.Data == null) return;
203+
204+ // OnErrorMessage?.Invoke(this, e.Data);
205+ // _logger.Log(LogType.Error, e.Data);
206+ // };
207+
208+ // if (!process.Start())
209+ // throw new YtdlpException("Failed to start yt-dlp process.");
210+
211+ // if (tuneProcess)
212+ // ProcessFactory.Tune(process);
213+
214+ // // ✅ Start reading AFTER handlers
215+ // process.BeginOutputReadLine();
216+ // process.BeginErrorReadLine();
217+
218+ // // 🔥 Cancellation
219+ // using var registration = ct.Register(() =>
220+ // {
221+ // if (!process.HasExited)
222+ // {
223+ // _logger.Log(LogType.Info, "Cancellation requested → killing process tree");
224+ // ProcessFactory.SafeKill(process, _logger);
225+ // }
226+ // });
227+
228+ // // Wait for exit OR cancellation
229+ // await Task.WhenAny(tcs.Task, Task.Delay(Timeout.Infinite, ct));
230+
231+ // // Ensure process is dead
232+ // if (!process.HasExited)
233+ // ProcessFactory.SafeKill(process);
234+
235+ // try
236+ // {
237+ // await process.WaitForExitAsync(ct);
238+ // }
239+ // catch (OperationCanceledException)
240+ // {
241+ // if (!process.HasExited)
242+ // ProcessFactory.SafeKill(process);
243+ // }
244+
245+ // var success = process.ExitCode == 0 && !ct.IsCancellationRequested;
246+
247+ // var message = success
248+ // ? "Completed successfully"
249+ // : ct.IsCancellationRequested
250+ // ? "Cancelled by user"
251+ // : $"Failed with exit code {process.ExitCode}";
252+
253+ // Complete(success, message);
254+ // }
255+ // catch (OperationCanceledException)
256+ // {
257+ // Complete(false, "Cancelled by user");
258+ // throw;
259+ // }
260+ // catch (Exception ex)
261+ // {
262+ // var msg = $"Error executing yt-dlp: {ex.Message}";
263+ // _logger.Log(LogType.Error, msg);
264+ // OnErrorMessage?.Invoke(this, msg);
265+
266+ // throw new YtdlpException(msg, ex);
267+ // }
268+ //}
125269}
0 commit comments