refactor(downloader): 重构下载器分片与系统资源分配逻辑 - #3506
Conversation
审查者指南该 PR 将旧版的逐文件分块下载替换为自适应的、基于 Range 的大文件下载流程,并提供顺序下载回退机制;同时通过全局连接、缓冲区、带宽和监控资源进行协调。该 PR 还新增了 HTTP 协议选择、面向连接的配置、更智能的加载器复用与探测机制,以及相应的 UI 更新。 自适应大文件下载时序图sequenceDiagram
participant LoaderDownload
participant FileDownloader
participant AdaptiveRangeDownloader
participant DownloadResourceManager
participant HTTPServer
participant Disk
LoaderDownload->>FileDownloader: DownloadAsync
FileDownloader->>AdaptiveRangeDownloader: TryDownloadAsync
AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
AdaptiveRangeDownloader->>HTTPServer: Range probe
HTTPServer-->>AdaptiveRangeDownloader: PartialContent with ContentRange
AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
AdaptiveRangeDownloader->>HTTPServer: Range segment requests
HTTPServer-->>AdaptiveRangeDownloader: PartialContent
AdaptiveRangeDownloader->>DownloadResourceManager: ReserveBufferAsync
AdaptiveRangeDownloader->>DownloadResourceManager: ThrottleAsync
AdaptiveRangeDownloader->>Disk: RandomAccess.WriteAsync
AdaptiveRangeDownloader->>DownloadResourceManager: RecordDownloadedBytes
AdaptiveRangeDownloader-->>FileDownloader: completed
FileDownloader->>FileDownloader: PromoteTempFile
下载策略选择流程图flowchart TD
Start[DownloadSingleAsync] --> Size{expectedSize known}
Size -->|less than 4 MiB| Sequential[DownloadSequentiallyAsync]
Size -->|unknown| Probe[TryDownloadAsync]
Size -->|at least 4 MiB| Range[TryDownloadAsync]
Probe -->|Range unsupported or small| Sequential
Range -->|Range supported| Parallel[Adaptive range workers]
Range -->|Range unsupported| Sequential
Sequential --> Promote[PromoteTempFile]
Parallel --> Promote
Promote --> Complete[MarkDownloadCompleted]
文件级变更
针对关联 Issue 的评估
可能关联的 Issue
提示和命令与 Sourcery 交互
自定义使用体验访问你的控制面板:
获取帮助Original review guide in EnglishReviewer's GuideThe PR replaces legacy per-file chunked downloading with an adaptive, range-based large-file pipeline and sequential fallback, coordinated by global connection, buffer, bandwidth, and monitoring resources; it also adds HTTP protocol selection, connection-oriented configuration, smarter loader reuse/probing, and corresponding UI updates. Sequence diagram for adaptive large-file downloadingsequenceDiagram
participant LoaderDownload
participant FileDownloader
participant AdaptiveRangeDownloader
participant DownloadResourceManager
participant HTTPServer
participant Disk
LoaderDownload->>FileDownloader: DownloadAsync
FileDownloader->>AdaptiveRangeDownloader: TryDownloadAsync
AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
AdaptiveRangeDownloader->>HTTPServer: Range probe
HTTPServer-->>AdaptiveRangeDownloader: PartialContent with ContentRange
AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
AdaptiveRangeDownloader->>HTTPServer: Range segment requests
HTTPServer-->>AdaptiveRangeDownloader: PartialContent
AdaptiveRangeDownloader->>DownloadResourceManager: ReserveBufferAsync
AdaptiveRangeDownloader->>DownloadResourceManager: ThrottleAsync
AdaptiveRangeDownloader->>Disk: RandomAccess.WriteAsync
AdaptiveRangeDownloader->>DownloadResourceManager: RecordDownloadedBytes
AdaptiveRangeDownloader-->>FileDownloader: completed
FileDownloader->>FileDownloader: PromoteTempFile
Flow diagram for download strategy selectionflowchart TD
Start[DownloadSingleAsync] --> Size{expectedSize known}
Size -->|less than 4 MiB| Sequential[DownloadSequentiallyAsync]
Size -->|unknown| Probe[TryDownloadAsync]
Size -->|at least 4 MiB| Range[TryDownloadAsync]
Probe -->|Range unsupported or small| Sequential
Range -->|Range supported| Parallel[Adaptive range workers]
Range -->|Range unsupported| Sequential
Sequential --> Promote[PromoteTempFile]
Parallel --> Promote
Promote --> Complete[MarkDownloadCompleted]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
# Conflicts: # PCL.Core/App/Localization/Languages/zh-CN.xaml # Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
There was a problem hiding this comment.
嘿——我发现了 4 个问题
AI Agent 提示词
请处理这次代码审查中的评论:
## 单独评论
### 评论 1
<location path="Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs" line_range="156-158" />
<code_context>
if (!File.Exists(fileAddress))
{
- FileDownloader.DownloadAsync(address, fileAddress + ModNet.netDownloadEnd).GetAwaiter().GetResult();
+ FileDownloader.DownloadAsync(address, fileAddress + ModNet.NetDownloadEnd).GetAwaiter().GetResult();
File.Delete(fileAddress);
- FileSystem.Rename(fileAddress + ModNet.netDownloadEnd, fileAddress);
+ FileSystem.Rename(fileAddress + ModNet.NetDownloadEnd, fileAddress);
ModBase.Log("[Minecraft] 皮肤下载成功:" + fileAddress);
}
</code_context>
<issue_to_address>
**issue (bug_risk):** `McSkinDownload` 将 `fileAddress + ModNet.NetDownloadEnd` 作为 `localPath` 传入,但重构后的下载器始终会写入 `localPath + ModNet.NetDownloadEnd`。因此,下载会创建 `fileAddress + .PCLDownloading + .PCLDownloading`,而调用方尝试重命名 `fileAddress + .PCLDownloading`,由于该路径不存在,重命名会失败。
**触发条件:** 通过 `McSkinDownload` 下载皮肤时。
**建议修复:** 将 `fileAddress` 传递给 `FileDownloader.DownloadAsync`,或者让下载器统一接受已经添加后缀的临时路径。
</issue_to_address>
### 评论 2
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="138" />
<code_context>
+ var totalSize = response.Content.Headers.ContentLength ?? -1;
+ // 某些源使用 chunked 传输没有 Content-Length,但清单校验信息仍可能带有文件大小
+ // 这个大小只用于慢速判断,完整性仍按响应头的 totalSize 校验
+ var slowCheckSize = totalSize > 0 ? totalSize : trackedFile?.Check?.actualSize ?? -1;
+ if (trackedFile is not null)
+ {
</code_context>
<issue_to_address>
**issue (broader_impact):** 当响应没有 `Content-Length` 时,顺序下载不会验证清单中的 `trackedFile.Check.actualSize`。`slowCheckSize` 仅用于慢速判断,而最终大小检查由于 `totalSize` 为 `-1` 被跳过,因此,如果分块传输的响应提前结束,文件仍会被当作成功完成的完整文件处理。
**触发条件:** 源使用不带 `Content-Length` 的分块传输,并且在达到清单声明的大小之前终止传输时。
**建议修复:** 将预期大小与响应的 `Content-Length` 分开跟踪,并在已下载字节数与 `trackedFile.Check.actualSize` 不一致时拒绝完成下载。
</issue_to_address>
### 评论 3
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs" line_range="54" />
<code_context>
+ CancellationToken cancellationToken)
+ {
+ var host = Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri.Host : url;
+ var hostQuota = HostConnectionQuotas.GetOrAdd(host, static _ => new AsyncQuota());
+ var hostLease = await hostQuota.AcquireAsync(1, () => ModNet.NetTaskConnectionsPerHostLimit, cancellationToken)
+ .ConfigureAwait(false);
</code_context>
<issue_to_address>
**issue (bug_risk):** 所有之前见过的主机都会永久保留在 `HostConnectionQuotas` 中,下载完成后也不会移除配额。因此,来自不受限制或由用户控制的主机名的下载会在进程的整个生命周期内为每个主机累积一个 `AsyncQuota` 对象。
**触发条件:** 启动器会话从许多不同的主机下载内容时,例如不断变化的签名 CDN 或镜像 URL。
**建议修复:** 在主机配额的使用量降为零后移除处于空闲状态的配额,或者使用有界的、会驱逐条目的主机配额注册表。
</issue_to_address>
### 评论 4
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="259-270" />
<code_context>
+ internal static async Task<HttpResponseMessage> SendDownloadRequestAsync(string url,
+ HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ using var requestTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ requestTimeout.CancelAfter(RequestTimeoutMilliseconds);
+ try
+ {
+ return await GetHttpClient(url)
+ .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, requestTimeout.Token)
+ .ConfigureAwait(false);
+ }
+ catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested &&
+ requestTimeout.IsCancellationRequested)
+ {
+ throw new TimeoutException($"等待下载源响应超时(30 秒):{url}", ex);
+ }
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** 等待响应头时发生的请求超时会被转换为 `TimeoutException`,而 `AdaptiveRangeDownloader.TryRecover` 处理的异常类型中不包括该异常。因此,单个分段的响应头超时会取消整个自适应下载,而不是按照所宣称的慢速连接恢复机制重试该分段。
**触发条件:** 某个范围分段请求接收响应头的时间超过 30 秒,而其他分段仍然可用时。
**建议修复:** 将转换后的 `TimeoutException` 视为可恢复的分段失败,或者为范围分段超时保留一种可重试的异常类型。
</issue_to_address>帮助我变得更有用!请在每条评论上点击 👍 或 👎,我会利用这些反馈来改进审查结果。
Original comment in English
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs" line_range="156-158" />
<code_context>
if (!File.Exists(fileAddress))
{
- FileDownloader.DownloadAsync(address, fileAddress + ModNet.netDownloadEnd).GetAwaiter().GetResult();
+ FileDownloader.DownloadAsync(address, fileAddress + ModNet.NetDownloadEnd).GetAwaiter().GetResult();
File.Delete(fileAddress);
- FileSystem.Rename(fileAddress + ModNet.netDownloadEnd, fileAddress);
+ FileSystem.Rename(fileAddress + ModNet.NetDownloadEnd, fileAddress);
ModBase.Log("[Minecraft] 皮肤下载成功:" + fileAddress);
}
</code_context>
<issue_to_address>
**issue (bug_risk):** `McSkinDownload` passes `fileAddress + ModNet.NetDownloadEnd` as `localPath`, but the refactored downloader always writes to `localPath + ModNet.NetDownloadEnd`. The download therefore creates `fileAddress + .PCLDownloading + .PCLDownloading`, while the caller renames `fileAddress + .PCLDownloading`, so the rename fails because that path does not exist.
**Triggers:** When downloading a skin through `McSkinDownload`.
**Suggested fix:** Pass `fileAddress` to `FileDownloader.DownloadAsync`, or make the downloader consistently accept an already-suffixed temporary path.
</issue_to_address>
### Comment 2
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="138" />
<code_context>
+ var totalSize = response.Content.Headers.ContentLength ?? -1;
+ // 某些源使用 chunked 传输没有 Content-Length,但清单校验信息仍可能带有文件大小
+ // 这个大小只用于慢速判断,完整性仍按响应头的 totalSize 校验
+ var slowCheckSize = totalSize > 0 ? totalSize : trackedFile?.Check?.actualSize ?? -1;
+ if (trackedFile is not null)
+ {
</code_context>
<issue_to_address>
**issue (broader_impact):** Sequential downloads do not validate the manifest's `trackedFile.Check.actualSize` when the response has no `Content-Length`. `slowCheckSize` is used only for the slow-speed heuristic, and the final size check is skipped because `totalSize` is `-1`, so a chunked response that ends early is promoted as a successful complete file.
**Triggers:** When a source uses chunked transfer without `Content-Length` and terminates before the manifest-declared size.
**Suggested fix:** Track the expected size separately from the response `Content-Length` and reject completion when the downloaded byte count differs from `trackedFile.Check.actualSize`.
</issue_to_address>
### Comment 3
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs" line_range="54" />
<code_context>
+ CancellationToken cancellationToken)
+ {
+ var host = Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri.Host : url;
+ var hostQuota = HostConnectionQuotas.GetOrAdd(host, static _ => new AsyncQuota());
+ var hostLease = await hostQuota.AcquireAsync(1, () => ModNet.NetTaskConnectionsPerHostLimit, cancellationToken)
+ .ConfigureAwait(false);
</code_context>
<issue_to_address>
**issue (bug_risk):** Every previously seen host is retained permanently in `HostConnectionQuotas`, and no quota is removed after its downloads finish. Downloads from unbounded or user-controlled hostnames therefore accumulate one `AsyncQuota` object per host for the lifetime of the process.
**Triggers:** When a launcher session downloads from many distinct hosts, such as changing signed CDN or mirror URLs.
**Suggested fix:** Remove an idle host quota after its usage reaches zero, or use a bounded/evicting host-quota registry.
</issue_to_address>
### Comment 4
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="259-270" />
<code_context>
+ internal static async Task<HttpResponseMessage> SendDownloadRequestAsync(string url,
+ HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ using var requestTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ requestTimeout.CancelAfter(RequestTimeoutMilliseconds);
+ try
+ {
+ return await GetHttpClient(url)
+ .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, requestTimeout.Token)
+ .ConfigureAwait(false);
+ }
+ catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested &&
+ requestTimeout.IsCancellationRequested)
+ {
+ throw new TimeoutException($"等待下载源响应超时(30 秒):{url}", ex);
+ }
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** A request timeout while waiting for response headers is converted to `TimeoutException`, which is not one of the exceptions handled by `AdaptiveRangeDownloader.TryRecover`; a single segment header timeout therefore cancels the entire adaptive download instead of retrying that segment under the advertised slow-connection recovery path.
**Triggers:** When a range-segment request takes longer than 30 seconds to receive response headers while other segments are still usable.
**Suggested fix:** Treat the converted `TimeoutException` as a recoverable segment failure, or preserve a retryable exception type for range-segment timeouts.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a2a8f061d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (speed < SequentialSlowSpeedBytesPerSecond) | ||
| throw new SlowSequentialDownloadException( | ||
| $"顺序下载速度连续 {SequentialSlowCheckSeconds} 秒低于 {SequentialSlowSpeedBytesPerSecond / 1024} KiB/s"); |
There was a problem hiding this comment.
Allow slow sequential downloads to finish
On an uncapped connection that remains below 40 KiB/s, every sequential attempt for a sufficiently large file throws here after five seconds. Because the same check is enabled for every source and all four retry rounds, a valid but slow single-source download can never complete; disable this restart heuristic on the final attempt or otherwise permit eventual progress.
Useful? React with 👍 / 👎.
| var availableAt = Math.Max(now, _nextBandwidthTick); | ||
| var duration = Math.Max(1L, (long)Math.Ceiling((double)bytes * Stopwatch.Frequency / limit)); | ||
| _nextBandwidthTick = availableAt + duration; | ||
| delayTicks = availableAt - now; |
There was a problem hiding this comment.
Remove canceled reads from the bandwidth schedule
When a speed cap is enabled, this permanently advances the shared schedule before the delay completes. If queued reads are then canceled, their unused reservations remain; with 64 default connections at the minimum 0.1 MiB/s cap, aborting a task can leave the next download waiting roughly 40 seconds, and the 256-connection setting can leave about 160 seconds of stale delay. Reclaim canceled reservations or use a limiter that accounts only for consumed permits.
Useful? React with 👍 / 👎.
| catch | ||
| { | ||
| lock (_lock) | ||
| _waiters.Remove(waiter); | ||
| throw; |
There was a problem hiding this comment.
Wake another quota waiter after a cancellation race
If Release removes a waiter and that waiter's cancellation wins before TrySetResult is observed, this catch cannot remove it because it was already dequeued, while no subsequent waiter is signaled. When the released quota is otherwise idle, unrelated live waiters can then remain blocked indefinitely; transfer the wake-up to another waiter when this race occurs or use cancellation-safe semaphore semantics.
Useful? React with 👍 / 👎.
| <RowDefinition Height="27" /> | ||
| <RowDefinition Height="27" /> | ||
| <RowDefinition Height="27" /> | ||
| <RowDefinition Height="27" /> |
There was a problem hiding this comment.
Keep the target-folder row auto-sized
This row corresponds to the target-folder hint, whose localized text spans two lines, but the change replaces its previous Auto height with a fixed 27 pixels. The hint is therefore clipped or overlaps the install-behavior row, especially in English; retain an auto-sized row for Grid.Row="9".
Useful? React with 👍 / 👎.
Generated by GPT-5.6 Terra Max
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 292138af6f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (manifestExpectedSize >= 0 && downloaded != manifestExpectedSize) | ||
| throw new IOException($"下载不完整:已写入 {downloaded},应为清单声明的 {manifestExpectedSize}"); |
There was a problem hiding this comment.
Let a matching hash override a stale manifest size
When a DownloadFile supplies both a hash and an inaccurate actualSize, this rejects the downloaded file on every source and retry even if its hash is correct. That conflicts with FileChecker.Check, which intentionally accepts a size mismatch when the hash matches (ModBase.cs lines 1252–1278), so manifests with stale size metadata can no longer be downloaded; validate the completed file with its checker or preserve the hash-over-size behavior.
Useful? React with 👍 / 👎.
This is generated by gpt-5.6-sol (max)
| public static DownloadService CreateDownloadService(string url, bool useBrowserUserAgent = false) | ||
| { | ||
| var chunkCount = Math.Min(Math.Max(1, ModNet.NetTaskThreadLimit), 4); | ||
| var chunkCount = Math.Min(Math.Clamp(ModNet.NetTaskConnectionLimit, 1, | ||
| ModNet.NetTaskSingleFileConnectionLimitMax), ModNet.NetTaskSingleFileConnectionLimit); | ||
| return new DownloadService(new DownloadConfiguration | ||
| { | ||
| ChunkCount = chunkCount, | ||
| ParallelCount = chunkCount, | ||
| ParallelDownload = chunkCount > 1, | ||
| MaximumBytesPerSecond = ModNet.NetTaskSpeedLimitHigh > 0 ? ModNet.NetTaskSpeedLimitHigh : 0, | ||
| DownloadFileExtension = ModNet.netDownloadEnd, | ||
| DownloadFileExtension = ModNet.NetDownloadEnd, | ||
| EnableAutoResumeDownload = false, | ||
| MaximumMemoryBufferBytes = 256L * 1024 * 1024, | ||
| MaximumMemoryBufferBytes = 2L * 1024 * 1024, | ||
| BufferBlockSize = 64 * 1024, | ||
| RequestConfiguration = DownloadRequestFactory.Create(url, useBrowserUserAgent) | ||
| }); | ||
| } |
There was a problem hiding this comment.
为什么 Http 请求组件里会有创建下载服务的函数?HOW?
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70d67f1cd3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| using var request = _owner.CreateRequest(HttpMethod.Get, DownloadRequestKind.RangeSegment); | ||
| request.Headers.Range = new RangeHeaderValue(segment.CurrentOffset, segment.End); | ||
| using var response = await FileDownloader.SendDownloadRequestAsync(_owner._url, request, | ||
| cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Pin range requests to one object version
When a large file at a mutable URL changes during the parallel download—or different CDN nodes serve same-length revisions—these requests send only Range and validate only offsets and total length. A download without a hash checker, such as the custom-download path, can therefore combine bytes from multiple revisions and still be promoted successfully. Retain a strong ETag from the probe/first response and send If-Range on every segment, falling back to a sequential restart if the validator changes.
Useful? React with 👍 / 👎.

本 PR 对下载器的分片策略与系统资源分配进行了重构。
修改内容:针对文件下载回退到 HTTP/1.1(至少在我这里高峰期 1.1 的速度显著快于 2.0),元数据与 API 请求仍然使用 HTTP/2.0;仅针对大于 4 MiB 的文件尝试分片;批量任务中未知大小的文件直接下载,不进行 Range 探测;大文件下载采用新的动态分片机制,分片大小更加灵活;内存缓存改为全局共享,并及时将内容写入硬盘,避免极端情况下内存过高占用。
测试时进行 Minecraft 26.2 清洁安装,下载高峰阶段相比 PCL 2.13.1.0 的相同阶段内存占用可减少约 2/3(稳定在 180 - 220 MB),同时保持下载速度基本持平甚至略微更快;与 PCL CE 2.15.0 相比内存占用差距不大(此 PR 版本占用略微减少),主要改进了部分情况下下载速度极其缓慢的问题。
同时,close #3356 。
本 PR 主要使用 AI 完成,因此需要较为细致的测试与检查。同时,回退到 HTTP/1.1 还是保留 HTTP/2.0 可能仍需要进一步调查与讨论。
Summary by Sourcery
重构下载器,使用新的自适应基于范围的分段机制并引入共享的全局资源管理,从而改进内存使用和大文件性能,同时重构配置和 UI,使其以“连接数”而非“线程数”的方式进行交互。
Enhancements:
DownloadService使用方式替换为:内部顺序下载器以及仅在文件足够大时才启用的自适应基于范围的并行下载器。DownloadResourceManager,在全局范围内协调所有下载的 HTTP 连接、共享缓冲区和带宽限速。Original summary in English
Summary by Sourcery
Refactor the downloader to use a new adaptive range-based segmentation mechanism with shared global resource management, improving memory usage and large-file performance while reworking configuration and UI to talk in terms of connections rather than threads.
Enhancements:
Sourcery 总结
重构下载器,使其能够根据文件大小自适应分段并共享资源,从而提升大文件下载的可靠性和内存使用效率,同时改进连接控制和 HTTP 行为。
新功能:
错误修复:
增强功能:
Original summary in English
Sourcery 总结
重构下载器的分片、资源管理与连接控制逻辑,以改善大文件下载性能、稳定性和内存使用。
新功能:
错误修复:
改进:
Original summary in English
Sourcery 摘要
重构下载器的分片策略与资源管理,以提升大文件下载性能、稳定性和内存使用效率。
新功能:
错误修复:
增强功能:
Original summary in English
Sourcery 摘要
重构下载器的分片、连接管理和资源调度逻辑,以提升大文件下载性能、稳定性及内存使用效率。
新功能:
错误修复:
改进:
日常维护:
Original summary in English
Summary by Sourcery
重构下载器的分片、连接管理和资源调度逻辑,以提升大文件下载性能、稳定性及内存使用效率。
New Features:
Bug Fixes:
Enhancements:
Chores: