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 DownloadMgr : SingletonMonobehaviour { #region 配置 public static int MaxRetryTimes = 5;//最大重试的次数 public static int MaxDownLoadTask = 15;//最大同时下载数量 public static int TimeOut = 10;//下载超时时间 #endregion #region 任务列表 private Dictionary allTask = new Dictionary(); //所有任务组 private Queue waitTask = new Queue(); //当前等待下载的任务 private Dictionary downloadTask = new Dictionary(); //当前正在下载的任务 #endregion 任务列表 private bool isBegin; private int startTickcount = 0; private Func 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 DownloadMgr AddTask(DownloadTask 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 DownloadMgr Prepare() { Stop(); this.onTaskCompleted = null; //清除所有任务 allTask.Clear(); waitTask.Clear(); downloadTask.Clear(); finishedBytes = 0; return this; } /// /// 开始下载 /// /// 任务结束回调 /// public DownloadMgr Begin(Func 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(DownloadTask 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 DownloadTask { //下载任务状态 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 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; } } /// /// /// /// 下载地址 /// 本地保存地址 /// 自定义对象 /// 是否清除上次下载的缓存 public DownloadTask(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(); // SnxxzGame.Instance.StartCoroutine(Co_GetHeader()); } public bool BeginDownload(Action onCompleted) { if ((IsWorking && requestHeaderOk) || IsDone)//已经在下载任务中或已经下载完成了 return false; if (state == TaskState.Wait)//第一次尝试 failedTimes = 0; this.onCompleted = onCompleted; this.isStop = false; Co_DownloadFile(); // 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 = DownloadMgr.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("临时文件改名出错"); } }