PartialDownloader.cs 7.1 KB

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