using System.Collections.Generic; using System.IO; using System.Threading; using Thousandto.Update.Data; using Thousandto.Update.Delegate; using Thousandto.Update.Flow; using Thousandto.Update.Log; using UnityEngine.Gonbest.MagicCube; using BaseDownloader = UnityEngine.Gonbest.MagicCube.BaseDownloader; using DownloadSpeeder = UnityEngine.Gonbest.MagicCube.DownloadSpeeder; using MD5Utils = UnityEngine.Gonbest.MagicCube.MD5Utils; namespace Thousandto.Update.Download { /// /// 资源修复时的下载,是多线程下载 /// public class RepairDownload { static string _TAG = "HttpDownload.cs "; //最大支持线程数 private const int _MAX_THREAD_COUNT = 4; //下载文件失败超过10个,则表示本次更新失败 private const int _MAX_ERROR_LIMIT = 10; //线程休眠时间 private const int _SLEEP_TIME = 1000; //每次下载失败,重试次数 private const int _RETRAY_TIMES = 3; //下载实例列表,多线程中会用到,最多会有线程个数的实例 private static List _httpInsList = new List(); //保存下载失败的文件,用于重试 private List _failDownloadFile = new List(); //总需要下载的文件数 private int _totalDownloadFileCount = 0; //当前已经下载的文件数 private int _currentDownloadFileCount = 0; private bool _abort; //线程锁 object m_locker = new object(); private HttpThreadPool _threadPool = null; public void AbortAll(AbortFinishCallback callback) { lock (m_locker) { _abort = true; if (_threadPool != null) { _threadPool.stop(); } for (int i = 0; i < _httpInsList.Count; ++i) { lock (m_locker) { _httpInsList[i].Abort(null); } } } if (callback != null) { callback(true); } } /// /// 多线程下载文件 /// /// 文件列表 /// 小于0失败 public int DownloadFileByMultiThread(List mapFileDataList) { int ret = CodeDefine.RET_FAIL; int allRetryTimes = _RETRAY_TIMES; DownloadSpeeder.Reset(); _abort = false; if (mapFileDataList == null) { ret = CodeDefine.RET_FAIL; return ret; } _failDownloadFile.Clear(); while (_failDownloadFile.Count <= _MAX_ERROR_LIMIT && allRetryTimes > 0) { _currentDownloadFileCount = 0; _failDownloadFile.Clear(); _totalDownloadFileCount = mapFileDataList.Count; _threadPool = new HttpThreadPool(_MAX_THREAD_COUNT, ThreadCallBack); for (int i = 0; i < mapFileDataList.Count; i++) { MapFileData fileData = mapFileDataList[i]; _threadPool.addTask(fileData); } //等待所有文件下载完 _threadPool.waitWhileWorking(); if (_abort) { UpdateLog.DEBUG_LOG("abort repaire download!!!"); _abort = false; return CodeDefine.RET_SKIP_BY_ABORT; } //当失败文件数小于_MAX_ERROR_LIMIT,则这些文件重新加到下载队列里面 if (_failDownloadFile.Count != 0) { for (int i = 0; i < _failDownloadFile.Count; i++) { UpdateLog.DEBUG_LOG("有文件下载失败" + _failDownloadFile[i].Name); } mapFileDataList.Clear(); mapFileDataList.AddRange(_failDownloadFile); //_failDownloadFile.Clear(); ret = CodeDefine.RET_FAIL; } else { mapFileDataList.Clear(); _failDownloadFile.Clear(); ret = CodeDefine.RET_SUCCESS; break; } allRetryTimes--; } //到这里还有文件没有下载成功,则表示下载失败了 if (mapFileDataList.Count > 0) { UpdateLog.DEBUG_LOG("更新失败,有" + mapFileDataList.Count + "个文件下载失败"); } return ret; } //线程回调方法,用来做具体的下载动作 public void ThreadCallBack(object state) { if (_abort) { return; } MapFileData fileData = (MapFileData)state; string saveFilePath = fileData.SaveDir + "/" + fileData.Dir + fileData.Name; //如果下载的文件失败个数超过10个,那么后面的文件就没有必要再下载了,需要检查网络 lock (this._failDownloadFile) { if (_failDownloadFile.Count >= _MAX_ERROR_LIMIT) { _failDownloadFile.Add(fileData); return; } } int ret = downloadMapData(fileData, saveFilePath); lock (m_locker) { _currentDownloadFileCount++; if (ret == CodeDefine.RET_SKIP_BY_ABORT) { UpdateLog.DEBUG_LOG("abort download: " + Thread.CurrentThread.Name); return; } //当前文件下载失败,放到失败列表中 if (ret <= CodeDefine.RET_FAIL) { _failDownloadFile.Add(fileData); return; } //文件下载成功后,对比md5是否正确,不正确也放到失败队列中 string downloadFileMD5 = MD5Utils.GetFileMD5(saveFilePath); if ("".Equals(downloadFileMD5) || !fileData.Md5.Equals(downloadFileMD5)) { _failDownloadFile.Add(fileData); } } } private int downloadMapData(MapFileData fileData, string saveFilePath) { int ret = CodeDefine.RET_FAIL; string tempFile = saveFilePath + ".bak"; try { lock (m_locker) { if (!Directory.Exists(fileData.SaveDir + "/" + fileData.Dir)) { Directory.CreateDirectory(fileData.SaveDir + "/" + fileData.Dir); } } //计算下载点,在资源包中,每个文件都有4个32字节的数据头加上文件名、md5长度,要跳过 long begin = fileData.Begin + 32 * 4 + fileData.DirLen + fileData.NameLen + fileData.Md5Len; //http的AddRange方法是闭包的,所以减一。([from, to]) long end = fileData.End - 1; //每个文件有3次下载机会 int i = _RETRAY_TIMES; while (i > 0) { i--; if (fileData.Name.Contains("RemoteVersion.xml") || fileData.Name.ToLower().Contains("localversion.xml")) { ret = CodeDefine.RET_SUCCESS; return ret; } using (FileStream outFile = new FileStream(tempFile, FileMode.Create)) { //UpdateLog.INFO_LOG(_TAG + " download: " + saveFilePath); ret = httpDownload(fileData.ResUrl, outFile, begin, end); outFile.Close(); if (ret >= CodeDefine.RET_SUCCESS) { RenameFile(tempFile, saveFilePath); break; } if (ret == CodeDefine.RET_SKIP_BY_ABORT) { return ret; } UpdateLog.WARN_LOG(_TAG + " try download i = " + i); Thread.Sleep(_SLEEP_TIME); } } } catch (System.Exception ex) { if (saveFilePath.Contains("ClassesResources.xml")) { ret = CodeDefine.RET_SUCCESS; } UpdateLog.ERROR_LOG(_TAG + "ThreadCallBack(object state) download fail: file= " + saveFilePath + "\n error" + ex.Message + "\n" + ex.StackTrace); UpdateLog.EXCEPTION_LOG(ex); } return ret; } private void RenameFile(string source, string target) { if (File.Exists(target)) File.Delete(target); File.Move(source, target); } private int httpDownload(string url, FileStream outFile, long begin = 0, long end = 0) { lock (m_locker) { if (_abort) { return CodeDefine.RET_SUCCESS; } } BaseDownloader downloadIns = new BaseDownloader(); addIns(downloadIns); downloadIns.ToDownload(url, outFile, begin, end); int ret = CodeDefine.FormDownloadCode(DownloadCode.RET_SUCCESS); removeIns(downloadIns); return ret; } private void addIns(BaseDownloader ins) { lock (m_locker) { _httpInsList.Add(ins); } } private void removeIns(BaseDownloader ins) { lock (m_locker) { _httpInsList.Remove(ins); } } } }