|   | 
|   | 
| using System; | 
| using System.Collections; | 
| using System.Collections.Generic; | 
| using System.IO; | 
| using System.Linq; | 
| using UnityEngine; | 
| using UnityEngine.Networking; | 
| using Cysharp.Threading.Tasks; | 
|   | 
|   | 
| public class DownloadHotMgr : SingletonMonobehaviour<DownloadHotMgr> | 
| { | 
|     #region 配置 | 
|     public static int MaxRetryTimes = 5;//最大重试的次数 | 
|     public static int MaxDownLoadTask = 15;//最大同时下载数量 | 
|     public static int TimeOut = 10;//下载超时时间 | 
|     #endregion | 
|   | 
|     #region 任务列表 | 
|     private Dictionary<string, DownloadHotTask> allTask = new Dictionary<string, DownloadHotTask>();    //所有任务组 | 
|     private Queue<DownloadHotTask> waitTask = new Queue<DownloadHotTask>();    //当前等待下载的任务 | 
|     private Dictionary<string, DownloadHotTask> downloadTask = new Dictionary<string, DownloadHotTask>();    //当前正在下载的任务 | 
|     #endregion 任务列表 | 
|   | 
|     private bool isBegin; | 
|     private int startTickcount = 0; | 
|     private Func<bool, DownloadHotTask, bool> onTaskCompleted;//文件下载完成回调,<是否全部完成,任务对象,return true:立即重试,false:到队尾重试> | 
|   | 
|     //总速度 | 
|     private float speed; | 
|     public float Speed { get { UpdateValue(); return speed; } } | 
|     public string SpeedFormat { get { return StringUtility.FormatSpeed(Speed); } } | 
|   | 
|     //总已下载大小 | 
|     private long finishedBytes;//已完成任务的大小 | 
|     private long downloadedBytes; | 
|     private long downloadedBytesRef; | 
|     public long DownloadedBytes { get { UpdateValue(); return downloadedBytes; } } | 
|   | 
|     public bool IsBegin { get { return isBegin; } } | 
|     public bool IsFinished { get { return allTask.Count == 0; } } | 
|   | 
|     //更新数值 | 
|     public void UpdateValue() | 
|     { | 
|         if (startTickcount == 0)//任务还未开始 | 
|             return; | 
|         float second = Mathf.Abs(TimeUtility.AllSeconds - startTickcount); | 
|         Debug.LogFormat("更新数值:{0}  {1}  {2}", TimeUtility.AllSeconds, startTickcount, second); | 
|         if (second < 1f)//刷新间隔1秒 | 
|             return; | 
|         long bytes = 0; | 
|         long length = 0; | 
|         foreach (var pair in downloadTask) | 
|         { | 
|             var task = pair.Value; | 
|             bytes += task.downloadedBytes; | 
|             length += task.remoteLength; | 
|         } | 
|         downloadedBytes = finishedBytes + bytes; | 
|         // totalLength = length; | 
|   | 
|         var delta = downloadedBytes < downloadedBytesRef || downloadedBytesRef == 0 ? 0 : downloadedBytes - downloadedBytesRef; | 
|         downloadedBytesRef = downloadedBytes; | 
|         speed = delta / second; | 
|         startTickcount = TimeUtility.AllSeconds; | 
|     } | 
|   | 
|     //添加任务 | 
|     public DownloadHotMgr AddTask(DownloadHotTask task) | 
|     { | 
|         if (allTask.ContainsKey(task.Key)) | 
|         { | 
|             Debug.LogFormat("DownloadMgr-已存在相同的下载任务:{0}", task.Key); | 
|             return this; | 
|         } | 
|         allTask.Add(task.Key, task); | 
|         waitTask.Enqueue(task); | 
|         return this; | 
|     } | 
|   | 
|     //准备下载,重置状态 | 
|     public DownloadHotMgr Prepare() | 
|     { | 
|         Stop(); | 
|         this.onTaskCompleted = null; | 
|         //清除所有任务 | 
|         allTask.Clear(); | 
|         waitTask.Clear(); | 
|         downloadTask.Clear(); | 
|         finishedBytes = 0; | 
|         return this; | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 开始下载 | 
|     /// </summary> | 
|     /// <param name="onTaskCompleted">任务结束回调</param> | 
|     /// <returns></returns> | 
|     public DownloadHotMgr Begin(Func<bool, DownloadHotTask, bool> onTaskCompleted = null) | 
|     { | 
|         this.onTaskCompleted = onTaskCompleted; | 
|         startTickcount = TimeUtility.AllSeconds; | 
|         downloadedBytesRef = 0; | 
|         isBegin = true; | 
|         foreach (var pair in downloadTask) | 
|         { | 
|             //重新开始下载任务 | 
|             if (pair.Value.BeginDownload(OnTaskCompleted)) | 
|                 Debug.LogFormat("重新开始下载:{0}", pair.Key); | 
|         } | 
|         return this; | 
|     } | 
|   | 
|     //停止下载 | 
|     public void Stop() | 
|     { | 
|         startTickcount = 0; | 
|         isBegin = false; | 
|         foreach (var pair in downloadTask) | 
|         { | 
|             pair.Value.Stop();//停止所有下载任务 | 
|         } | 
|     } | 
|   | 
|     private void Update() | 
|     { | 
|         if (!isBegin) | 
|             return; | 
|         if (waitTask.Count == 0) | 
|             return; | 
|         if (downloadTask.Count >= MaxDownLoadTask) | 
|             return; | 
|          | 
|         //Debug.LogFormat("下载最大任务:{0}", MaxDownLoadTask); | 
|         var task = waitTask.Dequeue(); | 
|         if (task.IsDone) | 
|             return; | 
|         //开始下载 | 
|         if (!downloadTask.ContainsKey(task.Key)) | 
|             downloadTask.Add(task.Key, task); | 
|         task.BeginDownload(OnTaskCompleted); | 
|     } | 
|   | 
|     // 任务下载完成 | 
|     private void OnTaskCompleted(DownloadHotTask task) | 
|     { | 
|         downloadTask.Remove(task.Key); | 
|         if (task.IsDone)//下载成功 | 
|         { | 
|             finishedBytes += task.downloadedBytes; | 
|             allTask.Remove(task.Key); | 
|             this.onTaskCompleted?.Invoke(IsFinished, task); | 
|         } | 
|         else//下载失败 | 
|         { | 
|             var prior = this.onTaskCompleted == null ? false : this.onTaskCompleted(IsFinished, task);//返回值为是否立即重试 | 
|             //所有任务中不存在,说明已经被清除了,就不重新排队 | 
|             if (allTask.ContainsKey(task.Key)) | 
|             { | 
|                 if (prior) //不排队,直接重新开始 | 
|                 { | 
|                     downloadTask.Add(task.Key, task); | 
|                     if (isBegin) | 
|                         task.BeginDownload(OnTaskCompleted); | 
|                 } | 
|                 else | 
|                     waitTask.Enqueue(task); //重新排队 | 
|             } | 
|         } | 
|         //TODO:这里暂时用无限重复下载,不做上限处理 | 
|         // if (task.failedTimes >= MaxRetryTimes)//重试次数已达到上限,任务失败 | 
|         // task.team.OnFailed(task); | 
|         // else//重试次数未达到上限,重新加入等待队列 | 
|         // waitTask.Enqueue(task); | 
|     } | 
| } | 
|   | 
|   | 
|   | 
| public class DownloadHotTask | 
| { | 
|     //下载任务状态 | 
|     public enum TaskState | 
|     { | 
|         Wait, | 
|         Working, | 
|         Succeed, | 
|         Failed | 
|     } | 
|     public readonly string remoteUrl; //远程连接地址 | 
|     public readonly string localFile; //本地地址 | 
|     public readonly string tempFile;//临时文件地址 | 
|     private readonly bool clearCache;//是否清除旧的下载缓存,(断点续传) | 
|     public readonly object obj; //自定义对象 | 
|     private Action<DownloadHotTask> onCompleted;//下载完成回调 | 
|     private TaskState state;//下载状态 | 
|   | 
|     public bool isStop;//下载被停止 | 
|     public int failedTimes = 0;//失败的次数 | 
|     private bool requestHeaderOk = false;//是否已获取头信息 | 
|     private System.DateTime remoteLastModified; //远端文件最后修改时间 | 
|     public long remoteLength;//远端文件大小 | 
|     private bool isAcceptRange = false;//服务端是否支持断点续传 | 
|     private string errorStr;//错误信息 | 
|     public long downloadedBytes;//当前已下载数据 | 
|     public float downloadedSpeed;//当前下载速度 | 
|     private int beginTickCount; | 
|   | 
|     private UnityWebRequest fileRequest; | 
|   | 
|     public string Key { get { return remoteUrl; } } | 
|   | 
|     public string SpeedFormat { get { return StringUtility.FormatSpeed(downloadedSpeed); } } | 
|   | 
|     //本地文件大小 | 
|     public long LocalLength { get { return (File.Exists(tempFile)) ? (new FileInfo(tempFile)).Length : 0L; } } | 
|   | 
|     //本地文件最后修改时间 | 
|     private System.DateTime LocalLastModified { get { return File.Exists(tempFile) ? File.GetLastWriteTime(tempFile) : DateTime.MinValue; } } | 
|     public bool IsWorking { get { return state == TaskState.Working; } } | 
|     public bool IsDone { get { return state == TaskState.Succeed; } } | 
|   | 
|     /// <summary> | 
|     ///  | 
|     /// </summary> | 
|     /// <param name="remoteUrl">下载地址</param> | 
|     /// <param name="localUrl">本地保存地址</param> | 
|     /// <param name="obj">自定义对象</param> | 
|     /// <param name="clearCache">是否清除上次下载的缓存</param> | 
|     public DownloadHotTask(string remoteUrl, string localUrl, object obj = null, bool clearCache = false) | 
|     { | 
|         this.remoteUrl = remoteUrl; | 
|         this.localFile = localUrl; | 
|         this.tempFile = localFile + ".temp"; | 
|         this.obj = obj; | 
|         this.clearCache = clearCache; | 
|         this.state = TaskState.Wait; | 
|     } | 
|   | 
|     private bool IsOutdated //本地文件是否已过时 | 
|     { | 
|         get { | 
|             if (File.Exists(tempFile)) | 
|                 return remoteLastModified > LocalLastModified; | 
|             return false; | 
|         } | 
|     } | 
|   | 
|     private void SetSucceed() | 
|     { | 
|         state = TaskState.Succeed; | 
|     } | 
|   | 
|     private void SetFailed(string error) | 
|     { | 
|         this.state = TaskState.Failed; | 
|         this.errorStr = error; | 
|         this.failedTimes++; | 
|     } | 
|   | 
|     public void BeginGetHeader() | 
|     { | 
|         if (IsWorking) | 
|             return; | 
|   | 
|         Co_GetHeader().Forget(); | 
|         // SnxxzGame.Instance.StartCoroutine(Co_GetHeader()); | 
|     } | 
|   | 
|     public bool BeginDownload(Action<DownloadHotTask> onCompleted) | 
|     { | 
|         if ((IsWorking && requestHeaderOk) || IsDone)//已经在下载任务中或已经下载完成了 | 
|             return false; | 
|         if (state == TaskState.Wait)//第一次尝试 | 
|             failedTimes = 0; | 
|         this.onCompleted = onCompleted; | 
|         this.isStop = false; | 
|         Co_DownloadFile().Forget(); | 
|         // SnxxzGame.Instance.StartCoroutine(Co_DownloadFile()); | 
|         return true; | 
|     } | 
|   | 
|     //停止下载 | 
|     public void Stop() | 
|     { | 
|         isStop = true; | 
|         if (fileRequest != null && !fileRequest.isDone) | 
|             fileRequest.Abort(); | 
|         Debug.LogFormat("暂停任务:{0}", remoteUrl); | 
|     } | 
|   | 
|     //获取文件头信息 | 
|     private async UniTask Co_GetHeader() | 
|     { | 
|         this.state = TaskState.Working; | 
|         FileExtersion.MakeSureDirectory(tempFile); | 
|         Debug.LogFormat("开始获取头信息:{0}", remoteUrl); | 
|         using (var www = UnityWebRequest.Head(remoteUrl)) | 
|         { | 
|             www.timeout = DownloadHotMgr.TimeOut; | 
|             await www.SendWebRequest(); | 
|             if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError) | 
|             { | 
|                 Debug.LogErrorFormat("头信息获取失败:{0};error:{1}", remoteUrl, www.error); | 
|                 requestHeaderOk = false; | 
|                 SetFailed(www.error); | 
|                 www.Dispose(); | 
|                 await UniTask.CompletedTask; | 
|             } | 
|             long.TryParse(www.GetResponseHeader(HttpHeader.ContentLength), out this.remoteLength); | 
|             this.remoteLastModified = DateTime.Parse(www.GetResponseHeader(HttpHeader.LastModified)); | 
|             var acceptRange = www.GetResponseHeader(HttpHeader.AcceptRanges); | 
|             this.isAcceptRange = acceptRange != "none"; | 
|             Debug.LogFormat("头信息获取成功:{0};大小:{1};修改时间:{2};是否支持续传:{3}", remoteUrl, remoteLength, remoteLastModified, acceptRange); | 
|             requestHeaderOk = true; | 
|             state = TaskState.Wait; | 
|             www.Dispose(); | 
|         } | 
|     } | 
|     private async UniTask Co_DownloadFile() | 
|     { | 
|         state = TaskState.Working; | 
|         while (TimeUtility.AllSeconds < beginTickCount + 5)//限制重新下载的时间间隔 | 
|         { | 
|             if (isStop) | 
|                 await UniTask.CompletedTask; | 
|             else | 
|                 await UniTask.DelayFrame(1); | 
|         } | 
|         beginTickCount = TimeUtility.AllSeconds; | 
|         if (!requestHeaderOk) | 
|             await Co_GetHeader(); | 
|         if (!requestHeaderOk)//头文件获取失败 | 
|         { | 
|             onCompleted?.Invoke(this); | 
|             await UniTask.CompletedTask; | 
|         } | 
|         try | 
|         { | 
|             if (clearCache && File.Exists(tempFile)) | 
|             { | 
|                 File.Delete(localFile);//清除上次下载的临时文件 | 
|                 Debug.LogFormat("已清除缓存文件:{0}", tempFile); | 
|             } | 
|         } | 
|         catch (Exception e) | 
|         { | 
|             Debug.LogFormat("清除缓存文件失败:{0};error:{1}", tempFile, e); | 
|         } | 
|         if (File.Exists(tempFile))//临时文件存在 | 
|         { | 
|             var _localLength = LocalLength; | 
|             var _outdated = IsOutdated; | 
|             //文件完整且未过期,无须再下载了 | 
|             if (_localLength == remoteLength && !_outdated) | 
|             { | 
|                 await Move(tempFile, localFile);//把临时文件改名为正式文件 | 
|                 onCompleted?.Invoke(this); | 
|                 await UniTask.CompletedTask; | 
|             } | 
|             else if (_localLength > remoteLength || _outdated)//文件不对或过期了 | 
|             { | 
|                 if (!_outdated) Debug.LogFormat("本地文件大于服务器端文件:{0}", tempFile); | 
|                 if (_outdated) Debug.LogFormat("文件已过期:{0}", tempFile); | 
|                 try | 
|                 { | 
|                     if (File.Exists(tempFile)) | 
|                         File.Delete(tempFile); | 
|                 } | 
|                 catch (Exception e) | 
|                 { | 
|                     Debug.LogErrorFormat("删除临时文件出错:{0};error:{1}", tempFile, e); | 
|                     isAcceptRange = false;//如果删除临时文件出错,取消续传,覆写文件 | 
|                 } | 
|             } | 
|         } | 
|         if (isStop)//任务被暂停 | 
|         { | 
|             state = TaskState.Wait; | 
|             await UniTask.CompletedTask; | 
|         } | 
|         Debug.LogFormat("下载任务开始:{0}", remoteUrl); | 
|         fileRequest = UnityWebRequest.Get(remoteUrl); | 
|   | 
|         fileRequest.downloadHandler = new DownloadHandlerFile(tempFile, this.isAcceptRange); | 
|         if (LocalLength != 0L && isAcceptRange) | 
|             fileRequest.SetRequestHeader(HttpHeader.Range, "bytes=" + LocalLength + "-"); | 
|         Debug.LogFormat("下载请求范围:{0} ;bytes={1}", remoteUrl, LocalLength); | 
|         var localLength = LocalLength; | 
|         this.downloadedBytes = localLength; | 
|         fileRequest.SendWebRequest(); | 
|         while (!fileRequest.isDone) | 
|         { | 
|             var length = localLength + (long)fileRequest.downloadedBytes;//本地文件大小+本次下载的数据大小 | 
|             this.downloadedSpeed = Math.Max(0, (length - this.downloadedBytes) / Time.deltaTime); | 
|             this.downloadedBytes = length; | 
|             await UniTask.DelayFrame(1); | 
|         } | 
|         if (fileRequest.result == UnityWebRequest.Result.ConnectionError || fileRequest.result == UnityWebRequest.Result.ProtocolError) | 
|         { | 
|             if (isStop)//任务被暂停 | 
|             { | 
|                 state = TaskState.Wait; | 
|             } | 
|             else //任务失败 | 
|             { | 
|                 SetFailed(fileRequest.error); | 
|                 onCompleted?.Invoke(this); | 
|                 Debug.LogErrorFormat("文件下载失败:{0};error:{1}", remoteUrl, fileRequest.error); | 
|             } | 
|             fileRequest.Dispose(); | 
|             fileRequest = null; | 
|             await UniTask.CompletedTask; | 
|         } | 
|         Debug.LogFormat("文件下载成功:{0};", tempFile); | 
|         fileRequest.Dispose(); | 
|         fileRequest = null; | 
|   | 
|         FileInfo tempFileInfo = new FileInfo(tempFile); | 
|         if (tempFileInfo.Exists) | 
|         { | 
|             //判断临时文件和远程文件大小是否一致 | 
|             if (tempFileInfo.Length != remoteLength && remoteLength != 0L) | 
|             { | 
|                 Debug.LogErrorFormat("下载完成后但是大小不一致:{0}; {1} || {2}", remoteUrl, tempFileInfo.Length, remoteLastModified); | 
|                 SetFailed("文件下载完成后但是大小不一致"); | 
|             } | 
|             else | 
|             {  //大小一致  | 
|                 await Move(tempFile, localFile); | 
|             } | 
|         } | 
|         else | 
|         {   //临时文件不存在 | 
|             Debug.LogErrorFormat("下载完成但是临时文件不存在:{0}", tempFile); | 
|             SetFailed("下载完成但是文件不存在"); | 
|         } | 
|         onCompleted?.Invoke(this); | 
|     } | 
|   | 
|     private async UniTask Move(string sourceFile, string destFile) | 
|     { | 
|   | 
|         var copyState = 0;//复制文件状态,0等待,1成功,2失败 | 
|         try | 
|         { | 
|             if (File.Exists(destFile)) | 
|                 File.Delete(destFile); | 
|             File.Move(sourceFile, destFile); | 
|             copyState = 1; | 
|         } | 
|         catch (Exception e) | 
|         { | 
|             Debug.LogErrorFormat("拷贝文件出错:{0};error:{1}", destFile, e); | 
|             copyState = 2; | 
|         } | 
|   | 
|         if (copyState == 1) | 
|         { | 
|             Debug.LogFormat("文件复制成功:{0}", localFile); | 
|             SetSucceed(); | 
|         } | 
|         else | 
|             SetFailed("临时文件改名出错"); | 
|   | 
|         await UniTask.CompletedTask; | 
|     } | 
|   | 
| } |