123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Net;
- using System.Net.Security;
- using System.Security.Cryptography.X509Certificates;
- using System.Threading;
- using UnityEngine;
- namespace VEngine
- {
- public enum DownloadStatus
- {
- Wait,
- Progressing,
- Success,
- Failed
- }
- public class DownloadInfo
- {
- public uint crc;
- public string savePath;
- public long size;
- public string url;
- }
- public class Download : CustomYieldInstruction
- {
- public static uint MaxDownloads = 10;
- public static uint ReadBufferSize = 1042 * 4;
- public static readonly List<Download> Prepared = new List<Download>();
- public static readonly List<Download> Progressing = new List<Download>();
- public static readonly Dictionary<string, Download> Cache = new Dictionary<string, Download>();
- private static float lastSampleTime;
- private static long lastTotalDownloadedBytes;
- private readonly byte[] _readBuffer = new byte[ReadBufferSize];
- private Thread _thread;
- private FileStream writer;
- private Download()
- {
- status = DownloadStatus.Wait;
- downloadedBytes = 0;
- }
- public DownloadInfo info { get; private set; }
- public DownloadStatus status { get; private set; }
- public string error { get; private set; }
- public Action<Download> completed { get; set; }
- public Action<Download> updated { get; set; }
- public bool isDone => status == DownloadStatus.Failed || status == DownloadStatus.Success;
- public float progress => downloadedBytes * 1f / info.size;
- public long downloadedBytes { get; private set; }
- public override bool keepWaiting => !isDone;
- public static bool Working => Progressing.Count > 0;
- public static long TotalDownloadedBytes
- {
- get
- {
- var value = 0L;
- foreach (var item in Cache) value += item.Value.downloadedBytes;
- return value;
- }
- }
- public static long TotalSize
- {
- get
- {
- var value = 0L;
- foreach (var item in Cache) value += item.Value.info.size;
- return value;
- }
- }
- public static long TotalBandwidth { get; private set; }
- public static void ClearAllDownloads()
- {
- foreach (var download in Progressing) download.Cancel();
- Prepared.Clear();
- Progressing.Clear();
- Cache.Clear();
- }
- public static Download DownloadAsync(string url, string savePath, Action<Download> completed = null,
- long size = 0, uint crc = 0)
- {
- return DownloadAsync(new DownloadInfo
- {
- url = url,
- savePath = savePath,
- crc = crc,
- size = size
- }, completed);
- }
- public static Download DownloadAsync(DownloadInfo info, Action<Download> completed = null)
- {
- Download download;
- if (!Cache.TryGetValue(info.url, out download))
- {
- download = new Download
- {
- info = info
- };
- Prepared.Add(download);
- Cache.Add(info.url, download);
- }
- else
- {
- Logger.W("Download url {0} already exist.", info.url);
- }
- if (completed != null) download.completed += completed;
- return download;
- }
- public static void UpdateAll()
- {
- if (Prepared.Count > 0)
- for (var index = 0; index < Mathf.Min(Prepared.Count, MaxDownloads - Progressing.Count); index++)
- {
- var download = Prepared[index];
- Prepared.RemoveAt(index);
- index--;
- Progressing.Add(download);
- download.Start();
- }
- if (Progressing.Count > 0)
- {
- for (var index = 0; index < Progressing.Count; index++)
- {
- var download = Progressing[index];
- if (download.updated != null) download.updated(download);
- if (download.isDone)
- {
- if (download.status == DownloadStatus.Failed)
- Logger.E("Unable to download {0} with error {1}", download.info.url, download.error);
- else
- Logger.I("Success to download {0}", download.info.url);
- Progressing.RemoveAt(index);
- index--;
- download.Complete();
- }
- }
- if (Time.realtimeSinceStartup - lastSampleTime >= 1)
- {
- TotalBandwidth = TotalDownloadedBytes - lastTotalDownloadedBytes;
- lastTotalDownloadedBytes = TotalDownloadedBytes;
- lastSampleTime = Time.realtimeSinceStartup;
- }
- }
- else
- {
- if (Cache.Count <= 0) return;
- Cache.Clear();
- lastTotalDownloadedBytes = 0;
- lastSampleTime = Time.realtimeSinceStartup;
- }
- }
- public void Retry()
- {
- status = DownloadStatus.Wait;
- Start();
- }
- public void UnPause()
- {
- Retry();
- }
- public void Pause()
- {
- status = DownloadStatus.Wait;
- }
- public void Cancel()
- {
- error = "User Cancel.";
- status = DownloadStatus.Failed;
- }
- private void Complete()
- {
- if (completed != null)
- {
- completed.Invoke(this);
- completed = null;
- }
- }
- private void Run()
- {
- try
- {
- Downloading();
- CloseWrite();
- if (status == DownloadStatus.Failed) return;
- if (downloadedBytes != info.size)
- {
- error = $"Download lenght {downloadedBytes} mismatch to {info.size}";
- status = DownloadStatus.Failed;
- return;
- }
- if (info.crc != 0)
- {
- var crc = Utility.ComputeCRC32(info.savePath);
- if (info.crc != crc)
- {
- File.Delete(info.savePath);
- error = $"Download crc {crc} mismatch to {info.crc}";
- status = DownloadStatus.Failed;
- return;
- }
- }
- status = DownloadStatus.Success;
- }
- catch (Exception e)
- {
- CloseWrite();
- error = e.Message;
- status = DownloadStatus.Failed;
- }
- }
- private void CloseWrite()
- {
- if (writer != null)
- {
- writer.Flush();
- writer.Close();
- }
- }
- private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain,
- SslPolicyErrors spe)
- {
- return true;
- }
- private void Downloading()
- {
- var request = CreateWebRequest();
- using (var response = request.GetResponse())
- {
- if (response.ContentLength > 0)
- {
- if (info.size == 0) info.size = response.ContentLength + downloadedBytes;
- using (var reader = response.GetResponseStream())
- {
- if (downloadedBytes < info.size)
- while (status == DownloadStatus.Progressing)
- if (ReadToEnd(reader))
- break;
- }
- }
- else
- {
- status = DownloadStatus.Success;
- }
- }
- }
- private WebRequest CreateWebRequest()
- {
- WebRequest request;
- if (info.url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
- {
- ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;
- request = GetHttpWebRequest();
- }
- else
- {
- request = GetHttpWebRequest();
- }
- return request;
- }
- private WebRequest GetHttpWebRequest()
- {
- var httpWebRequest = (HttpWebRequest) WebRequest.Create(info.url);
- httpWebRequest.ProtocolVersion = HttpVersion.Version10;
- if (downloadedBytes > 0) httpWebRequest.AddRange(downloadedBytes);
- return httpWebRequest;
- }
- private bool ReadToEnd(Stream reader)
- {
- var len = reader.Read(_readBuffer, 0, _readBuffer.Length);
- if (len > 0)
- {
- writer.Write(_readBuffer, 0, len);
- downloadedBytes += len;
- return false;
- }
- return true;
- }
- private void Start()
- {
- if (status != DownloadStatus.Wait) return;
- Logger.I("Start download {0}", info.url);
- status = DownloadStatus.Progressing;
- var file = new FileInfo(info.savePath);
- if (file.Exists && file.Length > 0)
- {
- if (info.size > 0 && file.Length == info.size)
- {
- status = DownloadStatus.Success;
- return;
- }
- writer = File.OpenWrite(info.savePath);
- downloadedBytes = writer.Length - 1;
- if (downloadedBytes > 0) writer.Seek(-1, SeekOrigin.End);
- }
- else
- {
- var dir = Path.GetDirectoryName(info.savePath);
- if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
- writer = File.Create(info.savePath);
- downloadedBytes = 0;
- }
- _thread = new Thread(Run)
- {
- IsBackground = true
- };
- _thread.Start();
- }
- }
- }
|