1
1
using CommunityToolkit . Diagnostics ;
2
+ using Microsoft . Extensions . Logging ;
2
3
using SharpCompress . Archives ;
4
+ using System . Net . Http . Headers ;
3
5
4
6
namespace Common . Client . Tools ;
5
7
6
8
public sealed class ArchiveTools
7
9
{
8
10
private readonly HttpClient _httpClient ;
11
+ private readonly ILogger _logger ;
9
12
10
13
/// <summary>
11
14
/// Operation progress
12
15
/// </summary>
13
16
public readonly Progress < float > Progress = new ( ) ;
14
17
15
- public ArchiveTools ( HttpClient httpClient )
18
+ public ArchiveTools (
19
+ HttpClient httpClient ,
20
+ ILogger logger
21
+ )
16
22
{
17
23
_httpClient = httpClient ;
24
+ _logger = logger ;
18
25
}
19
26
20
27
/// <summary>
@@ -30,6 +37,8 @@ public async Task<bool> DownloadFileAsync(
30
37
CancellationToken cancellationToken
31
38
)
32
39
{
40
+ _logger . LogInformation ( $ "Starting file downloading: { url } ") ;
41
+
33
42
IProgress < float > progress = Progress ;
34
43
var tempFile = filePath + ".temp" ;
35
44
@@ -38,25 +47,25 @@ CancellationToken cancellationToken
38
47
File . Delete ( tempFile ) ;
39
48
}
40
49
41
- FileStream ? file = null ;
50
+ var response = await _httpClient . GetAsync ( url , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) . ConfigureAwait ( false ) ;
42
51
43
- try
52
+ if ( ! response . IsSuccessStatusCode )
44
53
{
45
- var response = await _httpClient . GetAsync ( url , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) . ConfigureAwait ( false ) ;
54
+ ThrowHelper . ThrowExternalException ( $ "Error while downloading { url } , error: { response . StatusCode } ") ;
55
+ }
46
56
47
- if ( ! response . IsSuccessStatusCode )
48
- {
49
- ThrowHelper . ThrowExternalException ( $ "Error while downloading { url } , error: { response . StatusCode } ") ;
50
- }
57
+ await using var source = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
58
+ var contentLength = response . Content . Headers . ContentLength ;
51
59
52
- await using var source = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
53
- var contentLength = response . Content . Headers . ContentLength ;
60
+ _logger . LogInformation ( $ "File length is { contentLength } ") ;
54
61
55
- file = new ( tempFile , FileMode . Create , FileAccess . Write , FileShare . None ) ;
62
+ FileStream fileStream = new ( tempFile , FileMode . Create , FileAccess . Write , FileShare . None ) ;
56
63
64
+ try
65
+ {
57
66
if ( ! contentLength . HasValue )
58
67
{
59
- await source . CopyToAsync ( file , cancellationToken ) . ConfigureAwait ( false ) ;
68
+ await source . CopyToAsync ( fileStream , cancellationToken ) . ConfigureAwait ( false ) ;
60
69
}
61
70
else
62
71
{
@@ -66,22 +75,21 @@ CancellationToken cancellationToken
66
75
67
76
while ( ( bytesRead = await source . ReadAsync ( buffer , cancellationToken ) . ConfigureAwait ( false ) ) != 0 )
68
77
{
69
- await file . WriteAsync ( buffer . AsMemory ( 0 , bytesRead ) , cancellationToken ) . ConfigureAwait ( false ) ;
78
+ await fileStream . WriteAsync ( buffer . AsMemory ( 0 , bytesRead ) , cancellationToken ) . ConfigureAwait ( false ) ;
70
79
totalBytesRead += bytesRead ;
71
80
progress . Report ( totalBytesRead / ( long ) contentLength * 100 ) ;
72
81
}
73
82
}
74
-
75
- await file . DisposeAsync ( ) . ConfigureAwait ( false ) ;
76
- File . Move ( tempFile , filePath , true ) ;
77
-
78
- return true ;
83
+ }
84
+ catch ( HttpIOException )
85
+ {
86
+ await ContinueDownload ( url , contentLength , fileStream ! , cancellationToken ) . ConfigureAwait ( false ) ;
79
87
}
80
88
catch ( OperationCanceledException )
81
89
{
82
- if ( file is not null )
90
+ if ( fileStream is not null )
83
91
{
84
- await file . DisposeAsync ( ) . ConfigureAwait ( false ) ;
92
+ await fileStream . DisposeAsync ( ) . ConfigureAwait ( false ) ;
85
93
}
86
94
87
95
if ( File . Exists ( tempFile ) )
@@ -91,6 +99,14 @@ CancellationToken cancellationToken
91
99
92
100
return false ;
93
101
}
102
+ finally
103
+ {
104
+ await fileStream . DisposeAsync ( ) . ConfigureAwait ( false ) ;
105
+ }
106
+
107
+ _logger . LogInformation ( "Downloading finished, renaming temp file" ) ;
108
+ File . Move ( tempFile , filePath , true ) ;
109
+ return true ;
94
110
}
95
111
96
112
/// <summary>
@@ -100,7 +116,8 @@ CancellationToken cancellationToken
100
116
/// <param name="unpackTo">Directory to unpack archive to</param>
101
117
public async Task UnpackArchiveAsync (
102
118
string pathToArchive ,
103
- string unpackTo )
119
+ string unpackTo
120
+ )
104
121
{
105
122
IProgress < float > progress = Progress ;
106
123
@@ -141,4 +158,48 @@ await Task.Run(() =>
141
158
}
142
159
} ) . ConfigureAwait ( false ) ;
143
160
}
161
+
162
+
163
+ /// <summary>
164
+ /// Continue download after network error
165
+ /// </summary>
166
+ /// <param name="url">Url to the file</param>
167
+ /// <param name="contentLength">Total content length</param>
168
+ /// <param name="fileStream">File stream to write to</param>
169
+ /// <param name="cancellationToken">Cancellation token</param>
170
+ private async Task ContinueDownload (
171
+ Uri url ,
172
+ long ? contentLength ,
173
+ FileStream fileStream ,
174
+ CancellationToken cancellationToken
175
+ )
176
+ {
177
+ _logger . LogInformation ( $ "Trying to continue downloading after failing") ;
178
+
179
+ try
180
+ {
181
+ using HttpRequestMessage request = new ( )
182
+ {
183
+ RequestUri = url ,
184
+ Method = HttpMethod . Get
185
+ } ;
186
+
187
+ request . Headers . Range = new RangeHeaderValue ( fileStream . Position , contentLength ) ;
188
+
189
+ using var response = await _httpClient . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) . ConfigureAwait ( false ) ;
190
+
191
+ if ( response . StatusCode is not System . Net . HttpStatusCode . PartialContent )
192
+ {
193
+ ThrowHelper . ThrowInvalidOperationException ( "Error while downloading a file: " + response . StatusCode ) ;
194
+ }
195
+
196
+ await using var source = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
197
+
198
+ await source . CopyToAsync ( fileStream , cancellationToken ) . ConfigureAwait ( false ) ;
199
+ }
200
+ catch ( HttpIOException )
201
+ {
202
+ await ContinueDownload ( url , contentLength , fileStream , cancellationToken ) . ConfigureAwait ( false ) ;
203
+ }
204
+ }
144
205
}
0 commit comments