XFEExtension
【DLL】XFE各类拓展是一个C#的DLL库,旨在优化C#代码中常用语句的使用,并提供更简洁的访问方式,同时提供Xunit测试框架,快速搭建服务器/客户端,免费ChatGPTAPI接口,免费通讯服务器,XFE下载器,新增格式等
关注
0
Fork
0
Star
0
返回提交历史
Modified
XFE各类拓展.NetCore/FileExtension/FileExtension.cs
+34
-0
Modified
XFE各类拓展.NetCore/WebExtension/FileDownloadedEventArgs.cs
+14
-2
Modified
XFE各类拓展.NetCore/WebExtension/WebExtension.cs
+16
-1
Modified
XFE各类拓展.NetCore/WebExtension/XFEDownloader.cs
+152
-23
XFEstudio/XFEExtension
XFE下载器更新部分内容
ed8f914
代码差异
4 个文件
+216
-26
@@ -227,4 +227,38 @@ public static class FileExtension
227
227
catch { }
228
228
return null; // 如果没有找到占用文件的进程,返回null
229
229
}
230
/// <summary>
231
/// 获取文件的有效起始位置
232
/// </summary>
233
/// <param name="fileStream">文件流</param>
234
/// <param name="startPosition">起始搜寻位置</param>
235
/// <param name="endPosition">结束搜寻位置</param>
236
/// <returns>一个长整型,介于起始位置和结束位置之间</returns>
237
public static async Task<long> GetValidPosition(this FileStream fileStream, long startPosition, long endPosition)
238
{
239
long seekPosition = endPosition;
240
fileStream.Seek(startPosition, SeekOrigin.Begin);
241
byte[] buffer = new byte[endPosition - startPosition];
242
await fileStream.ReadAsync(buffer.AsMemory(0, (int)(endPosition - startPosition)));
243
for (int i = buffer.Length - 1; i >= 0; i--)
244
{
245
if (buffer[i] != 0)
246
break;
247
seekPosition--;
248
}
249
return seekPosition > startPosition ? seekPosition : startPosition;
250
}
251
/// <summary>
252
/// 获取文件的有效起始位置
253
/// </summary>
254
/// <param name="fileStream">文件流</param>
255
/// <param name="startPosition">起始搜寻位置</param>
256
/// <returns>一个长整型,介于起始位置和结束位置之间</returns>
257
public static async Task<long> GetValidPosition(this FileStream fileStream, long startPosition) => await GetValidPosition(fileStream, startPosition, fileStream.Length);
258
/// <summary>
259
/// 获取文件的有效起始位置
260
/// </summary>
261
/// <param name="fileStream">文件流</param>
262
/// <returns>一个长整型,介于起始位置和结束位置之间</returns>
263
public static async Task<long> GetValidPosition(this FileStream fileStream) => await GetValidPosition(fileStream, 0, fileStream.Length);
230
264
}
@@ -3,7 +3,7 @@
3
3
/// <summary>
4
4
/// 字符下载事件
5
5
/// </summary>
6
public class FileDownloadedEventArgs(byte[] currentBuffer, long downloadedBufferSize, long? totalBufferSize, bool downloaded) : EventArgs
6
public class FileDownloadedEventArgs(byte[] currentBuffer, long downloadedBufferSize, long? totalBufferSize, long currentSegmentTotalBufferSize, long currentSegmentDownloadedBufferSize, int currentSegmentIndex, bool downloaded) : EventArgs
7
7
{
8
8
9
9
/// <summary>
@@ -11,7 +11,7 @@ public class FileDownloadedEventArgs(byte[] currentBuffer, long downloadedBuffer
11
11
/// </summary>
12
12
public byte[] CurrentBuffer { get; } = currentBuffer;
13
13
/// <summary>
14
/// 总计下载的字节长度
14
/// 总共已下载的字节大小
15
15
/// </summary>
16
16
public long DownloadedBufferSize { get; } = downloadedBufferSize;
17
17
/// <summary>
@@ -19,6 +19,18 @@ public class FileDownloadedEventArgs(byte[] currentBuffer, long downloadedBuffer
19
19
/// </summary>
20
20
public long? TotalBufferSize { get; } = totalBufferSize;
21
21
/// <summary>
22
/// 当前节的总计需要下载的字节大小
23
/// </summary>
24
public long CurrentSegmentTotalBufferSize { get; } = currentSegmentTotalBufferSize;
25
/// <summary>
26
/// 当前节的已下载的字节大小
27
/// </summary>
28
public long CurrentSegmentDownloadedBufferSize { get; } = currentSegmentDownloadedBufferSize;
29
/// <summary>
30
/// 当前节的索引
31
/// </summary>
32
public int CurrentSegmentIndex { get; } = currentSegmentIndex;
33
/// <summary>
22
34
/// 是否下载完成
23
35
/// </summary>
24
36
public bool Downloaded { get; } = downloaded;
@@ -1,4 +1,5 @@
1
using System.Text;
1
using System.Net.Http;
2
using System.Text;
2
3
using XFE各类拓展.NetCore.TaskExtension;
3
4
4
5
namespace XFE各类拓展.NetCore.WebExtension;
@@ -152,4 +153,18 @@ public static class WebExtension
152
153
return null;
153
154
}
154
155
}
156
/// <summary>
157
/// 通过给定的URL获取其文件名
158
/// </summary>
159
/// <param name="url">指定的URL</param>
160
/// <returns></returns>
161
public static async Task<string?> GetFileNameFromURL(this string url)
162
{
163
var response = await new HttpClient().GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
164
response.EnsureSuccessStatusCode();
165
if (response.Content.Headers.ContentDisposition?.FileName is null)
166
return Path.GetFileName(response.RequestMessage?.RequestUri?.AbsolutePath);
167
else
168
return response.Content.Headers.ContentDisposition.FileNameStar;
169
}
155
170
}
@@ -1,12 +1,20 @@
1
using XFE各类拓展.NetCore.DelegateExtension;
1
using System.Diagnostics.CodeAnalysis;
2
using XFE各类拓展.NetCore.DelegateExtension;
3
using XFE各类拓展.NetCore.FileExtension;
4
using XFE各类拓展.NetCore.TaskExtension;
2
5
3
6
namespace XFE各类拓展.NetCore.WebExtension;
4
7
5
8
/// <summary>
6
9
/// XFE下载器
7
10
/// </summary>
8
public class XFEDownloader
11
public class XFEDownloader : IDisposable
9
12
{
13
private bool disposedValue;
14
private readonly HttpClient httpClient = new();
15
private HttpResponseMessage? responseMessage;
16
private readonly List<HttpResponseMessage> httpResponseMessages = [];
17
private readonly List<Task> downloadTasks = [];
10
18
/// <summary>
11
19
/// 字节下载事件
12
20
/// </summary>
@@ -14,45 +22,166 @@ public class XFEDownloader
14
22
/// <summary>
15
23
/// 目标下载地址
16
24
/// </summary>
17
public required string DownloadUrl { get; set; }
25
public required string DownloadUrl { get; init; }
18
26
/// <summary>
19
27
/// 储存位置
20
28
/// </summary>
21
public required string SavePath { get; set; }
29
public required string SavePath { get; init; }
30
/// <summary>
31
/// 文件分段下载的数量(可加速下载)
32
/// </summary>
33
public int FileSegmentCount { get; init; } = 1;
22
34
/// <summary>
23
35
/// 已下载
24
36
/// </summary>
25
public bool Downloaded { get; }
37
public bool Downloaded { get; private set; }
38
/// <summary>
39
/// 暂停
40
/// </summary>
41
public bool IsPaused { get; set; }
26
42
/// <summary>
27
43
/// 开始下载
28
44
/// </summary>
29
/// <param name="continueFromLastDownload"></param>
45
/// <param name="continueFromLastDownload">是否为继续上次的下载</param>
30
46
/// <returns></returns>
31
47
public async Task Download(bool continueFromLastDownload = true)
32
48
{
33
using var client = new HttpClient();
34
49
var continueDownload = continueFromLastDownload && File.Exists(SavePath);
35
using var fileStream = new FileStream(SavePath, continueDownload ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
36
50
long totalRead = 0;
37
long lastBufferDownloadSize = fileStream.Length;
38
if (continueDownload)
51
responseMessage ??= await GetHttpResponseMessage();
52
long? totalFileSize = responseMessage.Content.Headers.ContentLength;
53
using var createFileStream = new FileStream(SavePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, 8192, true);
54
if (totalFileSize is not null)
55
createFileStream.SetLength(totalFileSize.Value);
56
for (int i = 0; i < FileSegmentCount; i++)
39
57
{
40
client.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(lastBufferDownloadSize, null);
41
totalRead = lastBufferDownloadSize;
58
int currentSegment = i;
59
downloadTasks.Add(Task.Run(async () =>
60
{
61
using var fileStream = new FileStream(SavePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 8192, true);
62
long lastBufferDownloadSize = continueDownload ? await fileStream.GetValidPosition() : fileStream.Length / FileSegmentCount * currentSegment;
63
long endPosition = currentSegment == FileSegmentCount - 1 ? fileStream.Length - fileStream.Length / FileSegmentCount * currentSegment : fileStream.Length / FileSegmentCount * (currentSegment + 1);
64
fileStream.Seek(lastBufferDownloadSize, SeekOrigin.Begin);
65
if (continueDownload)
66
{
67
httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(lastBufferDownloadSize, endPosition);
68
totalRead += lastBufferDownloadSize;
69
}
70
using var contentStream = await (await GetHttpResponseMessage()).Content.ReadAsStreamAsync();
71
byte[] buffer = new byte[8192];
72
int currentRead;
73
while ((currentRead = await contentStream.ReadAsync(buffer)) > 0 && !disposedValue)
74
{
75
if (IsPaused)
76
while (!IsPaused && !disposedValue) { }
77
await fileStream.WriteAsync(buffer.AsMemory(0, currentRead));
78
totalRead += currentRead;
79
var bufferCopy = new byte[8192];
80
bufferCopy.CopyTo(buffer, 0);
81
if (totalRead == totalFileSize)
82
Downloaded = true;
83
BufferDownloaded?.Invoke(this, new FileDownloadedEventArgs(bufferCopy, totalRead, totalFileSize, Downloaded));
84
}
85
}));
42
86
}
43
using var response = await client.GetAsync(DownloadUrl, HttpCompletionOption.ResponseHeadersRead);
87
await Task.WhenAll(downloadTasks);
88
}
89
/// <summary>
90
/// 暂停下载
91
/// </summary>
92
public void Pause() => IsPaused = true;
93
/// <summary>
94
/// 继续下载
95
/// </summary>
96
/// <returns></returns>
97
public async Task Continue() => await Download();
98
/// <summary>
99
/// 获取下载信息
100
/// </summary>
101
/// <returns></returns>
102
public async Task<(string? fileName, long? fileSize)> GetDownloadInfo()
103
{
104
responseMessage ??= await GetHttpResponseMessage();
105
return (Path.GetFileName(responseMessage.RequestMessage?.RequestUri?.AbsolutePath), responseMessage.Content.Headers.ContentLength);
106
}
107
private async Task<HttpResponseMessage> GetHttpResponseMessage()
108
{
109
var response = await httpClient.GetAsync(DownloadUrl, HttpCompletionOption.ResponseHeadersRead);
44
110
response.EnsureSuccessStatusCode();
45
long? totalFileSize = response.Content.Headers.ContentLength + lastBufferDownloadSize;
46
using var contentStream = await response.Content.ReadAsStreamAsync();
47
byte[] buffer = new byte[8192];
48
int currentRead;
49
while ((currentRead = await contentStream.ReadAsync(buffer)) > 0)
111
return response;
112
}
113
/// <summary>
114
/// 释放资源
115
/// </summary>
116
/// <param name="disposing"></param>
117
protected virtual void Dispose(bool disposing)
118
{
119
if (!disposedValue)
50
120
{
51
await fileStream.WriteAsync(buffer.AsMemory(0, currentRead));
52
totalRead += currentRead;
53
var bufferCopy = new byte[8192];
54
bufferCopy.CopyTo(buffer, 0);
55
BufferDownloaded?.Invoke(this, new FileDownloadedEventArgs(bufferCopy, totalRead, totalFileSize, currentRead != 8192));
121
if (disposing)
122
{
123
httpClient.Dispose();
124
responseMessage?.Dispose();
125
foreach (var response in httpResponseMessages)
126
{
127
response.Dispose();
128
}
129
foreach (var task in downloadTasks)
130
{
131
task.Dispose();
132
}
133
}
134
135
// TODO: 释放未托管的资源(未托管的对象)并重写终结器
136
// TODO: 将大型字段设置为 null
137
disposedValue = true;
56
138
}
57
139
}
140
/// <summary>
141
/// 释放资源
142
/// </summary>
143
~XFEDownloader()
144
{
145
Dispose(disposing: false);
146
}
147
/// <summary>
148
/// 释放资源
149
/// </summary>
150
public void Dispose()
151
{
152
Dispose(disposing: true);
153
GC.SuppressFinalize(this);
154
}
155
/// <summary>
156
/// XFE下载器
157
/// </summary>
158
public XFEDownloader() { }
159
/// <summary>
160
/// XFE下载器
161
/// </summary>
162
/// <param name="url">下载的URL地址</param>
163
[SetsRequiredMembers]
164
public XFEDownloader(string url)
165
{
166
if (string.IsNullOrEmpty(url))
167
throw new ArgumentException($"“{nameof(url)}”不能为 null 或空。", nameof(url));
168
string filePath = url.GetFileNameFromURL().WaitAndGetResult()!;
169
DownloadUrl = url;
170
SavePath = filePath;
171
}
172
/// <summary>
173
/// XFE下载器
174
/// </summary>
175
/// <param name="url">下载的URL地址</param>
176
/// <param name="filePath">本地文件路径</param>
177
[SetsRequiredMembers]
178
public XFEDownloader(string url, string filePath)
179
{
180
if (string.IsNullOrEmpty(url))
181
throw new ArgumentException($"“{nameof(url)}”不能为 null 或空。", nameof(url));
182
if (string.IsNullOrEmpty(filePath))
183
throw new ArgumentException($"“{nameof(filePath)}”不能为 null 或空。", nameof(filePath));
184
DownloadUrl = url;
185
SavePath = filePath;
186
}
58
187
}