@@ -18,17 +18,8 @@ namespace YoutubeDl.Wpf.Models;
1818public class BackendInstance : ReactiveObject , IEnableLogger
1919{
2020 private readonly ObservableSettings _settings ;
21- private readonly Process _dlProcess ;
2221 private readonly BackendService _backendService ;
23- private readonly string [ ] outputSeparators =
24- {
25- "[download]" ,
26- "of" ,
27- "at" ,
28- "ETA" ,
29- "in" ,
30- " " ,
31- } ;
22+ private readonly Process _process ;
3223
3324 public List < string > GeneratedDownloadArguments { get ; } = new ( ) ;
3425
@@ -55,27 +46,27 @@ public BackendInstance(ObservableSettings settings, BackendService backendServic
5546 _settings = settings ;
5647 _backendService = backendService ;
5748
58- _dlProcess = new ( ) ;
59- _dlProcess . StartInfo . CreateNoWindow = true ;
60- _dlProcess . StartInfo . UseShellExecute = false ;
61- _dlProcess . StartInfo . RedirectStandardError = true ;
62- _dlProcess . StartInfo . RedirectStandardOutput = true ;
63- _dlProcess . StartInfo . StandardErrorEncoding = Encoding . UTF8 ;
64- _dlProcess . StartInfo . StandardOutputEncoding = Encoding . UTF8 ;
65- _dlProcess . EnableRaisingEvents = true ;
49+ _process = new ( ) ;
50+ _process . StartInfo . CreateNoWindow = true ;
51+ _process . StartInfo . UseShellExecute = false ;
52+ _process . StartInfo . RedirectStandardError = true ;
53+ _process . StartInfo . RedirectStandardOutput = true ;
54+ _process . StartInfo . StandardErrorEncoding = Encoding . UTF8 ;
55+ _process . StartInfo . StandardOutputEncoding = Encoding . UTF8 ;
56+ _process . EnableRaisingEvents = true ;
6657 }
6758
68- private async Task RunDlAsync ( CancellationToken cancellationToken = default )
59+ private async Task RunAsync ( CancellationToken cancellationToken = default )
6960 {
70- if ( ! _dlProcess . Start ( ) )
61+ if ( ! _process . Start ( ) )
7162 throw new InvalidOperationException ( "Method called when the backend process is running." ) ;
7263
7364 SetStatusRunning ( ) ;
7465
7566 await Task . WhenAll (
76- ReadAndParseAsync ( _dlProcess . StandardError , cancellationToken ) ,
77- ReadAndParseAsync ( _dlProcess . StandardOutput , cancellationToken ) ,
78- _dlProcess . WaitForExitAsync ( cancellationToken ) ) ;
67+ ReadAndParseLinesAsync ( _process . StandardError , cancellationToken ) ,
68+ ReadAndParseLinesAsync ( _process . StandardOutput , cancellationToken ) ,
69+ _process . WaitForExitAsync ( cancellationToken ) ) ;
7970
8071 SetStatusStopped ( ) ;
8172 }
@@ -95,7 +86,7 @@ private void SetStatusStopped()
9586 _backendService . UpdateProgress ( ) ;
9687 }
9788
98- private async Task ReadAndParseAsync ( StreamReader reader , CancellationToken cancellationToken = default )
89+ private async Task ReadAndParseLinesAsync ( StreamReader reader , CancellationToken cancellationToken = default )
9990 {
10091 while ( true )
10192 {
@@ -104,34 +95,61 @@ private async Task ReadAndParseAsync(StreamReader reader, CancellationToken canc
10495 return ;
10596
10697 this . Log ( ) . Info ( line ) ;
107- ParseDlOutput ( line ) ;
98+ ParseLine ( line ) ;
10899 }
109100 }
110101
111- private void ParseDlOutput ( string output )
102+ private void ParseLine ( ReadOnlySpan < char > line )
112103 {
113- var parsedStringArray = output . Split ( outputSeparators , StringSplitOptions . RemoveEmptyEntries ) ;
114- if ( parsedStringArray . Length >= 2 ) // valid [download] line
104+ // Example lines:
105+ // [download] 0.0% of 36.35MiB at 20.40KiB/s ETA 30:24
106+ // [download] 65.1% of 36.35MiB at 2.81MiB/s ETA 00:04
107+ // [download] 100% of 36.35MiB in 00:10
108+
109+ // Check and strip the download prefix.
110+ const string downloadPrefix = "[download] " ;
111+ if ( ! line . StartsWith ( downloadPrefix , StringComparison . Ordinal ) )
112+ return ;
113+ line = line [ downloadPrefix . Length ..] ;
114+
115+ // Parse and strip the percentage.
116+ const string percentageSuffix = "% of " ;
117+ var percentageEnd = line . IndexOf ( percentageSuffix , StringComparison . Ordinal ) ;
118+ if ( percentageEnd == - 1 || ! double . TryParse ( line [ ..percentageEnd ] , NumberStyles . AllowLeadingWhite | NumberStyles . AllowDecimalPoint , CultureInfo . InvariantCulture , out var percentage ) )
119+ return ;
120+ DownloadProgressPercentage = percentage / 100 ;
121+ StatusIndeterminate = false ;
122+ _backendService . UpdateProgress ( ) ;
123+ line = line [ ( percentageEnd + percentageSuffix . Length ) ..] ;
124+
125+ // Case 0: Download in progress
126+ const string speedPrefix = " at " ;
127+ var sizeEnd = line . IndexOf ( speedPrefix , StringComparison . Ordinal ) ;
128+ if ( sizeEnd != - 1 )
115129 {
116- ReadOnlySpan < char > percentageString = parsedStringArray [ 0 ] ;
117- if ( percentageString . Length >= 2 && percentageString . EndsWith ( "%" ) ) // actual percentage
118- {
119- if ( double . TryParse ( percentageString [ ..^ 1 ] , NumberStyles . AllowDecimalPoint , CultureInfo . InvariantCulture , out var percentageNumber ) )
120- {
121- DownloadProgressPercentage = percentageNumber / 100 ;
122- StatusIndeterminate = false ;
123- _backendService . UpdateProgress ( ) ;
124- }
125- }
130+ // Extract and strip file size.
131+ FileSizeString = line [ ..sizeEnd ] . ToString ( ) ;
132+ line = line [ ( sizeEnd + speedPrefix . Length ) ..] ;
133+
134+ // Extract and strip speed.
135+ const string etaPrefix = " ETA " ;
136+ var speedEnd = line . IndexOf ( etaPrefix , StringComparison . Ordinal ) ;
137+ if ( speedEnd == - 1 )
138+ return ;
139+ DownloadSpeedString = line [ ..speedEnd ] . TrimStart ( ) . ToString ( ) ;
140+ line = line [ ( speedEnd + etaPrefix . Length ) ..] ;
126141
127- // save other info
128- FileSizeString = parsedStringArray [ 1 ] ;
142+ // Extract ETA string.
143+ DownloadETAString = line . ToString ( ) ;
144+ return ;
145+ }
129146
130- if ( parsedStringArray . Length == 4 )
131- {
132- DownloadSpeedString = parsedStringArray [ 2 ] ;
133- DownloadETAString = parsedStringArray [ 3 ] ;
134- }
147+ // Case 1: Download finished
148+ sizeEnd = line . IndexOf ( " in " , StringComparison . Ordinal ) ;
149+ if ( sizeEnd != - 1 )
150+ {
151+ // Extract file size.
152+ FileSizeString = line [ ..sizeEnd ] . ToString ( ) ;
135153 }
136154 }
137155
@@ -248,16 +266,16 @@ public void GenerateDownloadArguments()
248266
249267 public async Task StartDownloadAsync ( string link , CancellationToken cancellationToken = default )
250268 {
251- _dlProcess . StartInfo . FileName = _settings . BackendPath ;
252- _dlProcess . StartInfo . ArgumentList . Clear ( ) ;
253- _dlProcess . StartInfo . ArgumentList . AddRange ( _settings . BackendGlobalArguments . Select ( x => x . Argument ) ) ;
254- _dlProcess . StartInfo . ArgumentList . AddRange ( GeneratedDownloadArguments ) ;
255- _dlProcess . StartInfo . ArgumentList . AddRange ( _settings . BackendDownloadArguments . Select ( x => x . Argument ) ) ;
256- _dlProcess . StartInfo . ArgumentList . Add ( link ) ;
269+ _process . StartInfo . FileName = _settings . BackendPath ;
270+ _process . StartInfo . ArgumentList . Clear ( ) ;
271+ _process . StartInfo . ArgumentList . AddRange ( _settings . BackendGlobalArguments . Select ( x => x . Argument ) ) ;
272+ _process . StartInfo . ArgumentList . AddRange ( GeneratedDownloadArguments ) ;
273+ _process . StartInfo . ArgumentList . AddRange ( _settings . BackendDownloadArguments . Select ( x => x . Argument ) ) ;
274+ _process . StartInfo . ArgumentList . Add ( link ) ;
257275
258276 try
259277 {
260- await RunDlAsync ( cancellationToken ) ;
278+ await RunAsync ( cancellationToken ) ;
261279 }
262280 catch ( Exception ex )
263281 {
@@ -267,37 +285,60 @@ public async Task StartDownloadAsync(string link, CancellationToken cancellation
267285
268286 public async Task ListFormatsAsync ( string link , CancellationToken cancellationToken = default )
269287 {
270- _dlProcess . StartInfo . FileName = _settings . BackendPath ;
271- _dlProcess . StartInfo . ArgumentList . Clear ( ) ;
272- _dlProcess . StartInfo . ArgumentList . AddRange ( _settings . BackendGlobalArguments . Select ( x => x . Argument ) ) ;
288+ _process . StartInfo . FileName = _settings . BackendPath ;
289+ _process . StartInfo . ArgumentList . Clear ( ) ;
290+ _process . StartInfo . ArgumentList . AddRange ( _settings . BackendGlobalArguments . Select ( x => x . Argument ) ) ;
273291 if ( ! string . IsNullOrEmpty ( _settings . Proxy ) )
274292 {
275- _dlProcess . StartInfo . ArgumentList . Add ( "--proxy" ) ;
276- _dlProcess . StartInfo . ArgumentList . Add ( _settings . Proxy ) ;
293+ _process . StartInfo . ArgumentList . Add ( "--proxy" ) ;
294+ _process . StartInfo . ArgumentList . Add ( _settings . Proxy ) ;
277295 }
278- _dlProcess . StartInfo . ArgumentList . Add ( "-F" ) ;
279- _dlProcess . StartInfo . ArgumentList . Add ( link ) ;
296+ _process . StartInfo . ArgumentList . Add ( "-F" ) ;
297+ _process . StartInfo . ArgumentList . Add ( link ) ;
280298
281299 try
282300 {
283- await RunDlAsync ( cancellationToken ) ;
301+ await RunAsync ( cancellationToken ) ;
284302 }
285303 catch ( Exception ex )
286304 {
287305 this . Log ( ) . Error ( ex ) ;
288306 }
289307 }
290308
291- public async Task AbortDlAsync ( CancellationToken cancellationToken = default )
309+ public async Task UpdateAsync ( CancellationToken cancellationToken = default )
292310 {
293- if ( CtrlCHelper . AttachConsole ( ( uint ) _dlProcess . Id ) )
311+ _settings . BackendLastUpdateCheck = DateTimeOffset . Now ;
312+
313+ _process . StartInfo . FileName = _settings . BackendPath ;
314+ _process . StartInfo . ArgumentList . Clear ( ) ;
315+ if ( ! string . IsNullOrEmpty ( _settings . Proxy ) )
316+ {
317+ _process . StartInfo . ArgumentList . Add ( "--proxy" ) ;
318+ _process . StartInfo . ArgumentList . Add ( _settings . Proxy ) ;
319+ }
320+ _process . StartInfo . ArgumentList . Add ( "-U" ) ;
321+
322+ try
323+ {
324+ await RunAsync ( cancellationToken ) ;
325+ }
326+ catch ( Exception ex )
327+ {
328+ this . Log ( ) . Error ( ex ) ;
329+ }
330+ }
331+
332+ public async Task AbortAsync ( CancellationToken cancellationToken = default )
333+ {
334+ if ( CtrlCHelper . AttachConsole ( ( uint ) _process . Id ) )
294335 {
295336 CtrlCHelper . SetConsoleCtrlHandler ( null , true ) ;
296337 try
297338 {
298339 if ( CtrlCHelper . GenerateConsoleCtrlEvent ( CtrlCHelper . CTRL_C_EVENT , 0 ) )
299340 {
300- await _dlProcess . WaitForExitAsync ( cancellationToken ) ;
341+ await _process . WaitForExitAsync ( cancellationToken ) ;
301342 }
302343 }
303344 catch ( Exception ex )
@@ -312,27 +353,4 @@ public async Task AbortDlAsync(CancellationToken cancellationToken = default)
312353 }
313354 this . Log ( ) . Info ( "🛑 Aborted." ) ;
314355 }
315-
316- public async Task UpdateDlAsync ( CancellationToken cancellationToken = default )
317- {
318- _settings . BackendLastUpdateCheck = DateTimeOffset . Now ;
319-
320- _dlProcess . StartInfo . FileName = _settings . BackendPath ;
321- _dlProcess . StartInfo . ArgumentList . Clear ( ) ;
322- if ( ! string . IsNullOrEmpty ( _settings . Proxy ) )
323- {
324- _dlProcess . StartInfo . ArgumentList . Add ( "--proxy" ) ;
325- _dlProcess . StartInfo . ArgumentList . Add ( _settings . Proxy ) ;
326- }
327- _dlProcess . StartInfo . ArgumentList . Add ( "-U" ) ;
328-
329- try
330- {
331- await RunDlAsync ( cancellationToken ) ;
332- }
333- catch ( Exception ex )
334- {
335- this . Log ( ) . Error ( ex ) ;
336- }
337- }
338356}
0 commit comments