1
+ using System ;
2
+ using System . IO ;
3
+ using System . Net . Http ;
4
+ using System . Threading ;
5
+ using System . Threading . Tasks ;
6
+
7
+ namespace GDPIControl . Extensions
8
+ {
9
+ internal static class IOExtensions
10
+ {
11
+ public static Task CopyToAsync ( this Stream source , Stream destination , int bufferSize ) => CopyToAsync ( source , destination , bufferSize , default , default ) ;
12
+
13
+ public static Task CopyToAsync ( this Stream source , Stream destination , int bufferSize , IProgress < long > progress ) => CopyToAsync ( source , destination , bufferSize , progress , default ) ;
14
+
15
+ /// <summary>
16
+ /// https://stackoverflow.com/a/46497896
17
+ /// </summary>
18
+ public static async Task CopyToAsync ( this Stream source , Stream destination , int bufferSize , IProgress < long > progress , CancellationToken cancellationToken )
19
+ {
20
+ if ( source is null ) { throw new ArgumentNullException ( nameof ( source ) ) ; }
21
+ if ( ! source . CanRead ) { throw new ArgumentException ( "Has to be readable" , nameof ( source ) ) ; }
22
+ if ( destination is null ) { throw new ArgumentNullException ( nameof ( destination ) ) ; }
23
+ if ( ! destination . CanWrite ) { throw new ArgumentException ( "Has to be writable" , nameof ( destination ) ) ; }
24
+ if ( bufferSize < 0 ) { throw new ArgumentOutOfRangeException ( nameof ( bufferSize ) ) ; }
25
+
26
+ var buffer = new byte [ bufferSize ] ;
27
+ long totalBytesRead = 0 ;
28
+ int bytesRead ;
29
+ while ( ( bytesRead = await source . ReadAsync ( buffer , 0 , buffer . Length , cancellationToken ) . ConfigureAwait ( false ) ) != 0 )
30
+ {
31
+ await destination . WriteAsync ( buffer , 0 , bytesRead , cancellationToken ) . ConfigureAwait ( false ) ;
32
+ totalBytesRead += bytesRead ;
33
+ progress ? . Report ( totalBytesRead ) ;
34
+ }
35
+ }
36
+
37
+ /// <summary>
38
+ /// Асинхронно скачать файл
39
+ /// </summary>
40
+ public static Task DownloadAsync ( this HttpClient client , string requestUri , Stream destination ) => DownloadAsync ( client , requestUri , destination , default ( CancellationToken ) ) ;
41
+
42
+ /// <summary>
43
+ /// Асинхронно скачать файл
44
+ /// </summary>
45
+ public static async Task DownloadAsync ( this HttpClient client , string requestUri , Stream destination , CancellationToken cancellationToken )
46
+ {
47
+ // Считать только заголовок
48
+ using var response = await client . GetAsync ( requestUri , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) ;
49
+ response . EnsureSuccessStatusCode ( ) ;
50
+ // А содержимое записать прямо в файл
51
+ using var download = await response . Content . ReadAsStreamAsync ( ) ;
52
+ await download . CopyToAsync ( destination ) ;
53
+ }
54
+
55
+ /// <summary>
56
+ /// Асинхронно скачать файл с отчётом о прогрессе
57
+ /// </summary>
58
+ public static Task DownloadAsync ( this HttpClient client , string requestUri , Stream destination , IProgress < float > progress ) => DownloadAsync ( client , requestUri , destination , progress , default ) ;
59
+
60
+ /// <summary>
61
+ /// Асинхронно скачать файл с отчётом о прогрессе
62
+ /// </summary>
63
+ public static async Task DownloadAsync ( this HttpClient client , string requestUri , Stream destination , IProgress < float > progress , CancellationToken cancellationToken )
64
+ {
65
+ // Get the http headers first to examine the content length
66
+ using var response = await client . GetAsync ( requestUri , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) ;
67
+ response . EnsureSuccessStatusCode ( ) ;
68
+ var contentLength = response . Content . Headers . ContentLength ;
69
+ using var download = await response . Content . ReadAsStreamAsync ( ) ;
70
+ // Ignore progress reporting when no progress reporter was
71
+ // passed or when the content length is unknown
72
+ if ( ! contentLength . HasValue )
73
+ {
74
+ await download . CopyToAsync ( destination ) ;
75
+ progress . Report ( 1 ) ;
76
+ return ;
77
+ }
78
+
79
+ // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
80
+ var relativeProgress = new Progress < long > ( totalBytes => progress . Report ( ( float ) totalBytes / contentLength . Value ) ) ;
81
+ // Use extension method to report progress while downloading
82
+ await download . CopyToAsync ( destination , 81920 , relativeProgress , cancellationToken ) ;
83
+ progress . Report ( 1 ) ;
84
+ }
85
+ }
86
+ }
0 commit comments