using System.IO; using UnityEngine; using UnityEngine.Networking; namespace NBC.Asset { /// /// 下载文件 /// public class DownloadFileTask : DownloadTaskBase { private const string DownloadHeaderKey = "Content-Length"; private const int RetryDownloadCount = 3; public readonly string DownloadPath; public readonly string SavePath; public readonly string FileHash; /// /// 开启断点续传 /// public bool ReDownload = true; public DownloadFileTask(string path, string savePath, string hash = "") { DownloadPath = path; SavePath = savePath; FileHash = hash; } public enum DownLoadStatus { None, GetHeader, PrepareDownload, Download, VerifyingFile, Success, Failed, } private ulong _downloadTotalSize = 1; private UnityWebRequest _content; private UnityWebRequest _header; private bool _isAbort; private ulong _latestDownloadBytes; private float _latestDownloadRealtime; private ulong _fileOriginLength; private int RetryCount; private long ResponseCode = 0; public DownLoadStatus DownloadStatus { get; protected internal set; } = DownLoadStatus.None; public override float Progress => DownloadedBytes * 1f / DownloadTotalSize; public ulong DownloadedBytes { get; protected set; } public ulong DownloadTotalSize { get => _downloadTotalSize; set { _downloadTotalSize = value; if (_downloadTotalSize < 1) _downloadTotalSize = 1; } } public void Abort() { Fail("abort"); Dispose(); } protected override void OnStart() { DownloadStatus = DownLoadStatus.GetHeader; } protected override NTaskStatus OnProcess() { if (DownloadStatus == DownLoadStatus.GetHeader) { _header = UnityWebRequest.Head(DownloadPath); _header.SendWebRequest(); DownloadStatus = DownLoadStatus.PrepareDownload; } if (DownloadStatus == DownLoadStatus.PrepareDownload) { if (_header == null) { Fail($"get header info error"); Debug.LogError("get header info error"); return NTaskStatus.Fail; } if (!_header.isDone) return NTaskStatus.Running; Reset(); //远程文件信息 var value = _header.GetResponseHeader(DownloadHeaderKey); if (ulong.TryParse(value, out var totalSize)) { DownloadTotalSize = totalSize; } if (ReDownload) { //读取未下载完成的文件信息 var tempInfo = new FileInfo(SavePath); if (tempInfo.Exists) { _fileOriginLength = (ulong)tempInfo.Length; if (_fileOriginLength == DownloadTotalSize) { DownloadedBytes = _fileOriginLength; DownloadStatus = DownLoadStatus.VerifyingFile; return NTaskStatus.Running; } } } else { _fileOriginLength = 0; } _content = UnityWebRequest.Get(DownloadPath); if (_fileOriginLength > 0) { Debug.Log($"断点续传===={_fileOriginLength} path={DownloadPath}"); #if UNITY_2019_1_OR_NEWER _content.SetRequestHeader("Range", $"bytes={_fileOriginLength}-"); _content.downloadHandler = new DownloadHandlerFile(SavePath, true); #else _request.DownloadedBytes = 0; _content.downloadHandler = new DownloadHandlerFile(TempPath); #endif } else { _content.downloadHandler = new DownloadHandlerFile(SavePath); } _content.certificateHandler = new DownloadCertificateHandler(); _content.disposeDownloadHandlerOnDispose = true; _content.disposeCertificateHandlerOnDispose = true; _content.disposeUploadHandlerOnDispose = true; _content.SendWebRequest(); DownloadStatus = DownLoadStatus.Download; } if (DownloadStatus == DownLoadStatus.Download) { DownloadedBytes = _fileOriginLength + _content.downloadedBytes; if (!_content.isDone) { CheckTimeout(); return NTaskStatus.Running; } bool hasError = false; // 检查网络错误 #if UNITY_2020_3_OR_NEWER if (_content.result != UnityWebRequest.Result.Success) { hasError = true; _errorMsg = _content.error; ResponseCode = _content.responseCode; } #else if (_content.isNetworkError || _content.isHttpError) { hasError = true; _errorMsg = _content.error; ResponseCode = _content.responseCode; } #endif // 如果网络异常 if (hasError) { RetryCount++; if (RetryCount <= RetryDownloadCount) { Debug.Log($"网络异常 重新开始下载={DownloadPath} code={ResponseCode} msg={_content.error}"); //重新开始下载 DownloadStatus = DownLoadStatus.PrepareDownload; } else { //重试后还是网络错误,直接失败 Debug.Log("重试后还是网络错误,直接失败"); DownloadStatus = DownLoadStatus.Failed; } } else { DownloadStatus = DownLoadStatus.VerifyingFile; } } if (DownloadStatus == DownLoadStatus.VerifyingFile) { Dispose(); var tryPass = false; var tempInfo = new FileInfo(SavePath); if (tempInfo.Exists) { if (tempInfo.Length == (long)DownloadTotalSize) { tryPass = true; } else { _errorMsg = "file size error"; } if (!string.IsNullOrEmpty(FileHash)) { var hash = Util.ComputeHash(SavePath); if (FileHash.Equals(hash)) { tryPass = true; } else { _errorMsg = "file hash error"; } } } else { _errorMsg = "file not exists"; } if (!tryPass) { // 验证失败后删除文件 if (File.Exists(SavePath)) File.Delete(SavePath); Debug.Log("验证失败后删除文件,尝试重新下载"); //重新下载 DownloadStatus = DownLoadStatus.PrepareDownload; } else { DownloadStatus = DownLoadStatus.Success; } } if (DownloadStatus == DownLoadStatus.Success) { return NTaskStatus.Success; } if (DownloadStatus == DownLoadStatus.Failed) { return NTaskStatus.Fail; } return NTaskStatus.Running; } private void CheckTimeout() { if (_isAbort) return; if (_latestDownloadBytes != DownloadedBytes) { _latestDownloadBytes = DownloadedBytes; _latestDownloadRealtime = Time.realtimeSinceStartup; } float offset = Time.realtimeSinceStartup - _latestDownloadRealtime; if (_latestDownloadRealtime > 0 && offset > Const.DownloadTimeOut) { _content.Abort(); _isAbort = true; } } private void Dispose() { if (_header != null) { _header.Dispose(); _header = null; } if (_content != null) { _content.Dispose(); _content = null; } } } }