| 
  
 | 
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; 
 | 
    } 
 | 
  
 | 
} 
 |