Files

306 lines
10 KiB
C#
Raw Permalink Normal View History

2025-01-25 04:38:09 +08:00
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
{
/// <summary>
/// 资源修复时的下载,是多线程下载
/// </summary>
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<BaseDownloader> _httpInsList = new List<BaseDownloader>();
//保存下载失败的文件,用于重试
private List<MapFileData> _failDownloadFile = new List<MapFileData>();
//总需要下载的文件数
private int _totalDownloadFileCount = 0;
//当前已经下载的文件数
private int _currentDownloadFileCount = 0;
private bool _abort;
//线程锁
object m_locker = new object();
private HttpThreadPool<MapFileData> _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);
}
}
/// <summary>
/// 多线程下载文件
/// </summary>
/// <param name="mapFileDataList">文件列表</param>
/// <returns>小于0失败</returns>
public int DownloadFileByMultiThread(List<MapFileData> 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<MapFileData>(_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);
}
}
}
}