PartialDownloader.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using System;
  2. using System.ComponentModel;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Threading;
  8. namespace Masuit.Tools.Net
  9. {
  10. /// <summary>
  11. /// 部分下载器
  12. /// </summary>
  13. public class PartialDownloader
  14. {
  15. /// <summary>
  16. /// 这部分完成事件
  17. /// </summary>
  18. public event EventHandler DownloadPartCompleted;
  19. /// <summary>
  20. /// 部分下载进度改变事件
  21. /// </summary>
  22. public event EventHandler DownloadPartProgressChanged;
  23. /// <summary>
  24. /// 部分下载停止事件
  25. /// </summary>
  26. public event EventHandler DownloadPartStopped;
  27. private readonly AsyncOperation _aop = AsyncOperationManager.CreateOperation(null);
  28. readonly int[] _lastSpeeds;
  29. int _counter;
  30. private bool _wait;
  31. private int _to;
  32. private int _totalBytesRead;
  33. /// <summary>
  34. /// 下载已停止
  35. /// </summary>
  36. public bool Stopped { get; private set; }
  37. /// <summary>
  38. /// 下载已完成
  39. /// </summary>
  40. public bool Completed { get; private set; }
  41. /// <summary>
  42. /// 下载进度
  43. /// </summary>
  44. public int Progress { get; private set; }
  45. /// <summary>
  46. /// 下载目录
  47. /// </summary>
  48. public string Directory { get; }
  49. /// <summary>
  50. /// 文件名
  51. /// </summary>
  52. public string FileName { get; }
  53. /// <summary>
  54. /// 已读字节数
  55. /// </summary>
  56. public long TotalBytesRead => _totalBytesRead;
  57. /// <summary>
  58. /// 内容长度
  59. /// </summary>
  60. public long ContentLength { get; private set; }
  61. /// <summary>
  62. /// RangeAllowed
  63. /// </summary>
  64. public bool RangeAllowed { get; }
  65. /// <summary>
  66. /// url
  67. /// </summary>
  68. public string Url { get; }
  69. /// <summary>
  70. /// to
  71. /// </summary>
  72. public int To
  73. {
  74. get => _to;
  75. set
  76. {
  77. _to = value;
  78. ContentLength = _to - From + 1;
  79. }
  80. }
  81. /// <summary>
  82. /// from
  83. /// </summary>
  84. public int From { get; }
  85. /// <summary>
  86. /// 当前位置
  87. /// </summary>
  88. public int CurrentPosition => From + _totalBytesRead - 1;
  89. /// <summary>
  90. /// 剩余字节数
  91. /// </summary>
  92. public int RemainingBytes => (int)(ContentLength - _totalBytesRead);
  93. /// <summary>
  94. /// 完整路径
  95. /// </summary>
  96. public string FullPath => Path.Combine(Directory, FileName);
  97. /// <summary>
  98. /// 下载速度
  99. /// </summary>
  100. public int SpeedInBytes
  101. {
  102. get
  103. {
  104. if (Completed)
  105. {
  106. return 0;
  107. }
  108. int totalSpeeds = _lastSpeeds.Sum();
  109. return totalSpeeds / 10;
  110. }
  111. }
  112. /// <summary>
  113. /// 部分块下载
  114. /// </summary>
  115. /// <param name="url"></param>
  116. /// <param name="dir"></param>
  117. /// <param name="fileGuid"></param>
  118. /// <param name="from"></param>
  119. /// <param name="to"></param>
  120. /// <param name="rangeAllowed"></param>
  121. public PartialDownloader(string url, string dir, string fileGuid, int from, int to, bool rangeAllowed)
  122. {
  123. From = from;
  124. _to = to;
  125. Url = url;
  126. RangeAllowed = rangeAllowed;
  127. FileName = fileGuid;
  128. Directory = dir;
  129. _lastSpeeds = new int[10];
  130. }
  131. void DownloadProcedure(Action<HttpWebRequest> config)
  132. {
  133. using (var file = new FileStream(FullPath, FileMode.Create, FileAccess.ReadWrite))
  134. {
  135. var sw = new Stopwatch();
  136. if (WebRequest.Create(Url) is HttpWebRequest req)
  137. {
  138. req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36";
  139. req.AllowAutoRedirect = true;
  140. req.MaximumAutomaticRedirections = 5;
  141. req.ServicePoint.ConnectionLimit += 1;
  142. req.ServicePoint.Expect100Continue = true;
  143. req.ProtocolVersion = HttpVersion.Version11;
  144. config(req);
  145. ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
  146. ServicePointManager.Expect100Continue = true;
  147. if (RangeAllowed)
  148. {
  149. req.AddRange(From, _to);
  150. }
  151. if (req.GetResponse() is HttpWebResponse resp)
  152. {
  153. ContentLength = resp.ContentLength;
  154. if (ContentLength <= 0 || (RangeAllowed && ContentLength != _to - From + 1))
  155. {
  156. throw new Exception("Invalid response content");
  157. }
  158. using var tempStream = resp.GetResponseStream();
  159. int bytesRead;
  160. byte[] buffer = new byte[4096];
  161. sw.Start();
  162. while ((bytesRead = tempStream.Read(buffer, 0, buffer.Length)) > 0)
  163. {
  164. if (_totalBytesRead + bytesRead > ContentLength)
  165. {
  166. bytesRead = (int)(ContentLength - _totalBytesRead);
  167. }
  168. file.Write(buffer, 0, bytesRead);
  169. _totalBytesRead += bytesRead;
  170. _lastSpeeds[_counter] = (int)(_totalBytesRead / Math.Ceiling(sw.Elapsed.TotalSeconds));
  171. _counter = (_counter >= 9) ? 0 : _counter + 1;
  172. int tempProgress = (int)(_totalBytesRead * 100 / ContentLength);
  173. if (Progress != tempProgress)
  174. {
  175. Progress = tempProgress;
  176. _aop.Post(state =>
  177. {
  178. DownloadPartProgressChanged?.Invoke(this, EventArgs.Empty);
  179. }, null);
  180. }
  181. if (Stopped || (RangeAllowed && _totalBytesRead == ContentLength))
  182. {
  183. break;
  184. }
  185. }
  186. }
  187. req.Abort();
  188. }
  189. sw.Stop();
  190. if (!Stopped && DownloadPartCompleted != null)
  191. {
  192. _aop.Post(state =>
  193. {
  194. Completed = true;
  195. DownloadPartCompleted(this, EventArgs.Empty);
  196. }, null);
  197. }
  198. if (Stopped && DownloadPartStopped != null)
  199. {
  200. _aop.Post(state => DownloadPartStopped(this, EventArgs.Empty), null);
  201. }
  202. }
  203. }
  204. /// <summary>
  205. /// 启动下载
  206. /// </summary>
  207. public void Start(Action<HttpWebRequest> config)
  208. {
  209. Stopped = false;
  210. var procThread = new Thread(_ => DownloadProcedure(config));
  211. procThread.Start();
  212. }
  213. /// <summary>
  214. /// 下载停止
  215. /// </summary>
  216. public void Stop()
  217. {
  218. Stopped = true;
  219. }
  220. /// <summary>
  221. /// 暂停等待下载
  222. /// </summary>
  223. public void Wait()
  224. {
  225. _wait = true;
  226. }
  227. /// <summary>
  228. /// 稍后唤醒
  229. /// </summary>
  230. public void ResumeAfterWait()
  231. {
  232. _wait = false;
  233. }
  234. }
  235. }