//#define UseWebClient
|
|
using UnityEngine;
|
using System.Collections;
|
using System.Net;
|
using System.IO;
|
using System.ComponentModel;
|
using System;
|
using System.Threading;
|
|
public class RemoteFile
|
{
|
static bool m_ProcessErroring = false;
|
public static bool processErroring
|
{
|
get { return m_ProcessErroring; }
|
}
|
|
public static int maxDownLoadTask = 48;
|
public static int MaxConnectLimit = 48;
|
static int gDownloadIsRunningCount;
|
public static int DownloadIsRunningCount
|
{
|
get { return gDownloadIsRunningCount; }
|
}
|
|
public static int gStartTickcount = 0;
|
static long gTotalDownloadSize = 0L; //已下载的字节数
|
static object lockObj = new object();
|
public static long TotalDownloadedSize
|
{
|
get
|
{
|
return System.Threading.Interlocked.Read(ref gTotalDownloadSize);
|
}
|
set
|
{
|
gTotalDownloadSize = value;
|
}
|
}
|
|
static float downloadSpeedRef = 0f; //字节/秒
|
static long downloadSizeRef = 0L;
|
|
public static string DownloadSpeed
|
{
|
get
|
{
|
float speed = downloadSpeedRef;
|
if (RemoteFile.gStartTickcount != 0)
|
{
|
float second = Mathf.Abs(System.Environment.TickCount - RemoteFile.gStartTickcount) / 1000f;
|
if (second > 1f || (downloadSpeedRef <= 0.1f && TotalDownloadedSize > 0))
|
{
|
if (second > 0f)
|
{
|
var delta = TotalDownloadedSize - downloadSizeRef;
|
downloadSizeRef = TotalDownloadedSize;
|
|
speed = (delta / second + downloadSpeedRef) * 0.5f;
|
downloadSpeedRef = speed;
|
RemoteFile.gStartTickcount = System.Environment.TickCount;
|
}
|
}
|
}
|
|
if (speed > 1048576f)
|
{
|
return StringUtility.Contact((speed / 1048576f).ToString("f1"), " M/S");
|
}
|
else if (speed > 1024f)
|
{
|
return StringUtility.Contact((speed / 1024f).ToString("f1"), " KB/S");
|
}
|
else
|
{
|
return StringUtility.Contact(speed.ToString("f1"), " B/S");
|
}
|
}
|
}
|
|
public AssetVersion assetVersion { get; private set; }
|
protected string mRemoteFile;
|
protected string localFile;
|
bool speedLimit = false;
|
|
protected string mLocalFileTemp; //临时文件
|
|
protected System.DateTime mRemoteLastModified;
|
protected System.DateTime mLocalLastModified;
|
protected long mRemoteFileSize = 0;
|
public static long TotalRemoteFileSize = 0L;
|
|
const int bufferSize = 8192;
|
byte[] buff;
|
|
HttpWebRequest headRequest;
|
HttpWebResponse headResponse;
|
HttpWebRequest fileRequest = null;
|
HttpWebResponse fileResponse = null;
|
|
Action<bool, AssetVersion> onCompleted;
|
|
protected bool mHadError = false;
|
public bool HaveError
|
{
|
get { return mHadError; }
|
}
|
|
bool m_Done = false;
|
public bool done
|
{
|
get { return m_Done; }
|
private set
|
{
|
m_Done = value;
|
if (value)
|
{
|
state = State.Stoped;
|
if (onCompleted != null)
|
{
|
onCompleted(!HaveError, assetVersion);
|
onCompleted = null;
|
}
|
}
|
}
|
}
|
|
State state = State.Wait;
|
|
int read_Stream_startTickcount = 0; //请求操作开始,用于超时判断
|
int timeOut = 5000; //超时时间
|
|
public static void Prepare()
|
{
|
gDownloadIsRunningCount = 0;
|
gStartTickcount = System.Environment.TickCount;
|
TotalDownloadedSize = 0L;
|
downloadSpeedRef = 0f;
|
downloadSizeRef = 0L;
|
}
|
|
public void Init(string remoteFile, string _localFile, AssetVersion _assetVersion, bool _speedLimit = false)
|
{
|
mRemoteFile = remoteFile;
|
localFile = _localFile;
|
assetVersion = _assetVersion;
|
this.speedLimit = _speedLimit;
|
}
|
|
public void Begin(Action<bool, AssetVersion> _onCompleted)
|
{
|
onCompleted = _onCompleted;
|
SnxxzGame.Instance.StartCoroutine(Co_DownloadRemoteFile());
|
}
|
|
bool stop = false;
|
|
public void Stop()
|
{
|
if (stop)
|
{
|
return;
|
}
|
|
stop = true;
|
try
|
{
|
if (headRequest != null)
|
{
|
headRequest.Abort();
|
headRequest = null;
|
}
|
|
if (headResponse != null)
|
{
|
headResponse.Close();
|
headResponse = null;
|
}
|
|
if (fileRequest != null)
|
{
|
fileRequest.Abort();
|
fileRequest = null;
|
}
|
|
if (fileResponse != null)
|
{
|
fileResponse.Close();
|
fileResponse = null;
|
}
|
|
if (fs != null)
|
{
|
fs.Flush();
|
fs.Close();
|
fs = null;
|
}
|
|
if (inStream != null)
|
{
|
inStream.Close();
|
inStream = null;
|
}
|
}
|
catch (Exception ex)
|
{
|
Debug.Log(ex);
|
}
|
finally
|
{
|
onCompleted = null;
|
mHadError = false;
|
if (state == State.Working)
|
{
|
gDownloadIsRunningCount--;
|
}
|
}
|
}
|
|
private void OnDispose()
|
{
|
|
}
|
|
void MakeSureDirectory(string filePath)
|
{
|
string dir = Path.GetDirectoryName(filePath);
|
if (!Directory.Exists(dir))
|
{
|
Directory.CreateDirectory(dir);
|
}
|
}
|
|
bool Move(string sourceFile, string destFile)
|
{
|
bool ret = true;
|
#if !UNITY_WEBPLAYER
|
try
|
{
|
if (speedLimit)
|
{
|
ThreadPool.QueueUserWorkItem((object _obj) =>
|
{
|
File.Move(sourceFile, destFile);
|
});
|
}
|
else
|
{
|
File.Move(sourceFile, destFile);
|
}
|
}
|
catch (Exception ex)
|
{
|
DebugEx.LogError(ex.Message);
|
ret = false;
|
}
|
#endif
|
return ret;
|
}
|
|
IEnumerator Co_DownloadRemoteFile()
|
{
|
done = false;
|
|
while (gDownloadIsRunningCount >= maxDownLoadTask)
|
{
|
if (stop)
|
{
|
yield break;
|
}
|
//超过最大任务数时,先等待
|
yield return null;
|
}
|
|
while (assetVersion != null && assetVersion.extersion == ".manifest" && !AssetVersionUtility.GetAssetVersion(assetVersion.relativePath.Replace(".manifest", "")).localValid)
|
{
|
if (stop)
|
{
|
yield break;
|
}
|
yield return null;
|
}
|
|
state = State.Working;
|
|
gDownloadIsRunningCount++;
|
mHadError = false;
|
fileWriteState = FileWriteState.None;
|
mLocalFileTemp = localFile + ".tmp"; //先下载为临时文件
|
|
MakeSureDirectory(mLocalFileTemp); //确保文件写入目录存在
|
mLocalLastModified = DateTime.MinValue;
|
long localFileSize = 0L;
|
#if !UNITY_WEBPLAYER
|
mLocalLastModified = File.GetLastWriteTime(mLocalFileTemp);
|
#endif
|
headRequest = (HttpWebRequest)System.Net.WebRequest.Create(mRemoteFile);
|
if (headRequest.ServicePoint.ConnectionLimit < RemoteFile.MaxConnectLimit)
|
{
|
headRequest.ServicePoint.ConnectionLimit = RemoteFile.MaxConnectLimit;
|
}
|
headRequest.Method = "HEAD"; // Only the header info, not full file!
|
headRequest.ServicePoint.Expect100Continue = false;
|
headRequest.Timeout = 3000;
|
headRequest.Proxy = null;
|
headRequest.KeepAlive = false;
|
bool isAcceptRange = true;
|
bool headRequestOk = false; //是否支持断点续传
|
|
int tick1 = 0;
|
try
|
{
|
headRequest.BeginGetResponse( //改为异步的方法
|
(x) =>
|
{
|
try
|
{
|
headResponse = (x.AsyncState as HttpWebRequest).EndGetResponse(x) as HttpWebResponse;
|
mRemoteLastModified = headResponse.LastModified;
|
mRemoteFileSize = headResponse.ContentLength;
|
if (headResponse.Headers["Accept-Ranges"] != null)
|
{
|
string s = headResponse.Headers["Accept-Ranges"];
|
if (s == "none")
|
{
|
isAcceptRange = false;
|
}
|
}
|
System.Threading.Interlocked.Add(ref RemoteFile.TotalRemoteFileSize, mRemoteFileSize);
|
headRequestOk = true;
|
}
|
catch (Exception ex)
|
{
|
DebugEx.LogWarning("ERROR: " + ex);
|
mHadError = true;
|
}
|
finally
|
{
|
if (headResponse != null)
|
{
|
headResponse.Close();
|
headResponse = null;
|
}
|
if (headRequest != null)
|
{
|
headRequest.Abort();
|
headRequest = null;
|
}
|
}
|
|
}, headRequest);
|
tick1 = System.Environment.TickCount;
|
}
|
catch (WebException webEx)
|
{
|
DebugEx.LogWarning("<color=red>Request File Head ERROR: " + mRemoteFile + "</color>");
|
DebugEx.LogWarning("ERROR: " + webEx);
|
mHadError = true;
|
gDownloadIsRunningCount--;
|
done = true;
|
yield break;
|
}
|
catch (System.Exception e)
|
{
|
DebugEx.LogWarning("<color=red>Request File Head ERROR: " + mRemoteFile + "</color>");
|
DebugEx.LogWarning("ERROR: " + e);
|
mHadError = true;
|
gDownloadIsRunningCount--;
|
done = true;
|
yield break;
|
}
|
|
while (!headRequestOk && !mHadError)
|
{
|
if (stop)
|
{
|
yield break;
|
}
|
|
if (processErroring)
|
{
|
mHadError = true;
|
break;
|
}
|
float dur = System.Environment.TickCount - tick1;
|
if (dur > timeOut)
|
{
|
DebugEx.LogWarningFormat("获取远程文件{0} 信息超时!", mRemoteFile);
|
mHadError = true;
|
break;
|
}
|
yield return null;
|
}
|
if (mHadError)
|
{
|
DebugEx.LogWarningFormat("获取远程文件{0} 信息失败!", mRemoteFile);
|
if (headRequest != null)
|
{
|
headRequest.Abort();
|
headRequest = null;
|
}
|
|
done = true;
|
gDownloadIsRunningCount--;
|
yield break;
|
}
|
|
//判断是否有已经下载部分的临时文件
|
if (File.Exists(mLocalFileTemp))
|
{ // This will not work in web player!
|
//判断是否断点续传, 依据临时文件是否存在,以及修改时间是否小于服务器文件时间
|
#if !UNITY_WEBPLAYER
|
localFileSize = (File.Exists(mLocalFileTemp)) ? (new FileInfo(mLocalFileTemp)).Length : 0L;
|
#endif
|
bool outDated = IsOutdated;
|
if (localFileSize == mRemoteFileSize && !outDated)
|
{
|
gDownloadIsRunningCount--;
|
mHadError = !Move(mLocalFileTemp, localFile);//把临时文件改名为正式文件
|
done = true;
|
yield break; // We already have the file, early out
|
}
|
else if (localFileSize > mRemoteFileSize || outDated)
|
{
|
if (!outDated) DebugEx.LogWarning("Local file is larger than remote file, but not outdated. PANIC!");
|
if (outDated)
|
{
|
DebugEx.LogWarning(mLocalFileTemp + " Local file is outdated, deleting");
|
}
|
try
|
{
|
if (File.Exists(mLocalFileTemp))
|
File.Delete(mLocalFileTemp);
|
}
|
catch (System.Exception e)
|
{
|
DebugEx.LogWarning("<color=red>Could not delete local file</color>");
|
DebugEx.LogError(e);
|
}
|
|
while (File.Exists(mLocalFileTemp))
|
{
|
if (stop)
|
{
|
yield break;
|
}
|
yield return null;
|
}
|
|
localFileSize = 0;
|
}
|
}
|
|
if (mHadError)
|
{
|
gDownloadIsRunningCount--;
|
done = true;
|
yield break;
|
}
|
|
if (File.Exists(localFile))
|
{
|
try
|
{
|
if (File.Exists(localFile))
|
File.Delete(localFile);
|
}
|
catch (System.Exception e)
|
{
|
DebugEx.LogWarning("<color=red>Could not delete local file</color>");
|
DebugEx.LogWarning(e);
|
}
|
while (File.Exists(localFile))
|
{
|
if (stop)
|
{
|
yield break;
|
}
|
yield return null;
|
}
|
}
|
|
#if UseWebClient //|| UNITY_IOS
|
using (WebClient client = new WebClient()) {
|
client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
|
client.DownloadFileAsync(new Uri(mRemoteFile), mLocalFileTemp);
|
}
|
|
while (!done) {
|
yield return null;
|
}
|
mHadError = !Move(mLocalFileTemp, localFile);//把临时文件改名为正式文件
|
#else
|
try
|
{
|
fileRequest = (HttpWebRequest)HttpWebRequest.Create(mRemoteFile);
|
if (fileRequest.ServicePoint.ConnectionLimit < RemoteFile.MaxConnectLimit)
|
{
|
fileRequest.ServicePoint.ConnectionLimit = RemoteFile.MaxConnectLimit;
|
}
|
fileRequest.ServicePoint.Expect100Continue = false;
|
fileRequest.Timeout = 3000;
|
fileRequest.Proxy = null;
|
fileRequest.KeepAlive = false;
|
|
if (localFileSize != 0L && isAcceptRange)
|
{
|
fileRequest.AddRange((int)localFileSize, (int)mRemoteFileSize - 1);
|
}
|
#if !UNITY_WEBPLAYER
|
fileRequest.Method = WebRequestMethods.Http.Get;
|
#endif
|
fileRequest.BeginGetResponse(AsynchCallback, fileRequest);
|
tick1 = System.Environment.TickCount;
|
}
|
catch (System.Exception ex)
|
{
|
DebugEx.LogWarning("BeginGetResponse exception: " + ex.Message);
|
DebugEx.LogWarning(ex);
|
if (fileRequest != null)
|
{
|
fileRequest.Abort();
|
fileRequest = null;
|
}
|
mHadError = true;
|
done = true;
|
gDownloadIsRunningCount--;
|
yield break;
|
}
|
|
while (fileResponse == null && !mHadError)
|
{ // Wait for asynch to finish
|
|
if (stop)
|
{
|
yield break;
|
}
|
|
if (processErroring)
|
{
|
mHadError = true;
|
break;
|
}
|
float dur = System.Environment.TickCount - tick1;
|
if (dur > timeOut)
|
{
|
DebugEx.LogWarningFormat("下载远程文件{0} 超时!", mRemoteFile);
|
mHadError = true;
|
break;
|
}
|
yield return null;
|
}
|
|
if (mHadError)
|
{
|
DebugEx.LogWarningFormat("[RemoteFile] 远程文件{0} 下载失败! ", localFile);
|
if (fileRequest != null)
|
{
|
fileRequest.Abort();
|
fileRequest = null;
|
}
|
if (fileResponse != null)
|
{
|
fileResponse.Close();
|
fileResponse = null;
|
}
|
done = true;
|
gDownloadIsRunningCount--;
|
yield break;
|
}
|
|
try
|
{
|
inStream = fileResponse.GetResponseStream();
|
fs = new FileStream(mLocalFileTemp, (localFileSize > 0) ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
if (buff == null)
|
{
|
buff = new byte[bufferSize];
|
}
|
fileWriteState = FileWriteState.Writting;
|
inStream.BeginRead(buff, 0, bufferSize, ReadDataCallback, null);
|
read_Stream_startTickcount = System.Environment.TickCount;
|
}
|
catch (Exception ex)
|
{
|
DebugEx.LogWarning("<color=red>ERROR: " + mRemoteFile + "</color>");
|
DebugEx.LogWarning(ex);
|
if (inStream != null)
|
{
|
inStream.Close();
|
inStream = null;
|
}
|
if (fs != null)
|
{
|
fs.Close();
|
fs = null;
|
}
|
if (fileResponse != null)
|
{
|
fileResponse.Close();
|
fileResponse = null;
|
}
|
mHadError = true;
|
fileWriteState = FileWriteState.Error;
|
}
|
|
while (fileWriteState == FileWriteState.Writting)
|
{
|
if (stop)
|
{
|
yield break;
|
}
|
|
if (processErroring)
|
{
|
fileWriteState = FileWriteState.Error;
|
break;
|
}
|
if (downloadSpeedRef == 0)
|
{
|
int dura = System.Environment.TickCount - read_Stream_startTickcount;
|
if (dura > timeOut)
|
{
|
fileWriteState = FileWriteState.Timeout;
|
DebugEx.LogWarningFormat("[RemoteFile] 远程文件{0} 读取超时{1}!", mRemoteFile, dura);
|
break;
|
}
|
}
|
|
yield return null;
|
}
|
|
if (fileRequest != null)
|
{
|
fileRequest.Abort();
|
fileRequest = null;
|
}
|
if (fileWriteState == FileWriteState.Error || fileWriteState == FileWriteState.Timeout)
|
{
|
DebugEx.LogWarningFormat("[RemoteFile] 远程文件{0} 下载失败! ", localFile);
|
if (fileResponse != null)
|
{
|
fileResponse.Close();
|
fileResponse = null;
|
}
|
|
gDownloadIsRunningCount--;
|
mHadError = true;
|
done = true;
|
yield break;
|
}
|
|
try
|
{
|
FileInfo localTempFileInfo = new FileInfo(mLocalFileTemp);
|
if (localTempFileInfo.Exists)
|
{ //临时文件存在,需要判断大小是否一致
|
//判断临时文件和远程文件size是否一致
|
if (localTempFileInfo.Length != mRemoteFileSize && mRemoteFileSize != 0L)
|
{
|
mHadError = true;
|
DebugEx.LogError(string.Format(localFile + " 下载完成后, 但是大小{0} 和远程文件不一致 {1}", localTempFileInfo.Length, mRemoteFileSize));
|
}
|
else
|
{ //大小一致
|
mHadError = !Move(mLocalFileTemp, localFile);//把临时文件改名为正式文件
|
}
|
}
|
else
|
{ //临时文件不存在
|
mHadError = true;
|
}
|
#endif
|
}
|
catch (Exception ex)
|
{
|
DebugEx.LogError(ex);
|
mHadError = true;
|
}
|
|
yield return null;
|
gDownloadIsRunningCount--;
|
done = true;
|
}
|
|
bool IsOutdated
|
{
|
get
|
{
|
if (File.Exists(mLocalFileTemp))
|
return mRemoteLastModified > mLocalLastModified;
|
return false;
|
}
|
}
|
|
enum FileWriteState
|
{
|
None,
|
Writting,
|
Completed,
|
Error,
|
Timeout,
|
}
|
|
Stream inStream;
|
FileStream fs;
|
FileWriteState fileWriteState = FileWriteState.None; //下载文件写入状态
|
|
void ReadDataCallback(IAsyncResult ar)
|
{
|
if (stop)
|
{
|
return;
|
}
|
|
try
|
{
|
if (inStream != null)
|
{
|
int read = inStream.EndRead(ar);
|
lock (lockObj)
|
{
|
gTotalDownloadSize += read;
|
}
|
|
if (read > 0)
|
{
|
fs.BeginWrite(buff, 0, read, WriteDataCallBack, fs);
|
}
|
else
|
{
|
if (fs != null)
|
{
|
fs.Close();
|
fs = null;
|
}
|
|
if (inStream != null)
|
{
|
inStream.Close();
|
inStream = null;
|
}
|
|
if (fileResponse != null)
|
{
|
fileResponse.Close();
|
fileResponse = null;
|
}
|
|
fileWriteState = FileWriteState.Completed;
|
}
|
}
|
}
|
catch (Exception ex)
|
{
|
if (!processErroring)
|
{
|
DebugEx.LogWarning(ex);
|
DebugEx.LogWarning("ReadDataCallback 异常信息: " + ex.Message);
|
}
|
if (fs != null)
|
{
|
fs.Close();
|
fs = null;
|
}
|
if (inStream != null)
|
{
|
inStream.Close();
|
inStream = null;
|
}
|
fileWriteState = FileWriteState.Error;
|
}
|
}
|
|
void WriteDataCallBack(IAsyncResult _asyncResult)
|
{
|
fs.Flush();
|
inStream.BeginRead(buff, 0, bufferSize, new AsyncCallback(ReadDataCallback), null);
|
read_Stream_startTickcount = System.Environment.TickCount;
|
}
|
|
#if UseWebClient //|| UNITY_IOS
|
protected void DownloadCompleted(System.Object sender, AsyncCompletedEventArgs e) {
|
done = true;
|
}
|
#else
|
// Throwind an exception here will not propogate to unity!
|
protected void AsynchCallback(IAsyncResult result)
|
{
|
try
|
{
|
if (result == null)
|
{
|
DebugEx.LogError("Asynch result is null!");
|
mHadError = true;
|
}
|
|
HttpWebRequest webRequest = (HttpWebRequest)result.AsyncState;
|
if (webRequest == null)
|
{
|
DebugEx.LogError("Could not cast to web request");
|
mHadError = true;
|
}
|
|
fileResponse = webRequest.EndGetResponse(result) as HttpWebResponse;
|
if (fileResponse == null)
|
{
|
DebugEx.LogError("Asynch response is null!");
|
mHadError = true;
|
}
|
}
|
catch (Exception ex)
|
{
|
mHadError = true;
|
DebugEx.LogWarning(ex);
|
DebugEx.LogWarning("[RemoteFile] AsynchCallback 异常: " + ex.Message);
|
}
|
}
|
#endif
|
|
public enum State
|
{
|
Wait,
|
Working,
|
Stoped,
|
}
|
}
|
|