279 lines
8.3 KiB
C#
279 lines
8.3 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.IO;
|
|||
|
using BestHTTP;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.Events;
|
|||
|
|
|||
|
public class AssetUpdateDownloader
|
|||
|
{
|
|||
|
public readonly string targetPath;
|
|||
|
public readonly Uri uri;
|
|||
|
private FileStream _fileStream;
|
|||
|
private HTTPRequest _request;
|
|||
|
public UnityAction<AssetUpdateDownloader> onComplete;
|
|||
|
|
|||
|
public AssetUpdateDownloader(string uriPath, string filePath)
|
|||
|
{
|
|||
|
uri = new Uri(uriPath);
|
|||
|
targetPath = filePath;
|
|||
|
}
|
|||
|
|
|||
|
public float progress { get; private set; }
|
|||
|
public AssetUpdateManager.AssetUpdateError error { get; private set; }
|
|||
|
public int finishedSize { get; private set; }
|
|||
|
public int totalSize { get; private set; }
|
|||
|
|
|||
|
public void Start(float timeOut = 10f)
|
|||
|
{
|
|||
|
if (_request == null)
|
|||
|
{
|
|||
|
_request = new HTTPRequest(uri, HTTPMethods.Get)
|
|||
|
{
|
|||
|
DisableCache = true,
|
|||
|
Timeout = TimeSpan.FromSeconds(timeOut),
|
|||
|
Callback = OnGetResponseComplete,
|
|||
|
OnDownloadProgress = OnDownloadProgress,
|
|||
|
OnStreamingData = OnStreamingData
|
|||
|
};
|
|||
|
_request.Send();
|
|||
|
AssetUpdateDownloaderTick.instance.onUpdate -= Update;
|
|||
|
AssetUpdateDownloaderTick.instance.onUpdate += Update;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DisposeInternal()
|
|||
|
{
|
|||
|
// 解除倒计时绑定
|
|||
|
AssetUpdateDownloaderTick.instance.onUpdate -= Update;
|
|||
|
if (_request != null)
|
|||
|
{
|
|||
|
// 取消Dispose可能造成的额外回调;
|
|||
|
// Dispose接口和其他造成析构的接口应该自行执行onComplete回调;
|
|||
|
_request.Callback = null;
|
|||
|
_request.OnDownloadProgress = null;
|
|||
|
_request.OnStreamingData = null;
|
|||
|
_request.Dispose();
|
|||
|
if (_request.Response != null)
|
|||
|
try
|
|||
|
{
|
|||
|
_request.Response.Dispose();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
}
|
|||
|
|
|||
|
_request = null;
|
|||
|
}
|
|||
|
|
|||
|
if (_fileStream != null)
|
|||
|
try
|
|||
|
{
|
|||
|
_fileStream.Dispose();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
_fileStream = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void Dispose()
|
|||
|
{
|
|||
|
DisposeInternal();
|
|||
|
error = AssetUpdateManager.AssetUpdateError.UserCancel;
|
|||
|
if (onComplete != null)
|
|||
|
onComplete(this);
|
|||
|
}
|
|||
|
|
|||
|
private bool OnStreamingData(HTTPRequest request, HTTPResponse response, byte[] data, int length)
|
|||
|
{
|
|||
|
// 注:返回错误仅仅由OnGetResponseComplete判定
|
|||
|
if (data != null && data.Length > 0)
|
|||
|
{
|
|||
|
// 试图创建文件流
|
|||
|
if (_fileStream == null)
|
|||
|
{
|
|||
|
EnsureMobileDir(targetPath);
|
|||
|
try
|
|||
|
{
|
|||
|
_fileStream = File.Create(targetPath);
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
error = AssetUpdateManager.AssetUpdateError.FileCreateError;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (_fileStream != null)
|
|||
|
try
|
|||
|
{
|
|||
|
_fileStream.Write(data, 0, length);
|
|||
|
finishedSize = (int) _fileStream.Length;
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
error = AssetUpdateManager.AssetUpdateError.FileCreateError;
|
|||
|
}
|
|||
|
|
|||
|
// 如果出现文件错误,就在这里断掉
|
|||
|
if (error == AssetUpdateManager.AssetUpdateError.FileCreateError)
|
|||
|
{
|
|||
|
DisposeInternal();
|
|||
|
if (onComplete != null)
|
|||
|
onComplete(this);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
private void OnDownloadProgress(HTTPRequest request, long downloaded, long downloadLength)
|
|||
|
{
|
|||
|
progress = downloadLength > 0 ? (float) downloaded / downloadLength : 0f;
|
|||
|
totalSize = (int) downloadLength;
|
|||
|
finishedSize = Mathf.Max(finishedSize, (int) downloaded);
|
|||
|
}
|
|||
|
|
|||
|
private void OnGetResponseComplete(HTTPRequest request, HTTPResponse response)
|
|||
|
{
|
|||
|
if (request != _request)
|
|||
|
{
|
|||
|
request.Dispose();
|
|||
|
if (response != null)
|
|||
|
try
|
|||
|
{
|
|||
|
response.Dispose();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (response == null)
|
|||
|
{
|
|||
|
error = AssetUpdateManager.AssetUpdateError.DownloadError;
|
|||
|
Debug.LogError(string.Format("Failed to get response from {0}!", uri));
|
|||
|
}
|
|||
|
else if (!response.IsSuccess)
|
|||
|
{
|
|||
|
error = AssetUpdateManager.AssetUpdateError.DownloadError;
|
|||
|
try
|
|||
|
{
|
|||
|
Debug.LogError(string.Format("{0}: {1} from {2}!", response.StatusCode, response.Message, uri));
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
}
|
|||
|
}
|
|||
|
// 注:下载php服务器的Json文件时,取得的totalSize会为-1,不能用于判定
|
|||
|
// 因此暂时处理为使用finishedSize判定
|
|||
|
else if (finishedSize <= 0 || totalSize > 0 && finishedSize < totalSize)
|
|||
|
{
|
|||
|
error = AssetUpdateManager.AssetUpdateError.DownloadError;
|
|||
|
Debug.LogError(string.Format("Download incomplete, downloaded {0} / total {1} from {2}!", finishedSize,
|
|||
|
totalSize, uri));
|
|||
|
}
|
|||
|
|
|||
|
if (response != null)
|
|||
|
try
|
|||
|
{
|
|||
|
response.Dispose();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Debug.LogError(e);
|
|||
|
}
|
|||
|
|
|||
|
DisposeInternal();
|
|||
|
if (onComplete != null)
|
|||
|
onComplete(this);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 注:某些手机平台不支持一次建立多重路径,因此使用这个流程
|
|||
|
public static void EnsureMobileDir(string path)
|
|||
|
{
|
|||
|
var dir = Path.GetDirectoryName(path);
|
|||
|
if (!Directory.Exists(dir))
|
|||
|
{
|
|||
|
dir = dir.ToUrl();
|
|||
|
var index = dir.LastIndexOf('/');
|
|||
|
var segments = new List<string>();
|
|||
|
while (index > 0)
|
|||
|
{
|
|||
|
segments.Add(dir.Substring(index + 1));
|
|||
|
dir = dir.Remove(index);
|
|||
|
if (Directory.Exists(dir))
|
|||
|
break;
|
|||
|
index = dir.LastIndexOf('/');
|
|||
|
}
|
|||
|
|
|||
|
for (var i = segments.Count - 1; i >= 0; i--)
|
|||
|
{
|
|||
|
dir = dir.Open(segments[i]);
|
|||
|
Directory.CreateDirectory(dir);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 某些手机网络环境下,会出现收不到GetResponseComplete,但是下载又没有速度的情况
|
|||
|
// 因此使用这个监控来执行主动断开
|
|||
|
|
|||
|
#region 下载速度监控
|
|||
|
|
|||
|
// 最低应有速度
|
|||
|
private const int minDownloadSpeed = 1024;
|
|||
|
|
|||
|
// 低于最低速度时,超过这个时间将会导致断开
|
|||
|
private const int minSpeedTolerance = 15;
|
|||
|
private float _lastTick;
|
|||
|
private int _lastBytes;
|
|||
|
private int _failTime;
|
|||
|
|
|||
|
private void Update()
|
|||
|
{
|
|||
|
if (_lastTick > 0f)
|
|||
|
{
|
|||
|
if (_lastTick + 1 < Time.realtimeSinceStartup)
|
|||
|
{
|
|||
|
_lastTick = Time.realtimeSinceStartup;
|
|||
|
var delta = finishedSize - _lastBytes;
|
|||
|
_lastBytes = finishedSize;
|
|||
|
if (delta < minDownloadSpeed)
|
|||
|
{
|
|||
|
_failTime++;
|
|||
|
if (_failTime >= minSpeedTolerance)
|
|||
|
ForceDisconnect();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_failTime = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_lastTick = Time.realtimeSinceStartup;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ForceDisconnect()
|
|||
|
{
|
|||
|
DisposeInternal();
|
|||
|
error = AssetUpdateManager.AssetUpdateError.DownloadError;
|
|||
|
if (onComplete != null)
|
|||
|
onComplete(this);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|