JJBB/Assets/Plugins/Script/AssetUpdate/AssetUpdateDownloader.cs

279 lines
8.3 KiB
C#
Raw Normal View History

2024-08-23 15:49:34 +08:00
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
}