sk-client/Assets/Scripts/Core/ResUpdate/Base/ThreadDownloader.cs

561 lines
18 KiB
C#

using System;
using System.Net;
using System.IO;
using System.Text;
using System.Threading;
using System.Collections;
using System.Net.Security;
using System.Collections.Generic;
using UnityEngine;
using GameCore;
using System.Text.RegularExpressions;
namespace ResUpdate
{
public enum DownLoadType
{
NewFile,
CacheResumeFile,
}
public enum DownloadState
{
None,
Init,
Loading,
Finished
}
/// <summary>
/// 线程下载器
/// </summary>
public class ThreadDownloader
{
/// <summary>
/// 正则表达式
/// </summary>
public static Regex httpsReg = new Regex("^https.*");
/// <summary>
/// 默认的缓冲区大小
/// </summary>
const int DEFAULT_BUFFER_SIZE = 1024 * 64; // 64k
/// <summary>
/// 超时时间
/// </summary>
const uint MAX_WAIT_TIME = 30 * 1000;
/// <summary>
/// 下载进度
/// </summary>
public DownLoadProgress progress
{
get;
private set;
}
public float speed
{
get;
private set;
}
/// <summary>
/// 下载的URL
/// </summary>
public string url
{
get;
private set;
}
/// <summary>
/// 文件保存路径
/// </summary>
public string savePath
{
get;
private set;
}
/// <summary>
/// 本地缓存文件
/// </summary>
public string fileLocalCache
{
get;
private set;
}
/// <summary>
/// 下载文件的大小
/// </summary>
public long fileSize
{
get;
private set;
}
/// <summary>
/// 下载文件的CRC
/// </summary>
public string fileCRC
{
get;
private set;
}
/// <summary>
/// 文件名
/// </summary>
public string fileName
{
get;
private set;
}
/// <summary>
/// 是否成功
/// </summary>
public bool isSuccess
{
get;
private set;
}
/// <summary>
/// 本地已经下载的大小
/// </summary>
public long localByteSize
{
get { return bytesSize + localBytes; }
}
/// <summary>
/// 当前下载的大小
/// </summary>
public long bytesSize
{
get;
private set;
}
/// <summary>
/// 下载状态
/// </summary>
public DownloadState downLoadState
{
get;
private set;
}
/// <summary>
/// 下载类型
/// </summary>
public DownLoadType downLoadType
{
get;
private set;
}
/// <summary>
/// 错误信息
/// </summary>
public string errorMsg
{
get;
private set;
}
/// <summary>
/// readBuffer
/// </summary>
byte[] readBuffer;
/// <summary>
/// 总共需要下载的大小
/// </summary>
long totalBytes;
/// <summary>
/// 本地已经下载的大小
/// </summary>
long localBytes ;
/// <summary>
/// 上下文
/// </summary>
byte[] content;
/// <summary>
/// 请求参数
/// </summary>
string postContent;
/// <summary>
/// http请求
/// </summary>
HttpWebRequest webRequest;
/// <summary>
/// http响应
/// </summary>
HttpWebResponse webResponse;
/// <summary>
///
/// </summary>
Stream responseStream;
/// <summary>
///
/// </summary>
Stream destStream;
/// <summary>
/// 完成回调
/// </summary>
Action<string,bool> finishAction;
/// <summary>
/// 进度回调
/// </summary>
Action<string, DownLoadProgress> progressAction;
/// <summary>
/// 下载线程
/// </summary>
Thread thread = null;
DateTime startTime ;
/// <summary>
/// 初始化
/// </summary>
public void Init() {
if (progress == null) progress = new DownLoadProgress();
if (readBuffer == null) readBuffer = new byte[DEFAULT_BUFFER_SIZE];
}
/// <summary>
/// 初始化参数
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="url">下载链接</param>
/// <param name="savePath">文件保存路径</param>
/// <param name="progressAction">进度回调</param>
/// <param name="finishAction">下载完成回调</param>
/// <param name="posContent">http参数</param>
public void Init(string fileName, string url, string savePath, Action<string, DownLoadProgress> progressAction = null, Action<string, bool> finishAction = null, string posContent = "")
{
this.downLoadState = DownloadState.Init;
this.downLoadType = DownLoadType.NewFile;
this.fileName = fileName;
this.url = url;
this.savePath = savePath;
this.progressAction = progressAction;
this.finishAction = finishAction;
this.postContent = posContent;
}
/// <summary>
/// 初始化参数
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="url">下载链接</param>
/// <param name="savePath">文件保存路径</param>
/// <param name="size"></param>
/// <param name="crc"></param>
/// <param name="progressAction"></param>
/// <param name="finishAction"></param>
/// <param name="posContent"></param>
public void Init(string fileName, string url, string savePath, long size, string crc, Action<string, DownLoadProgress> progressAction = null, Action<string, bool> finishAction = null, string posContent = "")
{
this.downLoadState = DownloadState.Init;
this.downLoadType = DownLoadType.CacheResumeFile;
this.url = url;
this.fileName = fileName;
this.savePath = savePath;
this.fileSize = size;
this.fileCRC = crc;
this.progressAction = progressAction;
this.finishAction = finishAction;
this.postContent = posContent;
}
/// <summary>
/// 开始多线程下载
/// </summary>
public void Start()
{
thread = new Thread(Download);
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// 开始下载
/// </summary>
void Download()
{
isSuccess = false;
downLoadState = DownloadState.Loading;
startTime = DateTime.Now;
try
{
if (!string.IsNullOrEmpty(httpsReg.Match(url).ToString()))
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ResourceDownloadManager.CheckValidationResult);
}
var stringBuilder = new StringBuilder();
var fullUrl = stringBuilder.Append(url).Append(fileName).ToString();
webRequest = (HttpWebRequest)WebRequest.Create(fullUrl);
Debug.LogError("url :" + fullUrl);
if (downLoadType == DownLoadType.CacheResumeFile)
{
stringBuilder.Length = 0;
fileLocalCache = stringBuilder.Append(savePath).Append(UpdateConfigs.TMP_SUFFIX).ToString();
localBytes = FileUtil.GetFileBytesSize(fileLocalCache);
webRequest.AddRange((int)localBytes);
}
else
{
FileUtil.DeleteFile(savePath);
}
webRequest.ProtocolVersion = HttpVersion.Version11;
if (!string.IsNullOrEmpty(postContent))
{
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
content = Encoding.UTF8.GetBytes(postContent);
webRequest.ContentLength = content.Length;
}
else
{
var ar = webRequest.BeginGetResponse(new AsyncCallback(BeginGetResponseCallback), null);
ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, new WaitOrTimerCallback(BeginGetResponseTimeout), webRequest, MAX_WAIT_TIME, true);
}
}
catch (WebException e)
{
if (webRequest != null) webRequest.Abort();
errorMsg = "web exception";
FinishDownload(false);
if (BaseLogger.isDebug) Debug.LogWarning(string.Format("BeginGetResponse web exception. message: {0}, status: {1}.", e.Message, e.Status));
}
catch (Exception e)
{
if (webRequest != null) webRequest.Abort();
errorMsg = "download exception";
FinishDownload(false);
if (BaseLogger.isDebug) Debug.LogWarning(string.Format("BeginGetResponse exception. source: {0}, message: {1}.", e.Source, e.Message));
}
}
/// <summary>
/// 获取响应超时
/// </summary>
/// <param name="state"></param>
/// <param name="isTimedOut"></param>
void BeginGetResponseTimeout(object state, bool isTimedOut)
{
if (!isTimedOut)
{
return;
}
errorMsg = "response timeout";
FinishDownload(false);
}
/// <summary>
/// 获取响应异步回调
/// </summary>
/// <param name="asynchronousResult"></param>
void BeginGetResponseCallback(IAsyncResult asynchronousResult)
{
try
{
speed = 0;
webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
totalBytes = webResponse.ContentLength;
if (fileSize != 0 && fileSize != totalBytes + localBytes) // TODO: 校验文件大小!
{
if (BaseLogger.isDebug) Debug.LogWarning(string.Format("Http file invalid File {0} size {1}, expected {2}!", fileName, totalBytes, fileSize));
errorMsg = "size verify failed";
FinishDownload(false);
return;
}
destStream = FileUtil.ForceOpenFileStream(downLoadType == DownLoadType.CacheResumeFile ? fileLocalCache : savePath);
if (destStream == null)
{
errorMsg = "open file error";
FinishDownload(false);
return;
}
if (downLoadType == DownLoadType.CacheResumeFile)
destStream.Seek(destStream.Length, SeekOrigin.Current);
responseStream = webResponse.GetResponseStream();
if (responseStream != null)
{
var ar = responseStream.BeginRead(readBuffer, 0, readBuffer.Length, ReadCallBack, null);
var signalled = ar.AsyncWaitHandle.WaitOne((int) MAX_WAIT_TIME);
BeginReadTimeOut(null, !signalled);
}
}
catch (Exception e)
{
errorMsg = "response callback exception";
FinishDownload(false);
if (BaseLogger.isDebug) Debug.LogError(string.Format("ResCallback exception. message: {0}.", e.Message));
}
}
/// <summary>
/// 读取数据回调
/// </summary>
/// <param name="asyncResult"></param>
void ReadCallBack(IAsyncResult asyncResult)
{
try
{
var bytesRead = responseStream.EndRead(asyncResult);
destStream.Write(readBuffer, 0, bytesRead);
if (bytesRead > 0)
{
bytesSize += bytesRead;
TimeSpan span = DateTime.Now - startTime;
float second = (float)span.TotalSeconds;
if (second > 0.0001)
{
speed = bytesSize / 1024 / second;
}
var ar = responseStream.BeginRead(readBuffer, 0, readBuffer.Length, ReadCallBack, null);
var signalled = ar.AsyncWaitHandle.WaitOne((int)MAX_WAIT_TIME);
BeginReadTimeOut(null, !signalled);
return;
}
else
{
destStream.Flush();
FinishDownload(bytesSize == totalBytes);
}
}
catch (Exception ex)
{
if (BaseLogger.isDebug) Debug.LogWarning(string.Format("ReadCallBack EndRead exception. message: {0}.", ex.Message));
errorMsg = "read bytes exception";
FinishDownload(false);
}
}
void BeginReadTimeOut(object state, bool isTimedOut)
{
if (!isTimedOut)
{
return;
}
errorMsg = "read timeout";
if (BaseLogger.isDebug) Debug.LogWarning(string.Format("Failed to download file {0}, BeginRead timeout!", fileName));
FinishDownload(false);
}
/// <summary>
/// 下载结束
/// </summary>
/// <param name="tmpSuccess">是否下载成功</param>
void FinishDownload(bool tmpSuccess)
{
lock (this)
{
if (downLoadState != DownloadState.Loading)
return;
if (webResponse != null) webResponse.Close();
if (responseStream != null) responseStream.Close();
if (destStream != null) destStream.Close();
var isVerifySuccess = true;
if (tmpSuccess)
{
if (downLoadType == DownLoadType.CacheResumeFile)
{
if (string.Empty != fileCRC)
{
var localCrc = FileToCRC32.GetFileCRC32(fileLocalCache);
if (localCrc == fileCRC)
{
FileUtil.MoveFile(fileLocalCache, savePath);
if (BaseLogger.isDebug) Debug.Log(string.Format("Finish download success file {0}", fileName));
}
else
{
isVerifySuccess = false;
FileUtil.DeleteFile(fileLocalCache);
errorMsg = "crc verify failed";
if (BaseLogger.isDebug) Debug.LogWarning(string.Format("Finish download crc check failed {0}", fileName));
}
}
else
{
FileUtil.MoveFile(fileLocalCache, savePath);
if (BaseLogger.isDebug) Debug.Log(string.Format("Finish download success file {0}", fileName));
}
}
}
else
{
//if (GameUpdateConfig.DEBUG) Debug.LogWarning(string.Format("Finish download failed to download file {0}", _fileName));
}
this.isSuccess = tmpSuccess && isVerifySuccess;
downLoadState = DownloadState.Finished;
}
}
/// <summary>
/// 进度更新回调
/// </summary>
public void UpdateCallBack()
{
progress.UpdateProgress(localByteSize, totalBytes,speed);
if (progressAction != null)
{
progressAction(fileName,progress);
}
}
/// <summary>
/// 下载完成回调
/// </summary>
public void FinishCallback()
{
if (finishAction != null)
finishAction(fileName,isSuccess);
}
/// <summary>
/// 清理数据
/// </summary>
public void Reset()
{
url = string.Empty;
fileName = string.Empty;
savePath = string.Empty;
fileLocalCache = string.Empty;
fileSize = 0;
fileCRC = string.Empty;
isSuccess = false;
readBuffer = null;
totalBytes = 0;
bytesSize = 0;
localBytes = 0;
content = null;
postContent = string.Empty;
webRequest = null;
webResponse = null;
responseStream = null;
destStream = null;
finishAction = null;
progressAction = null;
downLoadState = DownloadState.None;
errorMsg = string.Empty;
thread = null;
}
}
}