Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Modules/Network/Loaders/LoaderDownload.cs
T
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

238 lines
7.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using PCL.Core.Utils;
namespace PCL.Network.Loaders;
public class LoaderDownload : ModLoader.LoaderBase
{
public ModBase.SafeList<PCL.Network.DownloadFile> files;
private int _fileRemain;
private readonly object _fileRemainLock = new();
private CancellationTokenSource? _cancellationTokenSource;
public int FailCount { get; set; }
public override double Progress
{
get => State >= ModBase.LoadState.Finished ? 1 : (files.Any() ? files.Average(file => file.Progress) : 0);
set => throw new Exception("文件下载不允许指定进度");
}
public LoaderDownload(string name, List<PCL.Network.DownloadFile> fileTasks)
{
base.name = name;
files = new ModBase.SafeList<PCL.Network.DownloadFile>(fileTasks ?? new List<PCL.Network.DownloadFile>());
}
public void RefreshStat() { }
public override void Start(object input = null, bool isForceRestart = false)
{
if (input is List<PCL.Network.DownloadFile> inputFiles)
files = new ModBase.SafeList<PCL.Network.DownloadFile>(inputFiles);
lock (lockState)
{
if (State == ModBase.LoadState.Loading)
return;
State = ModBase.LoadState.Loading;
}
_cancellationTokenSource = new CancellationTokenSource();
lock (_fileRemainLock)
{
_fileRemain = files.Count;
}
ModNet.NetManager.Start(this);
ModBase.RunInNewThread(() => Run(_cancellationTokenSource.Token), $"DL/{Uuid}");
}
private void Run(CancellationToken cancellationToken)
{
try
{
if (!files.Any())
{
OnFinish();
return;
}
var exceptions = new ConcurrentQueue<Exception>();
using var semaphore = new SemaphoreSlim(GetMaxParallelFiles());
var tasks = files.Select(async file =>
{
var entered = false;
try
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
entered = true;
await ProcessFileAsync(file, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
}
catch (Exception ex)
{
file.AddError(ex);
file.State = PCL.Network.NetState.Interrupted;
exceptions.Enqueue(ex);
_cancellationTokenSource?.Cancel();
}
finally
{
if (entered)
semaphore.Release();
}
}).ToList();
Task.WhenAll(tasks).GetAwaiter().GetResult();
if (!exceptions.IsEmpty)
OnFail(exceptions.ToList());
}
catch (OperationCanceledException)
{
Abort();
}
catch (Exception ex)
{
OnFail(new List<Exception> { ex });
}
}
private int GetMaxParallelFiles()
{
return Math.Max(1, Math.Min(files.Count, Math.Clamp(ModNet.NetTaskThreadLimit, 1, 64)));
}
private async Task ProcessFileAsync(PCL.Network.DownloadFile file, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
file.RegisterLoader(this);
if (State >= ModBase.LoadState.Finished)
return;
Directory.CreateDirectory(Path.GetDirectoryName(file.LocalPath) ?? throw new IOException("下载路径无效"));
if (file.Check?.canUseExistsFile == true && file.Check.Check(file.LocalPath) is null)
{
file.IsCopy = true;
file.State = PCL.Network.NetState.Finished;
try { file.TotalSize = new FileInfo(file.LocalPath).Length; }
catch (IOException) { file.TotalSize = -1; }
file.DownloadedBytes = file.TotalSize;
file.Speed = 0;
file.ActiveThreads = 0;
OnFileFinish(file);
return;
}
file.State = PCL.Network.NetState.Connecting;
var enableParallelChunks = files.Count <= 1;
for (var retry = 0; retry < 4; retry++)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
await FileDownloader.DownloadAsync(file.Urls, file.LocalPath, file.UseBrowserUserAgent, file.CustomUserAgent,
cancellationToken, enableParallelChunks, file).ConfigureAwait(false);
break;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex) when (retry < 3)
{
ModBase.Log(ex, $"[Download] 重试 {retry + 1}/3{file.LocalPath}", ModBase.LogLevel.Debug);
Thread.Sleep(RandomUtils.NextInt(300, 500 + retry * 300));
}
}
try { file.TotalSize = new FileInfo(file.LocalPath).Length; }
catch (IOException) { file.TotalSize = -1; }
file.IsUnknownSize = file.TotalSize < 0;
file.DownloadedBytes = Math.Max(0, file.TotalSize);
file.Speed = 0;
file.ActiveThreads = 0;
file.State = PCL.Network.NetState.Finished;
OnFileFinish(file);
}
public void OnFileFinish(PCL.Network.DownloadFile file)
{
lock (_fileRemainLock)
{
_fileRemain -= 1;
if (_fileRemain > 0)
return;
}
OnFinish();
}
public void OnFinish()
{
RaisePreviewFinish();
lock (lockState)
{
if (State > ModBase.LoadState.Loading)
return;
State = ModBase.LoadState.Finished;
}
ModNet.NetManager.Finish(this);
}
public void OnFileFail(PCL.Network.DownloadFile file)
{
var errors = file.Errors;
OnFail(errors.Count > 0
? errors.ToList()
: new List<Exception> { new Exception($"文件下载失败:{file.LocalPath}") });
}
public void OnFail(List<Exception> exList)
{
lock (lockState)
{
if (State > ModBase.LoadState.Loading)
return;
Error = exList.FirstOrDefault() ?? new Exception("未知下载错误");
State = ModBase.LoadState.Failed;
}
FailCount += exList.Count;
foreach (var file in files.Where(file => file.State < PCL.Network.NetState.Finished))
{
file.State = PCL.Network.NetState.Interrupted;
file.Speed = 0;
file.ActiveThreads = 0;
file.AddErrors(exList);
}
ModNet.NetManager.Finish(this);
}
public override void Abort()
{
lock (lockState)
{
if (State >= ModBase.LoadState.Finished)
return;
State = ModBase.LoadState.Aborted;
}
_cancellationTokenSource?.Cancel();
foreach (var file in files.Where(file => file.State < PCL.Network.NetState.Finished))
{
file.State = PCL.Network.NetState.Interrupted;
file.Speed = 0;
file.ActiveThreads = 0;
}
ModNet.NetManager.Finish(this);
}
}