Files
JJBB/Assets/Plugins/Script/AssetUpdate/AssetUpdateDownloader.cs
2024-08-23 15:49:34 +08:00

279 lines
8.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}