星火微课系统客户端
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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