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