Download.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Net;
  5. using System.Net.Security;
  6. using System.Security.Cryptography.X509Certificates;
  7. using System.Threading;
  8. using UnityEngine;
  9. namespace VEngine
  10. {
  11. public enum DownloadStatus
  12. {
  13. Wait,
  14. Progressing,
  15. Success,
  16. Failed
  17. }
  18. public class DownloadInfo
  19. {
  20. public uint crc;
  21. public string savePath;
  22. public long size;
  23. public string url;
  24. }
  25. public class Download : CustomYieldInstruction
  26. {
  27. public static uint MaxDownloads = 10;
  28. public static uint ReadBufferSize = 1042 * 4;
  29. public static readonly List<Download> Prepared = new List<Download>();
  30. public static readonly List<Download> Progressing = new List<Download>();
  31. public static readonly Dictionary<string, Download> Cache = new Dictionary<string, Download>();
  32. private static float lastSampleTime;
  33. private static long lastTotalDownloadedBytes;
  34. private readonly byte[] _readBuffer = new byte[ReadBufferSize];
  35. private Thread _thread;
  36. private FileStream writer;
  37. private Download()
  38. {
  39. status = DownloadStatus.Wait;
  40. downloadedBytes = 0;
  41. }
  42. public DownloadInfo info { get; private set; }
  43. public DownloadStatus status { get; private set; }
  44. public string error { get; private set; }
  45. public Action<Download> completed { get; set; }
  46. public Action<Download> updated { get; set; }
  47. public bool isDone => status == DownloadStatus.Failed || status == DownloadStatus.Success;
  48. public float progress => downloadedBytes * 1f / info.size;
  49. public long downloadedBytes { get; private set; }
  50. public override bool keepWaiting => !isDone;
  51. public static bool Working => Progressing.Count > 0;
  52. public static long TotalDownloadedBytes
  53. {
  54. get
  55. {
  56. var value = 0L;
  57. foreach (var item in Cache) value += item.Value.downloadedBytes;
  58. return value;
  59. }
  60. }
  61. public static long TotalSize
  62. {
  63. get
  64. {
  65. var value = 0L;
  66. foreach (var item in Cache) value += item.Value.info.size;
  67. return value;
  68. }
  69. }
  70. public static long TotalBandwidth { get; private set; }
  71. public static void ClearAllDownloads()
  72. {
  73. foreach (var download in Progressing) download.Cancel();
  74. Prepared.Clear();
  75. Progressing.Clear();
  76. Cache.Clear();
  77. }
  78. public static Download DownloadAsync(string url, string savePath, Action<Download> completed = null,
  79. long size = 0, uint crc = 0)
  80. {
  81. return DownloadAsync(new DownloadInfo
  82. {
  83. url = url,
  84. savePath = savePath,
  85. crc = crc,
  86. size = size
  87. }, completed);
  88. }
  89. public static Download DownloadAsync(DownloadInfo info, Action<Download> completed = null)
  90. {
  91. Download download;
  92. if (!Cache.TryGetValue(info.url, out download))
  93. {
  94. download = new Download
  95. {
  96. info = info
  97. };
  98. Prepared.Add(download);
  99. Cache.Add(info.url, download);
  100. }
  101. else
  102. {
  103. Logger.W("Download url {0} already exist.", info.url);
  104. }
  105. if (completed != null) download.completed += completed;
  106. return download;
  107. }
  108. public static void UpdateAll()
  109. {
  110. if (Prepared.Count > 0)
  111. for (var index = 0; index < Mathf.Min(Prepared.Count, MaxDownloads - Progressing.Count); index++)
  112. {
  113. var download = Prepared[index];
  114. Prepared.RemoveAt(index);
  115. index--;
  116. Progressing.Add(download);
  117. download.Start();
  118. }
  119. if (Progressing.Count > 0)
  120. {
  121. for (var index = 0; index < Progressing.Count; index++)
  122. {
  123. var download = Progressing[index];
  124. if (download.updated != null) download.updated(download);
  125. if (download.isDone)
  126. {
  127. if (download.status == DownloadStatus.Failed)
  128. Logger.E("Unable to download {0} with error {1}", download.info.url, download.error);
  129. else
  130. Logger.I("Success to download {0}", download.info.url);
  131. Progressing.RemoveAt(index);
  132. index--;
  133. download.Complete();
  134. }
  135. }
  136. if (Time.realtimeSinceStartup - lastSampleTime >= 1)
  137. {
  138. TotalBandwidth = TotalDownloadedBytes - lastTotalDownloadedBytes;
  139. lastTotalDownloadedBytes = TotalDownloadedBytes;
  140. lastSampleTime = Time.realtimeSinceStartup;
  141. }
  142. }
  143. else
  144. {
  145. if (Cache.Count <= 0) return;
  146. Cache.Clear();
  147. lastTotalDownloadedBytes = 0;
  148. lastSampleTime = Time.realtimeSinceStartup;
  149. }
  150. }
  151. public void Retry()
  152. {
  153. status = DownloadStatus.Wait;
  154. Start();
  155. }
  156. public void UnPause()
  157. {
  158. Retry();
  159. }
  160. public void Pause()
  161. {
  162. status = DownloadStatus.Wait;
  163. }
  164. public void Cancel()
  165. {
  166. error = "User Cancel.";
  167. status = DownloadStatus.Failed;
  168. }
  169. private void Complete()
  170. {
  171. if (completed != null)
  172. {
  173. completed.Invoke(this);
  174. completed = null;
  175. }
  176. }
  177. private void Run()
  178. {
  179. try
  180. {
  181. Downloading();
  182. CloseWrite();
  183. if (status == DownloadStatus.Failed) return;
  184. if (downloadedBytes != info.size)
  185. {
  186. error = $"Download lenght {downloadedBytes} mismatch to {info.size}";
  187. status = DownloadStatus.Failed;
  188. return;
  189. }
  190. if (info.crc != 0)
  191. {
  192. var crc = Utility.ComputeCRC32(info.savePath);
  193. if (info.crc != crc)
  194. {
  195. File.Delete(info.savePath);
  196. error = $"Download crc {crc} mismatch to {info.crc}";
  197. status = DownloadStatus.Failed;
  198. return;
  199. }
  200. }
  201. status = DownloadStatus.Success;
  202. }
  203. catch (Exception e)
  204. {
  205. CloseWrite();
  206. error = e.Message;
  207. status = DownloadStatus.Failed;
  208. }
  209. }
  210. private void CloseWrite()
  211. {
  212. if (writer != null)
  213. {
  214. writer.Flush();
  215. writer.Close();
  216. }
  217. }
  218. private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain,
  219. SslPolicyErrors spe)
  220. {
  221. return true;
  222. }
  223. private void Downloading()
  224. {
  225. var request = CreateWebRequest();
  226. using (var response = request.GetResponse())
  227. {
  228. if (response.ContentLength > 0)
  229. {
  230. if (info.size == 0) info.size = response.ContentLength + downloadedBytes;
  231. using (var reader = response.GetResponseStream())
  232. {
  233. if (downloadedBytes < info.size)
  234. while (status == DownloadStatus.Progressing)
  235. if (ReadToEnd(reader))
  236. break;
  237. }
  238. }
  239. else
  240. {
  241. status = DownloadStatus.Success;
  242. }
  243. }
  244. }
  245. private WebRequest CreateWebRequest()
  246. {
  247. WebRequest request;
  248. if (info.url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
  249. {
  250. ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;
  251. request = GetHttpWebRequest();
  252. }
  253. else
  254. {
  255. request = GetHttpWebRequest();
  256. }
  257. return request;
  258. }
  259. private WebRequest GetHttpWebRequest()
  260. {
  261. var httpWebRequest = (HttpWebRequest) WebRequest.Create(info.url);
  262. httpWebRequest.ProtocolVersion = HttpVersion.Version10;
  263. if (downloadedBytes > 0) httpWebRequest.AddRange(downloadedBytes);
  264. return httpWebRequest;
  265. }
  266. private bool ReadToEnd(Stream reader)
  267. {
  268. var len = reader.Read(_readBuffer, 0, _readBuffer.Length);
  269. if (len > 0)
  270. {
  271. writer.Write(_readBuffer, 0, len);
  272. downloadedBytes += len;
  273. return false;
  274. }
  275. return true;
  276. }
  277. private void Start()
  278. {
  279. if (status != DownloadStatus.Wait) return;
  280. Logger.I("Start download {0}", info.url);
  281. status = DownloadStatus.Progressing;
  282. var file = new FileInfo(info.savePath);
  283. if (file.Exists && file.Length > 0)
  284. {
  285. if (info.size > 0 && file.Length == info.size)
  286. {
  287. status = DownloadStatus.Success;
  288. return;
  289. }
  290. writer = File.OpenWrite(info.savePath);
  291. downloadedBytes = writer.Length - 1;
  292. if (downloadedBytes > 0) writer.Seek(-1, SeekOrigin.End);
  293. }
  294. else
  295. {
  296. var dir = Path.GetDirectoryName(info.savePath);
  297. if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
  298. writer = File.Create(info.savePath);
  299. downloadedBytes = 0;
  300. }
  301. _thread = new Thread(Run)
  302. {
  303. IsBackground = true
  304. };
  305. _thread.Start();
  306. }
  307. }
  308. }