Update DownloadSystem

This commit is contained in:
hevinci
2022-04-04 22:43:37 +08:00
parent a0c8c9dc67
commit f1615b4487
10 changed files with 146 additions and 59 deletions
@@ -0,0 +1,119 @@
namespace YooAsset
{
internal abstract class DownloaderBase
{
protected enum ESteps
{
None,
CreateDownload,
CheckDownload,
TryAgain,
Succeed,
Failed,
}
protected readonly BundleInfo _bundleInfo;
protected ESteps _steps = ESteps.None;
protected int _timeout;
protected int _failedTryAgain;
protected int _requestCount;
protected string _requestURL;
protected string _lastError = string.Empty;
protected float _downloadProgress = 0f;
protected ulong _downloadedBytes = 0;
/// <summary>
/// 下载进度(0-100f)
/// </summary>
public float DownloadProgress
{
get { return _downloadProgress; }
}
/// <summary>
/// 已经下载的总字节数
/// </summary>
public ulong DownloadedBytes
{
get { return _downloadedBytes; }
}
public DownloaderBase(BundleInfo bundleInfo)
{
_bundleInfo = bundleInfo;
}
public void SendRequest(int failedTryAgain, int timeout)
{
if (string.IsNullOrEmpty(_bundleInfo.LocalPath))
throw new System.ArgumentNullException();
if (_steps == ESteps.None)
{
_failedTryAgain = failedTryAgain;
_timeout = timeout;
_steps = ESteps.CreateDownload;
}
}
public abstract void Update();
public abstract void Abort();
/// <summary>
/// 获取网络请求地址
/// </summary>
protected string GetRequestURL()
{
// 轮流返回请求地址
_requestCount++;
if (_requestCount % 2 == 0)
return _bundleInfo.RemoteFallbackURL;
else
return _bundleInfo.RemoteMainURL;
}
/// <summary>
/// 获取资源包信息
/// </summary>
public BundleInfo GetBundleInfo()
{
return _bundleInfo;
}
/// <summary>
/// 检测下载器是否已经完成(无论成功或失败)
/// </summary>
public bool IsDone()
{
return _steps == ESteps.Succeed || _steps == ESteps.Failed;
}
/// <summary>
/// 下载过程是否发生错误
/// </summary>
public bool HasError()
{
return _steps == ESteps.Failed;
}
/// <summary>
/// 报告错误信息
/// </summary>
public void ReportError()
{
YooLogger.Error($"Failed to download : {_requestURL} Error : {_lastError}");
}
/// <summary>
/// 获取最近一条错误日志
/// </summary>
public string GetLastError()
{
return _lastError;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dda4d1eafa2c9f34fade509f8dae9c04
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,166 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace YooAsset
{
internal sealed class FileDownloader : DownloaderBase
{
private UnityWebRequest _webRequest;
private UnityWebRequestAsyncOperation _operationHandle;
// 重置变量
private bool _isAbort = false;
private ulong _latestDownloadBytes;
private float _latestDownloadRealtime;
private float _tryAgainTimer;
public FileDownloader(BundleInfo bundleInfo) : base(bundleInfo)
{
}
public override void Update()
{
if (_steps == ESteps.None)
return;
if (IsDone())
return;
// 创建下载器
if (_steps == ESteps.CreateDownload)
{
// 重置变量
_downloadProgress = 0f;
_downloadedBytes = 0;
_isAbort = false;
_latestDownloadBytes = 0;
_latestDownloadRealtime = Time.realtimeSinceStartup;
_tryAgainTimer = 0f;
_requestURL = GetRequestURL();
_webRequest = new UnityWebRequest(_requestURL, UnityWebRequest.kHttpVerbGET);
DownloadHandlerFile handler = new DownloadHandlerFile(_bundleInfo.LocalPath);
handler.removeFileOnAbort = true;
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
_operationHandle = _webRequest.SendWebRequest();
_steps = ESteps.CheckDownload;
}
// 检测下载结果
if (_steps == ESteps.CheckDownload)
{
_downloadProgress = _webRequest.downloadProgress * 100f;
_downloadedBytes = _webRequest.downloadedBytes;
if (_operationHandle.isDone == false)
{
CheckTimeout();
return;
}
// 检查网络错误
bool hasError = false;
#if UNITY_2020_3_OR_NEWER
if (_webRequest.result != UnityWebRequest.Result.Success)
{
hasError = true;
_lastError = _webRequest.error;
}
#else
if (_webRequest.isNetworkError || _webRequest.isHttpError)
{
hasError = true;
_lastError = _webRequest.error;
}
#endif
// 检查文件完整性
if (hasError == false)
{
// 注意:如果文件验证失败需要删除文件
if (DownloadSystem.CheckContentIntegrity(_bundleInfo) == false)
{
hasError = true;
_lastError = $"Verification failed";
}
}
if (hasError == false)
{
_steps = ESteps.Succeed;
DownloadSystem.CacheVerifyFile(_bundleInfo.Hash, _bundleInfo.BundleName);
}
else
{
ReportError();
if (File.Exists(_bundleInfo.LocalPath))
File.Delete(_bundleInfo.LocalPath);
// 失败后重新尝试
if (_failedTryAgain > 0)
_steps = ESteps.TryAgain;
else
_steps = ESteps.Failed;
}
// 释放下载器
DisposeWebRequest();
}
// 重新尝试下载
if (_steps == ESteps.TryAgain)
{
_tryAgainTimer += Time.unscaledDeltaTime;
if (_tryAgainTimer > 1f)
{
_failedTryAgain--;
_steps = ESteps.CreateDownload;
YooLogger.Warning($"Try again download : {_requestURL}");
}
}
}
public override void Abort()
{
if (IsDone() == false)
{
_steps = ESteps.Failed;
_lastError = "user abort";
DisposeWebRequest();
}
}
private void CheckTimeout()
{
// 注意:在连续时间段内无新增下载数据及判定为超时
if (_isAbort == false)
{
if (_latestDownloadBytes != DownloadedBytes)
{
_latestDownloadBytes = DownloadedBytes;
_latestDownloadRealtime = Time.realtimeSinceStartup;
}
float offset = Time.realtimeSinceStartup - _latestDownloadRealtime;
if (offset > _timeout)
{
YooLogger.Warning($"Web file request timeout : {_requestURL}");
_webRequest.Abort();
_isAbort = true;
}
}
}
private void DisposeWebRequest()
{
if (_webRequest != null)
{
_webRequest.Dispose();
_webRequest = null;
_operationHandle = null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c6e1a8bc8d5e664395395daa772ddd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,255 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Threading;
namespace YooAsset
{
internal sealed class HttpDownloader : DownloaderBase
{
/// <summary>
/// 多线程下载器
/// </summary>
private class ThreadDownloader
{
private const int BufferSize = 1042 * 4;
private Thread _thread;
private bool _running = true;
private string _url;
private string _savePath;
private string _fileHash;
private string _fileCRC;
private long _fileSize;
private int _timeout;
/// <summary>
/// 下载是否结束
/// </summary>
public bool IsDone = false;
/// <summary>
/// 错误日志
/// </summary>
public string Error = string.Empty;
/// <summary>
/// 下载进度
/// </summary>
public float DownloadProgress = 0f;
/// <summary>
/// 已经下载的总字节数
/// </summary>
public ulong DownloadedBytes = 0;
/// <summary>
/// 开始下载
/// </summary>
public void Run(string url, string savePath, string fileHash, string fileCRC, long fileSize, int timeout)
{
_url = url;
_savePath = savePath;
_fileHash = fileHash;
_fileCRC = fileCRC;
_fileSize = fileSize;
_timeout = timeout;
_thread = new Thread(ThreadRun);
_thread.IsBackground = true;
_thread.Start();
}
/// <summary>
/// 中断下载线程
/// </summary>
public void Abort()
{
_running = false;
}
/// <summary>
/// 下载结果
/// </summary>
public bool HasError()
{
if (string.IsNullOrEmpty(Error))
return false;
else
return true;
}
private void ThreadRun()
{
long fileTotalSize = _fileSize;
FileStream fileStream = null;
HttpWebResponse webResponse = null;
Stream responseStream = null;
try
{
// 创建文件流
fileStream = new FileStream(_savePath, FileMode.OpenOrCreate, FileAccess.Write);
long fileLength = fileStream.Length;
// 创建HTTP下载请求
HttpWebRequest webRequest = WebRequest.Create(_url) as HttpWebRequest;
webRequest.Timeout = _timeout * 1000;
webRequest.ProtocolVersion = HttpVersion.Version10;
if (fileLength > 0)
{
// 注意:设置远端请求文件的起始位置
webRequest.AddRange(fileLength);
// 注意:设置本地文件流的起始位置
fileStream.Seek(fileLength, SeekOrigin.Begin);
}
// 读取下载数据并保存到文件
webResponse = webRequest.GetResponse() as HttpWebResponse;
responseStream = webResponse.GetResponseStream();
byte[] buffer = new byte[BufferSize];
while (_running)
{
int length = responseStream.Read(buffer, 0, buffer.Length);
if (length <= 0)
break;
fileStream.Write(buffer, 0, length);
// 计算下载进度
// 注意:原子操作保证数据安全
fileLength += length;
float progress = fileLength / fileTotalSize;
DownloadProgress = progress;
DownloadedBytes = (ulong)fileLength;
}
}
catch (Exception e)
{
Error = e.Message;
}
finally
{
if (responseStream != null)
{
responseStream.Close();
responseStream.Dispose();
}
if (webResponse != null)
{
webResponse.Close();
webResponse.Dispose();
}
if (fileStream != null)
{
fileStream.Flush();
fileStream.Close();
}
// 验证下载文件完整性
if (DownloadedBytes == (ulong)_fileSize)
{
bool verfiyResult = DownloadSystem.CheckContentIntegrity(_savePath, _fileSize, _fileCRC);
if (verfiyResult == false)
{
Error = $"Verify download content failed : {_fileHash}";
if (File.Exists(_savePath))
File.Delete(_savePath);
}
}
else
{
Error = $"Download content is incomplete : {_fileHash}";
}
IsDone = true;
}
}
}
private ThreadDownloader _threadDownloader;
private float _tryAgainTimer;
public HttpDownloader(BundleInfo bundleInfo) : base(bundleInfo)
{
}
public override void Update()
{
if (_steps == ESteps.None)
return;
if (IsDone())
return;
if (_steps == ESteps.CreateDownload)
{
// 重置变量
_downloadProgress = 0f;
_downloadedBytes = 0;
_tryAgainTimer = 0f;
_requestURL = GetRequestURL();
_threadDownloader = new ThreadDownloader();
_threadDownloader.Run(_requestURL, _bundleInfo.LocalPath, _bundleInfo.Hash, _bundleInfo.CRC, _bundleInfo.SizeBytes, _timeout);
_steps = ESteps.CheckDownload;
}
if (_steps == ESteps.CheckDownload)
{
_downloadProgress = _threadDownloader.DownloadProgress * 100f;
_downloadedBytes = _threadDownloader.DownloadedBytes;
if (_threadDownloader.IsDone == false)
return;
if (_threadDownloader.HasError())
{
_lastError = _threadDownloader.Error;
ReportError();
// 失败后重新尝试
if (_failedTryAgain > 0)
_steps = ESteps.TryAgain;
else
_steps = ESteps.Failed;
}
else
{
DownloadSystem.CacheVerifyFile(_bundleInfo.Hash, _bundleInfo.BundleName);
_steps = ESteps.Succeed;
}
}
// 重新尝试下载
if (_steps == ESteps.TryAgain)
{
_tryAgainTimer += UnityEngine.Time.unscaledDeltaTime;
if (_tryAgainTimer > 1f)
{
_failedTryAgain--;
_steps = ESteps.CreateDownload;
YooLogger.Warning($"Try again download : {_requestURL}");
}
}
}
public override void Abort()
{
if(IsDone() == false)
{
_steps = ESteps.Failed;
_lastError = "user abort";
if (_threadDownloader != null)
{
_threadDownloader.Abort();
_threadDownloader = null;
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 057a358d2d0c92b47add59dac8bef783
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
namespace YooAsset
{
internal sealed class TempDownloader : DownloaderBase
{
public TempDownloader(BundleInfo bundleInfo) : base(bundleInfo)
{
_downloadProgress = 1f;
_downloadedBytes = (ulong)bundleInfo.SizeBytes;
_steps = ESteps.Succeed;
}
public override void Update()
{
}
public override void Abort()
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c7907ead85e2f94786308b28c37a8aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: