COSXMLDownloadTask.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using COSXML.Model.Object;
  5. using COSXML.Utils;
  6. using COSXML.Model;
  7. using System.IO;
  8. using COSXML.Log;
  9. using COSXML.CosException;
  10. using System.Xml.Serialization;
  11. using COSXML.Common;
  12. using System.Threading;
  13. namespace COSXML.Transfer
  14. {
  15. public sealed class COSXMLDownloadTask : COSXMLTask
  16. {
  17. // user params
  18. private string localDir;
  19. public string localFileName;
  20. private long localFileOffset = 0;
  21. private long rangeStart = 0L;
  22. private long rangeEnd = -1L;
  23. private int maxTasks = 5;
  24. private long sliceSize = 10 * 1024 * 1024;
  25. private long divisionSize = 20 * 1024 * 1024;
  26. private bool enableCrc64Check = false;
  27. private long singleTaskTimeoutMs = 30 * 1000;
  28. // concurrency control
  29. private volatile int activeTasks = 0;
  30. private int maxRetries = 3;
  31. private Object syncExit = new Object();
  32. private bool isExit = false;
  33. private static ReaderWriterLockSlim resumableFileWriteLock = new ReaderWriterLockSlim();
  34. private Dictionary<int, DownloadSliceStruct> sliceList = new Dictionary<int, DownloadSliceStruct>();
  35. // internal requests
  36. private HeadObjectRequest headObjectRequest;
  37. private GetObjectRequest getObjectRequest;
  38. private string localFileCrc64;
  39. // list of ongoing getObjectRequest
  40. private List<GetObjectRequest> getObjectRequestsList;
  41. // resumable info
  42. private string resumableTaskFile = null;
  43. private DownloadResumableInfo resumableInfo = null;
  44. private bool resumable = false;
  45. private HashSet<string> tmpFilePaths = new HashSet<string>();
  46. private HashSet<int> sliceToRemove = null;
  47. // global exception
  48. private COSXML.CosException.CosClientException gClientExp = null;
  49. public COSXMLDownloadTask(string bucket, string key, string localDir, string localFileName)
  50. : base(bucket, key)
  51. {
  52. if (localDir.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.OrdinalIgnoreCase))
  53. {
  54. this.localDir = localDir;
  55. }
  56. else
  57. {
  58. this.localDir = localDir + System.IO.Path.DirectorySeparatorChar;
  59. }
  60. this.localFileName = localFileName;
  61. }
  62. public COSXMLDownloadTask(GetObjectRequest request)
  63. : base(request.Bucket, request.Key)
  64. {
  65. this.getObjectRequest = request;
  66. if (request.localDir.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString(), StringComparison.OrdinalIgnoreCase))
  67. {
  68. this.localDir = request.localDir;
  69. }
  70. else
  71. {
  72. this.localDir = request.localDir + System.IO.Path.DirectorySeparatorChar;
  73. }
  74. this.localFileName = request.localFileName;
  75. }
  76. public void SetRange(long rangeStart, long rangeEnd)
  77. {
  78. this.rangeStart = rangeStart;
  79. this.rangeEnd = rangeEnd;
  80. }
  81. public void SetLocalFileOffset(long localFileOffset)
  82. {
  83. this.localFileOffset = localFileOffset;
  84. }
  85. public void SetResumableDownload(bool resumable)
  86. {
  87. this.resumable = resumable;
  88. }
  89. public void SetResumableTaskFile(string file)
  90. {
  91. this.resumableTaskFile = file;
  92. }
  93. public string GetLocalFileCrc64()
  94. {
  95. return localFileCrc64;
  96. }
  97. public void SetMaxTasks(int maxTasks)
  98. {
  99. if (maxTasks <= 0)
  100. {
  101. throw new COSXML.CosException.CosClientException((int) CosClientError.InvalidArgument, "max tasks cannot be negative or zero");
  102. return;
  103. }
  104. this.maxTasks = maxTasks;
  105. }
  106. public void SetSliceSize(long sliceSize)
  107. {
  108. if (sliceSize <= 0)
  109. {
  110. throw new COSXML.CosException.CosClientException((int) CosClientError.InvalidArgument, "slice size cannot be negative or zero");
  111. return;
  112. }
  113. this.sliceSize = sliceSize;
  114. }
  115. public void SetDivisionSize(long divisionSize)
  116. {
  117. if (divisionSize <= 0)
  118. {
  119. throw new COSXML.CosException.CosClientException((int) CosClientError.InvalidArgument, "division size cannot be negative or zero");
  120. return;
  121. }
  122. this.divisionSize = divisionSize;
  123. }
  124. public void SetEnableCRC64Check(bool enableCrc64Check)
  125. {
  126. this.enableCrc64Check = enableCrc64Check;
  127. }
  128. public void SetSingleTaskTimeoutMs(long singleTaskTimeoutMs)
  129. {
  130. if (singleTaskTimeoutMs > 0)
  131. {
  132. this.singleTaskTimeoutMs = singleTaskTimeoutMs;
  133. }
  134. }
  135. public void SetMaxRetries(int maxRetries)
  136. {
  137. if (maxRetries > 0)
  138. {
  139. this.maxRetries = maxRetries;
  140. }
  141. }
  142. private void ComputeSliceList(HeadObjectResult result)
  143. {
  144. // slice list can be not empty, if use pause&resume, skip it
  145. if (this.sliceList.Count != 0)
  146. {
  147. return;
  148. }
  149. long contentLength = result.size;
  150. rangeEnd = rangeEnd == -1L || (rangeEnd > contentLength) ? contentLength - 1 : rangeEnd;
  151. if (rangeEnd - rangeStart + 1 < this.divisionSize)
  152. {
  153. DownloadSliceStruct slice = new DownloadSliceStruct();
  154. slice.partNumber = 1;
  155. slice.sliceStart = rangeStart;
  156. slice.sliceEnd = rangeEnd;
  157. this.sliceList.Add(slice.partNumber, slice);
  158. }
  159. else
  160. {
  161. long sliceCount = ((rangeEnd - rangeStart) / this.sliceSize) + 1;
  162. for (int i = 0; i < sliceCount; i++)
  163. {
  164. DownloadSliceStruct slice = new DownloadSliceStruct();
  165. slice.partNumber = i + 1;
  166. slice.sliceStart = rangeStart + i * this.sliceSize;
  167. slice.sliceEnd =
  168. (slice.sliceStart + this.sliceSize > rangeEnd)
  169. ? rangeEnd : slice.sliceStart + this.sliceSize - 1;
  170. this.sliceList.Add(slice.partNumber, slice);
  171. }
  172. }
  173. }
  174. internal void Download()
  175. {
  176. UpdateTaskState(TaskState.Waiting);
  177. //对象是否存在
  178. headObjectRequest = new HeadObjectRequest(bucket, key);
  179. cosXmlServer.HeadObject(headObjectRequest, delegate (CosResult cosResult)
  180. {
  181. lock (syncExit)
  182. {
  183. if (isExit)
  184. {
  185. return;
  186. }
  187. }
  188. if (UpdateTaskState(TaskState.Running))
  189. {
  190. HeadObjectResult result = cosResult as HeadObjectResult;
  191. ComputeSliceList(result);
  192. // resolv resumeInfo
  193. if (resumable)
  194. {
  195. if (resumableTaskFile == null)
  196. {
  197. resumableTaskFile = localDir + localFileName + ".cosresumabletask";
  198. }
  199. ResumeDownloadInPossible(result, localDir + localFileName);
  200. }
  201. // concurrent download
  202. ConcurrentGetObject(result.crc64ecma);
  203. }
  204. },
  205. delegate (CosClientException clientEx, CosServerException serverEx)
  206. {
  207. lock (syncExit)
  208. {
  209. if (isExit)
  210. {
  211. return;
  212. }
  213. }
  214. if (UpdateTaskState(TaskState.Failed))
  215. {
  216. if (failCallback != null)
  217. {
  218. failCallback(clientEx, serverEx);
  219. }
  220. }
  221. });
  222. }
  223. // resolve resumable task file, continue in proper position
  224. private void ResumeDownloadInPossible(HeadObjectResult result, string localFile)
  225. {
  226. this.resumableInfo = DownloadResumableInfo.LoadFromResumableFile(resumableTaskFile);
  227. if (this.resumableInfo != null)
  228. {
  229. if ((result.crc64ecma == null || result.crc64ecma == resumableInfo.crc64ecma) &&
  230. this.resumableInfo.eTag == result.eTag &&
  231. this.resumableInfo.lastModified == result.lastModified &&
  232. this.resumableInfo.contentLength == result.size)
  233. {
  234. // load parts downloaded
  235. if (this.resumableInfo.slicesDownloaded != null)
  236. {
  237. // process downloaded parts
  238. foreach (DownloadSliceStruct downloadedSlice in resumableInfo.slicesDownloaded)
  239. {
  240. // remove from current queue
  241. DownloadSliceStruct calculatedSlice;
  242. bool ret = this.sliceList.TryGetValue(downloadedSlice.partNumber, out calculatedSlice);
  243. if (!ret) {
  244. // resumable file broken
  245. break;
  246. }
  247. if (calculatedSlice.sliceStart == downloadedSlice.sliceStart
  248. && calculatedSlice.sliceEnd == downloadedSlice.sliceEnd)
  249. {
  250. if (this.sliceToRemove == null)
  251. this.sliceToRemove = new HashSet<int>();
  252. this.sliceToRemove.Add(downloadedSlice.partNumber);
  253. }
  254. // add to merging list
  255. string tmpFileName = localDir + "." + localFileName + ".cosresumable." + downloadedSlice.partNumber;
  256. this.tmpFilePaths.Add(tmpFileName);
  257. }
  258. }
  259. else
  260. {
  261. this.resumableInfo.slicesDownloaded = new List<DownloadSliceStruct>();
  262. }
  263. }
  264. }
  265. else
  266. {
  267. this.resumableInfo = new DownloadResumableInfo();
  268. this.resumableInfo.contentLength = result.size;
  269. this.resumableInfo.crc64ecma = result.crc64ecma;
  270. this.resumableInfo.eTag = result.eTag;
  271. this.resumableInfo.lastModified = result.lastModified;
  272. this.resumableInfo.slicesDownloaded = new List<DownloadSliceStruct>();
  273. resumableInfo.Persist(resumableTaskFile);
  274. }
  275. }
  276. private bool CompareCrc64(string localFile, string crc64ecma)
  277. {
  278. Crc64.InitECMA();
  279. String hash = String.Empty;
  280. using (FileStream fs = File.Open(localFile, FileMode.Open))
  281. {
  282. byte[] buffer = new byte[2048];
  283. int bytesRead;
  284. ulong crc = 0;
  285. while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
  286. {
  287. ulong partCrc = Crc64.Compute(buffer, 0, bytesRead);
  288. if (crc == 0)
  289. {
  290. crc = partCrc;
  291. }
  292. else
  293. {
  294. crc = Crc64.Combine(crc, partCrc, bytesRead);
  295. }
  296. }
  297. localFileCrc64 = crc.ToString();
  298. return localFileCrc64 == crc64ecma;
  299. }
  300. }
  301. // 发起多线程下载
  302. private void ConcurrentGetObject(string crc64ecma)
  303. {
  304. lock (syncExit)
  305. {
  306. if (isExit)
  307. {
  308. return;
  309. }
  310. }
  311. // 保障目标路径是存在的
  312. DirectoryInfo dirInfo = new DirectoryInfo(localDir);
  313. if (!dirInfo.Exists)
  314. {
  315. dirInfo.Create();
  316. }
  317. // 控制任务数
  318. AutoResetEvent resetEvent = new AutoResetEvent(false);
  319. // 记录成功的分片
  320. if (sliceToRemove == null)
  321. {
  322. sliceToRemove = new HashSet<int>();
  323. }
  324. // 记录子任务
  325. if (getObjectRequestsList == null)
  326. {
  327. getObjectRequestsList = new List<GetObjectRequest>();
  328. }
  329. int retries = 0;
  330. // 只抛出最后一条服务端回包
  331. GetObjectResult downloadResult = null;
  332. while (sliceList.Count != 0 && retries < maxRetries)
  333. {
  334. retries += 1;
  335. foreach (int partNumber in sliceList.Keys)
  336. {
  337. if (sliceToRemove.Contains(partNumber))
  338. {
  339. continue;
  340. }
  341. DownloadSliceStruct slice;
  342. bool get_state = sliceList.TryGetValue(partNumber, out slice);
  343. if (activeTasks >= maxTasks)
  344. {
  345. resetEvent.WaitOne();
  346. }
  347. lock (syncExit)
  348. {
  349. if (isExit)
  350. {
  351. return;
  352. }
  353. }
  354. string tmpFileName = "." + localFileName + ".cosresumable." + slice.partNumber;
  355. /*
  356. 以下几种场景需要清理临时文件
  357. 1. 一次下载中, 重试未下载完成的文件
  358. 2. Pause之后重入下载, 清理Pause前未下载完成的文件
  359. 3. 断点续传中, 清理未下载完成的单个分块
  360. */
  361. FileInfo tmpFileInfo = new FileInfo(localDir + tmpFileName);
  362. if (tmpFileInfo.Exists
  363. && tmpFileInfo.Length != (slice.sliceEnd - slice.sliceStart + 1)
  364. && localFileOffset != 0)
  365. {
  366. System.IO.File.Delete(localDir + tmpFileName);
  367. }
  368. GetObjectRequest subGetObjectRequest = new GetObjectRequest(bucket, key, localDir, tmpFileName);
  369. tmpFilePaths.Add(localDir + tmpFileName);
  370. subGetObjectRequest.SetRange(slice.sliceStart, slice.sliceEnd);
  371. getObjectRequestsList.Add(subGetObjectRequest);
  372. // 计算出来只有一个分块, 而且不是Resume或重试剩的一个, 即不走并发下载, 用GetObject的进度回调给客户端
  373. if (progressCallback != null && this.sliceList.Count == 1 && sliceToRemove.Count == 0)
  374. {
  375. subGetObjectRequest.SetCosProgressCallback(delegate(long completed, long total)
  376. {
  377. progressCallback(completed, total);
  378. }
  379. );
  380. }
  381. Interlocked.Increment(ref activeTasks);
  382. cosXmlServer.GetObject(subGetObjectRequest,
  383. delegate (CosResult result)
  384. {
  385. Interlocked.Decrement(ref activeTasks);
  386. lock (syncExit)
  387. {
  388. if (isExit)
  389. {
  390. return;
  391. }
  392. }
  393. sliceToRemove.Add(partNumber);
  394. if (progressCallback != null && this.sliceList.Count > 1)
  395. {
  396. long completed = sliceToRemove.Count * this.sliceSize;
  397. long total = rangeEnd - rangeStart;
  398. if (completed > total)
  399. completed = total;
  400. progressCallback(completed, total);
  401. }
  402. downloadResult = result as GetObjectResult;
  403. resetEvent.Set();
  404. if (resumable)
  405. {
  406. // flush done parts
  407. this.resumableInfo.slicesDownloaded.Add(slice);
  408. this.resumableInfo.Persist(resumableTaskFile);
  409. }
  410. },
  411. delegate (CosClientException clientEx, CosServerException serverEx)
  412. {
  413. Interlocked.Decrement(ref activeTasks);
  414. lock (syncExit)
  415. {
  416. if (isExit)
  417. {
  418. return;
  419. }
  420. }
  421. // 对服务端返回的4xx, 不重试, 直接抛异常
  422. if (serverEx != null && serverEx.statusCode < 500) {
  423. throw serverEx;
  424. if (failCallback != null)
  425. {
  426. failCallback(null, serverEx);
  427. }
  428. return;
  429. }
  430. // 对客户端异常, 全部都重试
  431. if (clientEx != null) {
  432. gClientExp = clientEx;
  433. }
  434. resetEvent.Set();
  435. }
  436. );
  437. }
  438. long waitTimeMs = 0;
  439. int lastActiveTasks = activeTasks;
  440. while (activeTasks != 0)
  441. {
  442. lock (syncExit)
  443. {
  444. if (isExit)
  445. {
  446. return;
  447. }
  448. }
  449. Thread.Sleep(100);
  450. if (lastActiveTasks == activeTasks) {
  451. /*
  452. 兼容一种子任务既不成功,也不失败,完全hang住的场景
  453. 在 .net core + 丢包率高的使用场景下会概率性复现
  454. 当activeTasks一直不变时,累加一个等待的时间
  455. 超出 singleTaskTimeoutMs 时,全部清理掉进入下一轮重试
  456. */
  457. waitTimeMs += 100;
  458. } else {
  459. waitTimeMs = 0;
  460. lastActiveTasks = activeTasks;
  461. }
  462. if (waitTimeMs > singleTaskTimeoutMs) {
  463. foreach(GetObjectRequest subGetObjectRequest in getObjectRequestsList) {
  464. try {
  465. cosXmlServer.Cancel(subGetObjectRequest);
  466. } catch (Exception e) {
  467. ;
  468. }
  469. }
  470. getObjectRequestsList.Clear();
  471. activeTasks = 0;
  472. break;
  473. }
  474. }
  475. // 从下载列表中移除成功分块
  476. foreach (int partNumber in sliceToRemove)
  477. {
  478. sliceList.Remove(partNumber);
  479. }
  480. }
  481. if (this.sliceList.Count != 0)
  482. {
  483. if (gClientExp != null)
  484. {
  485. throw gClientExp;
  486. }
  487. COSXML.CosException.CosClientException clientEx = new COSXML.CosException.CosClientException
  488. ((int)CosClientError.InternalError, "max retries " + retries + " excceed, download fail");
  489. throw clientEx;
  490. if (UpdateTaskState(TaskState.Failed))
  491. {
  492. if (failCallback != null)
  493. {
  494. failCallback(clientEx, null);
  495. }
  496. }
  497. return;
  498. }
  499. // 预期每个分块都下载完成了, 开始顺序合并
  500. FileMode fileMode = FileMode.OpenOrCreate;
  501. FileInfo localFileInfo = new FileInfo(localDir + localFileName);
  502. if (localFileInfo.Exists && localFileOffset == 0 && localFileInfo.Length != rangeEnd - rangeStart + 1)
  503. fileMode = FileMode.Truncate;
  504. using (var outputStream = File.Open(localDir + localFileName, fileMode))
  505. {
  506. outputStream.Seek(localFileOffset, 0);
  507. // sort
  508. List<string> tmpFileList = new List<string>(this.tmpFilePaths);
  509. tmpFileList.Sort(delegate(string x, string y){
  510. int partNumber1 = int.Parse(x.Split(new string[]{"cosresumable."}, StringSplitOptions.None)[1]);
  511. int partNumber2 = int.Parse(y.Split(new string[]{"cosresumable."}, StringSplitOptions.None)[1]);
  512. return partNumber1 - partNumber2;
  513. });
  514. foreach (var inputFilePath in tmpFileList)
  515. {
  516. // tmp not exist, clear everything and ask for retry
  517. if (!File.Exists(inputFilePath))
  518. {
  519. // check if download already completed
  520. if (File.Exists(localDir + localFileName))
  521. {
  522. FileInfo fileInfo = new FileInfo(localDir + localFileName);
  523. if (fileInfo.Length == rangeEnd - rangeStart + 1)
  524. {
  525. foreach (var tmpFile in tmpFileList)
  526. {
  527. System.IO.File.Delete(tmpFile);
  528. }
  529. if (resumableTaskFile != null)
  530. {
  531. System.IO.File.Delete(resumableTaskFile);
  532. }
  533. break;
  534. }
  535. }
  536. // not completed, report fatal error
  537. foreach (var tmpFile in tmpFileList)
  538. {
  539. System.IO.File.Delete(tmpFile);
  540. }
  541. if (resumableTaskFile != null)
  542. {
  543. System.IO.File.Delete(resumableTaskFile);
  544. }
  545. if (File.Exists(localDir + localFileName))
  546. {
  547. System.IO.File.Delete(localDir + localFileName);
  548. }
  549. COSXML.CosException.CosClientException clientEx = new COSXML.CosException.CosClientException
  550. ((int)CosClientError.InternalError, "local tmp file not exist, could be concurrent writing same file"
  551. + inputFilePath +" download again");
  552. throw clientEx;
  553. if (failCallback != null)
  554. {
  555. failCallback(clientEx, null);
  556. }
  557. break;
  558. }
  559. using (var inputStream = File.OpenRead(inputFilePath))
  560. {
  561. FileInfo info = new FileInfo(inputFilePath);
  562. inputStream.CopyTo(outputStream);
  563. }
  564. }
  565. tmpFileList.Clear();
  566. tmpFilePaths.Clear();
  567. outputStream.Close();
  568. // 合并完成后,默认进行文件大小的检查
  569. FileInfo completedFileInfo = new FileInfo(localDir + localFileName);
  570. if (completedFileInfo.Length != rangeEnd - rangeStart + 1) {
  571. COSXML.CosException.CosClientException clientEx = new COSXML.CosException.CosClientException
  572. ((int)CosClientError.InternalError, "local File Length " + completedFileInfo.Length +
  573. " does not equals to applied download length " + (rangeEnd - rangeStart + 1) + ", try again");
  574. throw clientEx;
  575. if (UpdateTaskState(TaskState.Failed))
  576. {
  577. if (failCallback != null)
  578. {
  579. failCallback(clientEx, null);
  580. }
  581. }
  582. return;
  583. }
  584. // 按需进行CRC64的检查
  585. if (enableCrc64Check) {
  586. if (!CompareCrc64(localDir + localFileName, crc64ecma)) {
  587. COSXML.CosException.CosClientException clientEx = new COSXML.CosException.CosClientException
  588. ((int)CosClientError.CRC64ecmaCheckFailed, "local File Crc64 " +
  589. " does not equals to crc64ecma on cos, try download again");
  590. throw clientEx;
  591. if (UpdateTaskState(TaskState.Failed))
  592. {
  593. if (failCallback != null)
  594. {
  595. failCallback(clientEx, null);
  596. }
  597. }
  598. return;
  599. }
  600. }
  601. if (UpdateTaskState(TaskState.Completed))
  602. {
  603. var dir = new DirectoryInfo(localDir);
  604. foreach (var file in dir.EnumerateFiles("." + localFileName + ".cosresumable.*")) {
  605. file.Delete();
  606. }
  607. if (resumableTaskFile != null)
  608. {
  609. FileInfo info = new FileInfo(resumableTaskFile);
  610. if (info.Exists)
  611. {
  612. info.Delete();
  613. }
  614. }
  615. //outputStream.Close();
  616. DownloadTaskResult downloadTaskResult = new DownloadTaskResult();
  617. downloadTaskResult.SetResult(downloadResult);
  618. if (successCallback != null)
  619. {
  620. successCallback(downloadTaskResult);
  621. }
  622. return;
  623. } else {
  624. // 容灾 return
  625. DownloadTaskResult downloadTaskResult = new DownloadTaskResult();
  626. downloadTaskResult.SetResult(downloadResult);
  627. //outputStream.Close();
  628. if (successCallback != null)
  629. {
  630. successCallback(downloadTaskResult);
  631. }
  632. }
  633. }
  634. return;
  635. }
  636. private void RealCancle()
  637. {
  638. // 停止可能进行中的Head请求
  639. cosXmlServer.Cancel(headObjectRequest);
  640. // 停止可能进行中的下载线程
  641. foreach (GetObjectRequest subGetObjectRequest in getObjectRequestsList) {
  642. cosXmlServer.Cancel(subGetObjectRequest);
  643. }
  644. getObjectRequestsList.Clear();
  645. }
  646. private void Clear()
  647. {
  648. // delete tmp files
  649. var dir = new DirectoryInfo(localDir);
  650. foreach (var file in dir.EnumerateFiles("." + localFileName + ".cosresumable.*")) {
  651. file.Delete();
  652. }
  653. // delete resumable file
  654. if (resumableTaskFile != null)
  655. {
  656. FileInfo info = new FileInfo(resumableTaskFile);
  657. if (info.Exists)
  658. {
  659. info.Delete();
  660. }
  661. }
  662. }
  663. public override void Pause()
  664. {
  665. if (UpdateTaskState(TaskState.Pause))
  666. {
  667. //exit download
  668. lock (syncExit)
  669. {
  670. isExit = true;
  671. }
  672. //cancle request
  673. RealCancle();
  674. }
  675. }
  676. public override void Cancel()
  677. {
  678. if (UpdateTaskState(TaskState.Cancel))
  679. {
  680. //exit copy
  681. lock (syncExit)
  682. {
  683. isExit = true;
  684. }
  685. //cancle request
  686. RealCancle();
  687. //clear recoder
  688. Clear();
  689. // throw exception if requested
  690. if (throwExceptionIfCancelled) {
  691. throw new CosClientException((int)CosClientError.UserCancelled, "Download Task Cancelled by user");
  692. }
  693. }
  694. }
  695. public override void Resume()
  696. {
  697. if (UpdateTaskState(TaskState.Resume))
  698. {
  699. lock (syncExit)
  700. {
  701. //continue to download
  702. isExit = false;
  703. }
  704. Download();
  705. }
  706. }
  707. public class DownloadTaskResult : CosResult
  708. {
  709. public string eTag;
  710. public void SetResult(GetObjectResult result)
  711. {
  712. this.eTag = result.eTag;
  713. this.httpCode = result.httpCode;
  714. this.httpMessage = result.httpMessage;
  715. this.responseHeaders = result.responseHeaders;
  716. }
  717. public override string GetResultInfo()
  718. {
  719. return base.GetResultInfo() + ("\n : ETag: " + eTag);
  720. }
  721. }
  722. public sealed class DownloadResumableInfo
  723. {
  724. public string lastModified;
  725. public long contentLength;
  726. public string eTag;
  727. public string crc64ecma;
  728. public List<DownloadSliceStruct> slicesDownloaded = new List<DownloadSliceStruct>();
  729. public static DownloadResumableInfo LoadFromResumableFile(string taskFile)
  730. {
  731. try
  732. {
  733. using (FileStream stream = File.OpenRead(taskFile))
  734. {
  735. DownloadResumableInfo resumableInfo = XmlParse.Deserialize<DownloadResumableInfo>(stream);
  736. return resumableInfo;
  737. }
  738. }
  739. catch (System.Exception)
  740. {
  741. return null;
  742. }
  743. }
  744. public void Persist(string taskFile)
  745. {
  746. try
  747. {
  748. resumableFileWriteLock.EnterWriteLock();
  749. string xml = XmlBuilder.Serialize(this);
  750. using (FileStream stream = File.Create(taskFile))
  751. {
  752. byte[] info = new UTF8Encoding(true).GetBytes(xml);
  753. stream.Write(info, 0, info.Length);
  754. }
  755. }
  756. catch (Exception)
  757. {
  758. return;
  759. }
  760. finally
  761. {
  762. resumableFileWriteLock.ExitWriteLock();
  763. }
  764. }
  765. }
  766. }
  767. }