1- using System ;
2- using System . Text . Json ;
3- using System . Text . Json . Serialization ;
4- using ByteSizeLib ;
5- using Humanizer ;
61using NetPace . Core ;
2+ using NetPace . Console . ConsoleWriters ;
73using Spectre . Console . Extensions ;
84
95namespace NetPace . Console . Commands ;
@@ -12,6 +8,14 @@ public sealed class SpeedTestCommand(IAnsiConsole console, ISpeedTestService spe
128{
139 protected override async Task < int > ExecuteAsync ( CommandContext context , SpeedTestCommandSettings settings , CancellationToken cancellationToken )
1410 {
11+ IConsoleWriter writer = settings switch
12+ {
13+ { CSV : true } => new CSVConsoleWriter ( ) ,
14+ { Json : true } or { JsonPretty : true } => new JsonConsoleWriter ( ) ,
15+ { Verbosity : Verbosity . Minimal } => new MinimalConsoleWriter ( ) ,
16+ _ => new DefaultConsoleWriter ( )
17+ } ;
18+
1519 if ( settings . Loop )
1620 {
1721 // Run continuously.
@@ -21,7 +25,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, SpeedTes
2125 try
2226 {
2327 // Run the speed test.
24- await internalExecuteAsync ( includeCSVHeader : firstLoop , settings , cancellationToken ) ;
28+ await writer . PerformSpeedTestAsync ( initialSpeedTest : firstLoop , console , clock , speedTestClient , settings , cancellationToken ) ;
2529 }
2630 catch ( TaskCanceledException )
2731 {
@@ -58,7 +62,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, SpeedTes
5862 try
5963 {
6064 // Run the speed test.
61- await internalExecuteAsync ( includeCSVHeader : ( i == 0 ) , settings , cancellationToken ) ;
65+ await writer . PerformSpeedTestAsync ( initialSpeedTest : ( i == 0 ) , console , clock , speedTestClient , settings , cancellationToken ) ;
6266 }
6367 catch ( TaskCanceledException )
6468 {
@@ -91,7 +95,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, SpeedTes
9195 try
9296 {
9397 // Run the speed test.
94- await internalExecuteAsync ( includeCSVHeader : true , settings , cancellationToken ) ;
98+ await writer . PerformSpeedTestAsync ( initialSpeedTest : true , console , clock , speedTestClient , settings , cancellationToken ) ;
9599 }
96100 catch ( TaskCanceledException )
97101 {
@@ -106,234 +110,4 @@ protected override async Task<int> ExecuteAsync(CommandContext context, SpeedTes
106110
107111 return 0 ;
108112 }
109-
110- private async Task internalExecuteAsync ( bool includeCSVHeader , SpeedTestCommandSettings settings , CancellationToken cancellationToken )
111- {
112- ServerLatencyResult fastest ;
113-
114- if ( string . IsNullOrEmpty ( settings . ServerUrl ) )
115- {
116- // Get the fastest speed test server.
117- var servers = await speedTestClient . GetServersAsync ( cancellationToken ) ;
118- fastest = await speedTestClient . GetFastestServerByLatencyAsync ( servers , cancellationToken ) ;
119- }
120- else
121- {
122- // User specified speed test server.
123- var server = new Core . Clients . Ookla . Server ( ) { Sponsor = "(Unknown)" , Url = settings . ServerUrl } ;
124- fastest = await speedTestClient . GetServerLatencyAsync ( server , cancellationToken ) ;
125- }
126-
127-
128- if ( ! settings . CSV && ! settings . Json && ! settings . JsonPretty && ( ( settings . Verbosity & ( Verbosity . Normal | Verbosity . Debug ) ) != 0 ) )
129- {
130- console . WriteLine ( "" ) ;
131- console . WriteLine ( $ "{ fastest . Server . Sponsor } ", new Style ( foreground : Color . Yellow , decoration : Decoration . Bold ) ) ;
132- console . WriteLine ( $ "{ fastest . Server . Url } ") ;
133-
134- if ( ! console . Profile . Capabilities . Interactive )
135- {
136- // Add an extra line given the live widget will not appear.
137- console . WriteLine ( "" ) ;
138- }
139- }
140-
141-
142- // Perform speed test
143- var ( downloadResult , uploadResult ) = await PerformSpeedTestAsync ( fastest . Server , settings , cancellationToken ) ;
144-
145-
146- // CSV output overrides the display options below
147- if ( settings . CSV )
148- {
149- // Always including the timestamp in the CSV output seems reasonable
150- settings . IncludeTimestamp = true ;
151-
152- if ( settings . CSVHeaderUnits )
153- {
154- var downloadFormattedParts = downloadResult . GetSpeedStringParts ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) ;
155- var uploadFormattedParts = uploadResult . GetSpeedStringParts ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) ;
156-
157- // Header row.
158- if ( includeCSVHeader )
159- {
160- console . WriteLine ( string . Join ( settings . CSVDelimiter , new [ ]
161- {
162- settings . IncludeTimestamp ? "Timestamp" : null ,
163- "Latency (ms)" ,
164- ! settings . NoDownload ? $ "Download ({ downloadFormattedParts . unit } )" : null ,
165- ! settings . NoUpload ? $ "Upload ({ uploadFormattedParts . unit } )" : null
166- } . Where ( s => ! string . IsNullOrEmpty ( s ) ) ) ) ;
167- }
168-
169- // Data row.
170- console . WriteLine ( string . Join ( settings . CSVDelimiter , new [ ]
171- {
172- settings . IncludeTimestamp ? clock . Now . ToString ( settings . DateTimeFormat ) : null ,
173- $ "{ fastest . Latency } ",
174- ! settings . NoDownload ? downloadFormattedParts . speed : null ,
175- ! settings . NoUpload ? uploadFormattedParts . speed : null
176- } . Where ( s => ! string . IsNullOrEmpty ( s ) ) ) ) ;
177- }
178- else
179- {
180- // Header row.
181- if ( includeCSVHeader )
182- {
183- console . WriteLine ( string . Join ( settings . CSVDelimiter , new [ ]
184- {
185- settings . IncludeTimestamp ? "Timestamp" : null ,
186- "Latency" ,
187- ! settings . NoDownload ? "Download" : null ,
188- ! settings . NoUpload ? "Upload" : null
189- } . Where ( s => ! string . IsNullOrEmpty ( s ) ) ) ) ;
190- }
191-
192- // Data row.
193- console . WriteLine ( string . Join ( settings . CSVDelimiter , new [ ]
194- {
195- settings . IncludeTimestamp ? clock . Now . ToString ( settings . DateTimeFormat ) : null ,
196- $ "{ fastest . Latency } ms",
197- ! settings . NoDownload ? downloadResult . GetSpeedString ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) : null ,
198- ! settings . NoUpload ? uploadResult . GetSpeedString ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) : null
199- } . Where ( s => ! string . IsNullOrEmpty ( s ) ) ) ) ;
200- }
201- }
202- // Json output overrides the display options below
203- else if ( settings . Json || settings . JsonPretty )
204- {
205- var downloadFormatted = ! settings . NoDownload ? downloadResult . GetSpeedString ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) : null ;
206- var uploadFormatted = ! settings . NoUpload ? uploadResult . GetSpeedString ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) : null ;
207-
208- var jsonResult = new JsonResult
209- {
210- ServerLocation = fastest . Server . Location ,
211- ServerSponsor = fastest . Server . Sponsor ,
212- ServerUrl = fastest . Server . Url ,
213- Timestamp = clock . Now . ToString ( settings . DateTimeFormat ) ,
214- Latency = $ "{ fastest . Latency } ms",
215- DownloadSpeed = downloadFormatted ! ,
216- UploadSpeed = uploadFormatted !
217- } ;
218-
219- var options = new JsonSerializerOptions { WriteIndented = settings . JsonPretty , DefaultIgnoreCondition = JsonIgnoreCondition . WhenWritingNull } ;
220- string jsonString = JsonSerializer . Serialize ( jsonResult , options ) ;
221-
222- console . WriteLine ( jsonString ) ;
223- }
224- else
225- {
226- if ( ( settings . Verbosity & Verbosity . Debug ) != 0 )
227- {
228- // Display detailed diagnostics
229- ByteSize size ; TimeSpan elapsed ;
230-
231- if ( ! settings . NoDownload )
232- {
233- size = ByteSize . FromBytes ( downloadResult . BytesProcessed ) ;
234- elapsed = TimeSpan . FromMilliseconds ( downloadResult . ElapsedMilliseconds ) ;
235- console . WriteLine ( $ "{ size } downloaded in { elapsed . Humanize ( ) } ") ;
236- }
237- if ( ! settings . NoUpload )
238- {
239- size = ByteSize . FromBytes ( uploadResult . BytesProcessed ) ;
240- elapsed = TimeSpan . FromMilliseconds ( uploadResult . ElapsedMilliseconds ) ;
241- console . WriteLine ( $ "{ size } uploaded in { elapsed . Humanize ( ) } ") ;
242- }
243-
244- if ( ! ( settings . NoDownload && settings . NoUpload ) )
245- {
246- console . WriteLine ( "" ) ;
247- }
248- }
249-
250- if ( ( settings . NoDownload && settings . NoUpload ) && ( ( settings . Verbosity & ( Verbosity . Normal | Verbosity . Debug ) ) != 0 ) &&
251- console . Profile . Capabilities . Interactive )
252- {
253- // Latency only test: Add an extra blank line for formatting.
254- console . WriteLine ( "" ) ;
255- }
256-
257-
258- // Display speed test result
259- console . WriteLine ( string . Join ( ", " , new [ ]
260- {
261- settings . IncludeTimestamp ? clock . Now . ToString ( settings . DateTimeFormat ) : null ,
262- $ "Latency: { fastest . Latency } ms",
263- ! settings . NoDownload ? $ "Download: { downloadResult . GetSpeedString ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) } " : null ,
264- ! settings . NoUpload ? $ "Upload: { uploadResult . GetSpeedString ( settings . SpeedUnit , settings . SpeedUnitSystem , settings . SpeedScale ) } " : null
265- } . Where ( s => ! string . IsNullOrEmpty ( s ) ) ) ) ;
266-
267-
268- if ( ( settings . Verbosity & ( Verbosity . Normal | Verbosity . Debug ) ) != 0 )
269- {
270- console . WriteLine ( "\n Try 'NetPace --help' for more information." ) ;
271- }
272- }
273- }
274-
275- private async Task < ( SpeedTestResult downloadResult , SpeedTestResult uploadResult ) > PerformSpeedTestAsync ( IServer server , SpeedTestCommandSettings settings , CancellationToken cancellationToken )
276- {
277- var downloadResult = new SpeedTestResult ( ) ;
278- var uploadResult = new SpeedTestResult ( ) ;
279-
280-
281- if ( settings . NoDownload && settings . NoUpload )
282- {
283- // Latency only test - so just return
284- return ( downloadResult , uploadResult ) ;
285- }
286-
287-
288- if ( settings . CSV || settings . Json || settings . JsonPretty || ( ( settings . Verbosity & Verbosity . Minimal ) != 0 ) )
289- {
290- // No progress is reported
291- if ( ! settings . NoDownload ) downloadResult = await speedTestClient . GetDownloadSpeedAsync ( server , settings . DownloadSizeMb , cancellationToken ) ;
292- if ( ! settings . NoUpload ) uploadResult = await speedTestClient . GetUploadSpeedAsync ( server , settings . UploadSizeMb , cancellationToken ) ;
293- }
294- else
295- {
296- // Graphical progress bar
297- await console . Progress ( )
298- . AutoClear ( false )
299- . Columns (
300- [
301- new TaskDescriptionColumn ( ) ,
302- new ProgressBarColumn ( ) ,
303- new PercentageColumn ( ) ,
304- ] )
305- . StartAsync ( async progress =>
306- {
307- ProgressTask ? downloadProgress = null ; ProgressTask ? uploadProgress = null ;
308-
309- // Create the progress bars
310- if ( ! settings . NoDownload )
311- {
312- downloadProgress = progress . AddTask ( "Downloading" , autoStart : true , maxValue : 100 ) ;
313- }
314- if ( ! settings . NoUpload )
315- {
316- uploadProgress = progress . AddTask ( "Uploading" , autoStart : true , maxValue : 100 ) ;
317- }
318-
319- // Perform the speed tests and show progress
320- if ( ! settings . NoDownload )
321- {
322- downloadResult = await speedTestClient . GetDownloadSpeedAsync ( server , settings . DownloadSizeMb , ( SpeedTestProgress progress ) =>
323- {
324- downloadProgress ! . Value = progress . PercentageComplete ;
325- } , cancellationToken ) ;
326- }
327- if ( ! settings . NoUpload )
328- {
329- uploadResult = await speedTestClient . GetUploadSpeedAsync ( server , settings . UploadSizeMb , ( SpeedTestProgress progress ) =>
330- {
331- uploadProgress ! . Value = progress . PercentageComplete ;
332- } , cancellationToken ) ;
333- }
334- } ) ;
335- }
336-
337- return ( downloadResult , uploadResult ) ;
338- }
339113}
0 commit comments