星火微课系统客户端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DownloadManager.cs 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. using Common.Model;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading;
  8. namespace Common.system
  9. {
  10. /// <summary>
  11. /// 下载方法
  12. /// 创建人:赵耀
  13. /// 创建时间:2018年10月30日
  14. /// </summary>
  15. public class DownloadManager
  16. {
  17. private long fromIndex = 0;//开始下载的位置
  18. private bool isRun = false;//是否正在进行
  19. private DownloadInfoModel dlInfo;
  20. private List<DownloadService> dls = new List<DownloadService>();
  21. /// <summary>
  22. /// 开始下载
  23. /// </summary>
  24. public event Action OnStart;
  25. /// <summary>
  26. /// 停止下载
  27. /// </summary>
  28. public event Action OnStop;
  29. /// <summary>
  30. /// 下载进度
  31. /// </summary>
  32. public event Action<long, long, string> OnDownload;
  33. /// <summary>
  34. /// 下载完成
  35. /// </summary>
  36. public event Action<string> OnFinsh;
  37. /// <summary>
  38. /// 断开网络连接
  39. /// </summary>
  40. public event Action<string> OnDisconnect;
  41. public DownloadManager(DownloadInfoModel dlInfo)
  42. {
  43. this.dlInfo = dlInfo;
  44. }
  45. /// <summary>
  46. /// 暂停
  47. /// </summary>
  48. public void Stop()
  49. {
  50. this.isRun = false;
  51. dls.ForEach(dl => dl.Stop());
  52. OnStopHandler();
  53. }
  54. /// <summary>
  55. /// 开始下载
  56. /// </summary>
  57. public void Start()
  58. {
  59. this.dlInfo.isReStart = false;
  60. WorkStart();
  61. }
  62. /// <summary>
  63. /// 重新下载
  64. /// </summary>
  65. public void ReStart()
  66. {
  67. this.dlInfo.isReStart = true;
  68. WorkStart();
  69. }
  70. /// <summary>
  71. /// 下载
  72. /// </summary>
  73. private void WorkStart()
  74. {
  75. new Action(() =>
  76. {
  77. if (dlInfo.isReStart)
  78. {
  79. this.Stop();
  80. }
  81. while (dls.Where(dl => !dl.isStopped).Count() > 0)
  82. {
  83. if (dlInfo.isReStart) Thread.Sleep(100);
  84. else return;
  85. }
  86. this.isRun = true;
  87. OnStartHandler();
  88. //首次任务或者不支持断点续传的进入
  89. if (dlInfo.isNewTask || (!dlInfo.isNewTask && !dlInfo.IsSupportMultiThreading))
  90. {
  91. try
  92. {
  93. //第一次请求获取一小块数据,根据返回的情况判断是否支持断点续传
  94. using (var rsp = HttpHelper.Download(dlInfo.downloadUrlList[0], 0, 0, dlInfo.method))
  95. {
  96. //获取文件名,如果包含附件名称则取下附件,否则从url获取名称
  97. var Disposition = rsp.Headers["Content-Disposition"];
  98. try
  99. {
  100. if (Disposition != null)
  101. dlInfo.fileName = Disposition.Split('=')[1];
  102. else
  103. dlInfo.fileName = Path.GetFileName(rsp.ResponseUri.AbsolutePath);
  104. }
  105. catch (Exception)//无法获取文件名时 使用传入保存名称
  106. {
  107. dlInfo.fileName = dlInfo.saveFileName;
  108. }
  109. //默认给流总数
  110. dlInfo.count = rsp.ContentLength;
  111. //尝试获取 Content-Range 头部,不为空说明支持断点续传
  112. var contentRange = rsp.Headers["Content-Range"];
  113. if (contentRange != null)
  114. {
  115. //支持断点续传的话,就取range 这里的总数
  116. dlInfo.count = long.Parse(rsp.Headers["Content-Range"].Split('/')[1]);
  117. dlInfo.IsSupportMultiThreading = true;
  118. //生成一个临时文件名
  119. //var tempFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(dlInfo.fileName)).ToUpper();
  120. //tempFileName = tempFileName.Length > 32 ? tempFileName.Substring(0, 32) : tempFileName;
  121. //dlInfo.tempFileName = "test"; //tempFileName + DateTime.Now.ToString("yyyyMMddHHmmssfff");
  122. ///创建线程信息
  123. ///
  124. GetTaskInfo(dlInfo);
  125. }
  126. else
  127. {
  128. //不支持断点续传则一开始就直接读完整流
  129. Save(GetRealFileName(dlInfo), rsp.GetResponseStream());
  130. OnFineshHandler();
  131. }
  132. }
  133. }
  134. catch (Exception ex)
  135. {
  136. string ErrMessage = "【下载】(DownloadManager):" + ex.Message;
  137. LogHelper.WriteErrLog(ErrMessage, ex);
  138. OnDisconnectHandler();
  139. return;
  140. }
  141. dlInfo.isNewTask = false;
  142. }
  143. //如果支持断点续传采用这个
  144. if (dlInfo.IsSupportMultiThreading)
  145. {
  146. StartTask(dlInfo);
  147. //等待合并
  148. while (this.dls.Where(td => !td.isFinish).Count() > 0 && this.isRun)
  149. {
  150. Thread.Sleep(100);
  151. }
  152. if ((this.dls.Where(td => !td.isFinish).Count() == 0))
  153. {
  154. CombineFiles(dlInfo);
  155. OnFineshHandler();
  156. }
  157. }
  158. }).BeginInvoke(null, null);
  159. }
  160. /// <summary>
  161. /// 合成文件
  162. /// </summary>
  163. /// <param name="dlInfo"></param>
  164. private void CombineFiles(DownloadInfoModel dlInfo)
  165. {
  166. string realFilePath = GetRealFileName(dlInfo);
  167. //合并数据
  168. byte[] buffer = new Byte[2048];
  169. int length = 0;
  170. using (var fileStream = File.Open(realFilePath, FileMode.CreateNew))
  171. {
  172. for (int i = 0; i < dlInfo.TaskInfoList.Count; i++)
  173. {
  174. var tempFile = dlInfo.TaskInfoList[i].filePath;
  175. using (var tempStream = File.Open(tempFile, FileMode.Open))
  176. {
  177. while ((length = tempStream.Read(buffer, 0, buffer.Length)) > 0)
  178. {
  179. fileStream.Write(buffer, 0, length);
  180. }
  181. tempStream.Flush();
  182. }
  183. //File.Delete(tempFile);
  184. }
  185. }
  186. }
  187. /// <summary>
  188. /// 创建文件
  189. /// </summary>
  190. /// <param name="dlInfo"></param>
  191. /// <returns></returns>
  192. private static string GetRealFileName(DownloadInfoModel dlInfo)
  193. {
  194. //创建正式文件名,如果已存在则加数字序号创建,避免覆盖
  195. var fileIndex = 0;
  196. var realFilePath = Path.Combine(dlInfo.saveDir, dlInfo.saveFileName);
  197. while (File.Exists(realFilePath))
  198. {
  199. realFilePath = Path.Combine(dlInfo.saveDir, string.Format("{0}_{1}", fileIndex++, dlInfo.fileName));
  200. }
  201. return realFilePath;
  202. }
  203. private void StartTask(DownloadInfoModel dlInfo)
  204. {
  205. this.dls = new List<DownloadService>();
  206. if (dlInfo.TaskInfoList != null)
  207. {
  208. foreach (var item in dlInfo.TaskInfoList)
  209. {
  210. var dl = new DownloadService();
  211. dl.OnDownload += OnDownloadHandler;
  212. dl.OnDisconnect += OnDisconnectHandler;
  213. dls.Add(dl);
  214. try
  215. {
  216. dl.Start(item, dlInfo.isReStart);
  217. }
  218. catch (Exception ex)
  219. {
  220. string ErrMessage = "【下载】(DownloadManager):" + ex.Message;
  221. LogHelper.WriteErrLog(ErrMessage, ex);
  222. }
  223. }
  224. }
  225. }
  226. /// <summary>
  227. /// 创建下载任务
  228. /// </summary>
  229. /// <param name="dlInfo"></param>
  230. private void GetTaskInfo(DownloadInfoModel dlInfo)
  231. {
  232. var pieceSize = (dlInfo.count) / dlInfo.taskCount;
  233. dlInfo.TaskInfoList = new List<TaskInfoModel>();
  234. var rand = new Random();
  235. var urlIndex = 0;
  236. for (int i = 0; i <= dlInfo.taskCount + 1; i++)
  237. {
  238. var from = (i * pieceSize);
  239. if (from >= dlInfo.count) break;
  240. var to = from + pieceSize;
  241. if (to >= dlInfo.count) to = dlInfo.count;
  242. dlInfo.TaskInfoList.Add(
  243. new TaskInfoModel
  244. {
  245. method = dlInfo.method,
  246. downloadUrl = dlInfo.downloadUrlList[urlIndex++],
  247. filePath = Path.Combine(dlInfo.saveDir, dlInfo.tempFileName + ".temp" + i),
  248. fromIndex = from,
  249. toIndex = to
  250. });
  251. if (urlIndex >= dlInfo.downloadUrlList.Count) urlIndex = 0;
  252. }
  253. }
  254. /// <summary>
  255. /// 保存内容
  256. /// </summary>
  257. /// <param name="filePath"></param>
  258. /// <param name="stream"></param>
  259. private void Save(string filePath, Stream stream)
  260. {
  261. try
  262. {
  263. using (var writer = File.Open(filePath, FileMode.Append))
  264. {
  265. using (stream)
  266. {
  267. var repeatTimes = 0;
  268. byte[] buffer = new byte[1024];
  269. var length = 0;
  270. while ((length = stream.Read(buffer, 0, buffer.Length)) > 0 && this.isRun)
  271. {
  272. writer.Write(buffer, 0, length);
  273. this.fromIndex += length;
  274. if (repeatTimes % 5 == 0)
  275. {
  276. writer.Flush();//一定大小就刷一次缓冲区
  277. OnDownloadHandler();
  278. }
  279. repeatTimes++;
  280. }
  281. writer.Flush();
  282. OnDownloadHandler();
  283. }
  284. }
  285. }
  286. catch (Exception)
  287. {
  288. //异常也不影响
  289. }
  290. }
  291. /// <summary>
  292. /// 开始下载
  293. /// </summary>
  294. private void OnStartHandler()
  295. {
  296. new Action(() =>
  297. {
  298. this.OnStart.Invoke();
  299. }).BeginInvoke(null, null);
  300. }
  301. /// <summary>
  302. /// 暂停下载
  303. /// </summary>
  304. private void OnStopHandler()
  305. {
  306. new Action(() =>
  307. {
  308. this.OnStop.Invoke();
  309. }).BeginInvoke(null, null);
  310. }
  311. /// <summary>
  312. /// 下载完成
  313. /// </summary>
  314. private void OnFineshHandler()
  315. {
  316. new Action(() =>
  317. {
  318. for (int i = 0; i < dlInfo.TaskInfoList.Count; i++)
  319. {
  320. var tempFile = dlInfo.TaskInfoList[i].filePath;
  321. File.Delete(tempFile);
  322. }
  323. this.OnFinsh.Invoke(dlInfo.saveFileName);
  324. }).BeginInvoke(null, null);
  325. }
  326. /// <summary>
  327. /// 下载进度
  328. /// </summary>
  329. private void OnDownloadHandler()
  330. {
  331. new Action(() =>
  332. {
  333. long current = GetDownloadLength();
  334. this.OnDownload.Invoke(current, dlInfo.count, dlInfo.saveFileName);
  335. }).BeginInvoke(null, null);
  336. }
  337. /// <summary>
  338. /// 断开网络连接
  339. /// </summary>
  340. private void OnDisconnectHandler()
  341. {
  342. new Action(() =>
  343. {
  344. this.OnDisconnect.Invoke(dlInfo.saveFileName);
  345. }).BeginInvoke(null, null);
  346. }
  347. /// <summary>
  348. /// 当前下载进度
  349. /// </summary>
  350. /// <returns></returns>
  351. public long GetDownloadLength()
  352. {
  353. if (dlInfo.IsSupportMultiThreading) return dls.Sum(dl => dl.GetDownloadedCount());
  354. else return this.fromIndex;
  355. }
  356. /// <summary>
  357. /// 获取保存文件名
  358. /// </summary>
  359. /// <returns></returns>
  360. public DownloadInfoModel GetFileInfo
  361. {
  362. get { return dlInfo; }
  363. }
  364. }
  365. }