vCloudScanDataV2.cs 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  1. using SkiaSharp;
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Http;
  9. using System.Text;
  10. using System.Text.Json;
  11. using System.Threading;
  12. using System.Xml.Serialization;
  13. using Vinno.IUS.Common.Log;
  14. using Vinno.vCloud.Common.FIS.Helper;
  15. using Vinno.vCloud.Common.FIS.Storages;
  16. using Vinno.vCloud.Common.Storage.ObjectStorageInfo;
  17. using Vinno.vCloud.Common.Vid2;
  18. using Vinno.vCloud.FIS.CrossPlatform.Common.Helper;
  19. using Vinno.vCloud.FIS.CrossPlatform.Common.SQLite;
  20. using Vinno.vCloud.Protocol.Infrastructures;
  21. using WingInterfaceLibrary.DTO.Comment;
  22. using WingInterfaceLibrary.DTO.Common;
  23. using WingInterfaceLibrary.DTO.Measure;
  24. using WingInterfaceLibrary.DTO.Record;
  25. using WingInterfaceLibrary.DTO.Storage;
  26. using WingInterfaceLibrary.Enum;
  27. using WingInterfaceLibrary.Interface;
  28. using WingInterfaceLibrary.LiveConsultation;
  29. using WingInterfaceLibrary.Request.Examine;
  30. using WingInterfaceLibrary.Request.Storage;
  31. using WingInterfaceLibrary.ResearchEdition;
  32. using WingInterfaceLibrary.Result.Examine;
  33. namespace Vinno.vCloud.Common.FIS.Remedicals
  34. {
  35. internal class vCloudScanDataV2 : IvCloudScanData, IDisposable
  36. {
  37. private UploadStatus _status;
  38. private readonly IRemedicalService _remedicalService;
  39. private readonly IStorageService _storageService;
  40. private readonly IDeviceService _deviceService;
  41. private readonly string _token;
  42. private bool _disposed;
  43. private const string BreastImageLocation = "BreastImageLocation";
  44. private const string LiverImageLocation = "LiverImageLocation";
  45. private const string ThyroidImageLocation = "ThyroidImageLocation";
  46. private Action<ExamRecordCache> _updateExamRecordCache;
  47. private Action<ExamRecordCacheForResearch> _updateExamRecordCacheForResearch;
  48. private Func<string, ResearchProjectDTO> _getResearchProjectDTO;
  49. /// <inheritdoc />
  50. /// <summary>
  51. /// Gets the id of this scan data.
  52. /// </summary>
  53. public string Id { get; }
  54. /// <inheritdoc />
  55. /// <summary>
  56. /// Gets the examId of this scan data.
  57. /// </summary>
  58. public string ExamId { get; }
  59. /// <inheritdoc />
  60. /// <summary>
  61. /// Gets the patientId in Ultrasound Machine
  62. /// </summary>
  63. public string PatientId { get; }
  64. /// <summary>
  65. /// Get or set the Patient Id In Server
  66. /// </summary>
  67. public string PatientIdInServer { get; set; }
  68. /// <inheritdoc />
  69. /// <summary>
  70. /// Gets or sets the ExamRecordId for this scan data.
  71. /// </summary>
  72. public string ExamRecordId { get; set; }
  73. /// <inheritdoc />
  74. /// <summary>
  75. /// Gets the workorder Id.
  76. /// </summary>
  77. public string WorkOrderId { get; }
  78. /// <summary>
  79. /// 项目Id
  80. /// </summary>
  81. public string ProjectCode { get; set; }
  82. /// <summary>
  83. /// Img List
  84. /// </summary>
  85. public List<ImageItem> ImageList { get; set; }
  86. public Dictionary<string, string> URMParameters { get; set; }
  87. /// <summary>
  88. /// Raised when the status changed.
  89. /// </summary>
  90. public event EventHandler StatusChanged;
  91. /// <inheritdoc />
  92. /// <summary>
  93. /// Gets the status of this Scan data.
  94. /// </summary>
  95. public UploadStatus Status
  96. {
  97. get => _status;
  98. internal set
  99. {
  100. if (_status != value)
  101. {
  102. var old = _status;
  103. _status = value;
  104. if (_status == UploadStatus.Uploaded || _status == UploadStatus.Deleted)
  105. {
  106. Delete(old);
  107. }
  108. else
  109. {
  110. Move(old, _status);
  111. }
  112. OnStatusChanged();
  113. }
  114. }
  115. }
  116. /// <inheritdoc />
  117. /// <summary>
  118. /// Gets the vid file path.
  119. /// </summary>
  120. public string VidFilePath { get; private set; }
  121. /// <summary>
  122. /// Gets the sd file path.
  123. /// </summary>
  124. public string ScanDataFilePath { get; private set; }
  125. /// <inheritdoc />
  126. /// <summary>
  127. /// Gets the description of this scan data.
  128. /// </summary>
  129. public string Description { get; }
  130. /// <inheritdoc />
  131. /// <summary>
  132. /// Gets the application of this scan data.
  133. /// </summary>
  134. public string Application { get; }
  135. /// <summary>
  136. /// Gets the Application Category of this scan data
  137. /// </summary>
  138. public string ApplicationCategory { get; }
  139. /// <inheritdoc />
  140. /// <summary>
  141. /// Gets the data type of this scan data.
  142. /// </summary>
  143. public VidType DataType { get; }
  144. /// <inheritdoc />
  145. /// <summary>
  146. /// Gets the patient info for this scan data.
  147. /// </summary>
  148. public PatientScanInfo PatientInfo { get; }
  149. /// <summary>
  150. /// Get the Measure Results
  151. /// </summary>
  152. public MeasuredResultsDTO MeasuredResults { get; }
  153. /// <summary>
  154. /// Get the comment Results
  155. /// </summary>
  156. public ScanImageDTO CommentResults { get; }
  157. /// <summary>
  158. /// 是否能重新创建检查(只有魔盒的截图版,且非Server指定的检查才可以)
  159. /// </summary>
  160. public bool CanRecreateExamRecord { get; }
  161. /// <summary>
  162. /// 是否是截图版(只有魔盒有)
  163. /// </summary>
  164. public bool IsScreenShotVersion { get; }
  165. /// <summary>
  166. /// 是否是会诊图像
  167. /// </summary>
  168. public bool IsConsultationData { get; }
  169. private bool _isUploadFail;
  170. public vCloudScanDataV2(string patientId, IvCloudExamRecord record, string id, string vidFilePath,
  171. VidType dataType, string description, PatientScanInfo patientInfo, UploadStatus status, IRemedicalService remedicalService, IStorageService storageService, IDeviceService deviceService, string token, Action<ExamRecordCache> updateExamRecordCache, MeasuredResultsDTO measuredResults, ScanImageDTO commentResults, bool isScreenShotVersion, bool canRecreateExamRecord, bool isConsultationData, string projectCode, List<ImageItem> imageList, Action<ExamRecordCacheForResearch> updateExamRecordCacheForResearch, Dictionary<string, string> urmParameters, Func<string, ResearchProjectDTO> getResearchProjectDTO)
  172. {
  173. PatientId = patientId;
  174. _status = status;
  175. _token = token;
  176. _remedicalService = remedicalService;
  177. _storageService = storageService;
  178. _deviceService = deviceService;
  179. ExamRecordId = record.Id;
  180. ExamId = record.ExamId;
  181. WorkOrderId = record.WorkOrderId;
  182. Id = id;
  183. DataType = dataType;
  184. Description = description;
  185. PatientInfo = patientInfo;
  186. MeasuredResults = measuredResults;
  187. CommentResults = commentResults;
  188. IsScreenShotVersion = isScreenShotVersion;
  189. CanRecreateExamRecord = canRecreateExamRecord;
  190. IsConsultationData = isConsultationData;
  191. ProjectCode = projectCode;
  192. _updateExamRecordCache = updateExamRecordCache;
  193. _updateExamRecordCacheForResearch = updateExamRecordCacheForResearch;
  194. _getResearchProjectDTO = getResearchProjectDTO;
  195. ImageList = imageList;
  196. URMParameters = urmParameters;
  197. //Generate files.
  198. VidFilePath = GetVidFilePath(_status);
  199. if (string.Compare(VidFilePath, vidFilePath, StringComparison.OrdinalIgnoreCase) != 0)
  200. {
  201. File.Move(vidFilePath, VidFilePath);
  202. }
  203. if (description == "FromSonopost")
  204. {
  205. Application = string.Empty;
  206. ApplicationCategory = description;
  207. }
  208. else
  209. {
  210. using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open))
  211. {
  212. ApplicationCategory = vinnoImage.Probe?.Application?.ApplicationCategoryName;
  213. Application = vinnoImage.Probe?.Application?.ApplicationName;
  214. }
  215. }
  216. //Save self.
  217. Save();
  218. }
  219. ~vCloudScanDataV2()
  220. {
  221. DoDispose();
  222. }
  223. private void OnStatusChanged()
  224. {
  225. StatusChanged?.Invoke(this, EventArgs.Empty);
  226. }
  227. /// <summary>
  228. /// Delete this scanData and its files.
  229. /// </summary>
  230. public void Delete()
  231. {
  232. Status = UploadStatus.Deleted;
  233. }
  234. /// <summary>
  235. /// Force update the scan data's status.
  236. /// </summary>
  237. /// <param name="status">The new status</param>
  238. public void ForceUpdateStaus(UploadStatus status)
  239. {
  240. Status = status;
  241. }
  242. /// <summary>
  243. /// Force update and save the scan data.
  244. /// </summary>
  245. public void Update()
  246. {
  247. Save();
  248. }
  249. /// <summary>
  250. /// Get saved scan data file path
  251. /// </summary>
  252. /// <param name="status"></param>
  253. /// <returns></returns>
  254. private string GetScanDataFilePath(UploadStatus status)
  255. {
  256. if (status == UploadStatus.Deleted)
  257. {
  258. throw new InvalidOperationException("File already deleted.");
  259. }
  260. var subFolder = string.Empty;
  261. if (status == UploadStatus.Idle || status == UploadStatus.Waiting || status == UploadStatus.Uploading)
  262. {
  263. subFolder = "Upload";
  264. }
  265. if (status == UploadStatus.Fail || status == UploadStatus.FailBecauseExamIsFinished)
  266. {
  267. subFolder = "Failed";
  268. }
  269. var folder = GetWorkingFolder(subFolder);
  270. var fileName = $"{Id}.sd";
  271. return Path.Combine(folder, fileName);
  272. }
  273. private string GetWorkingFolder(string subFolder)
  274. {
  275. var folder = Path.Combine(vCloudTerminalV2.WorkingFolder, "RemedicalV2", subFolder);
  276. DirectoryHelper.CreateDirectory(folder);
  277. return folder;
  278. }
  279. /// <summary>
  280. /// Get the vid file path.
  281. /// </summary>
  282. /// <param name="status"></param>
  283. /// <returns></returns>
  284. private string GetVidFilePath(UploadStatus status)
  285. {
  286. if (status == UploadStatus.Deleted)
  287. {
  288. throw new InvalidOperationException("File already deleted.");
  289. }
  290. var subFolder = string.Empty;
  291. if (status == UploadStatus.Idle || status == UploadStatus.Waiting || status == UploadStatus.Uploading)
  292. {
  293. subFolder = "Upload";
  294. }
  295. if (status == UploadStatus.Fail || status == UploadStatus.FailBecauseExamIsFinished)
  296. {
  297. subFolder = "Failed";
  298. }
  299. var folder = GetWorkingFolder(subFolder);
  300. var fileName = $"{Id}.vid";
  301. return Path.Combine(folder, fileName);
  302. }
  303. /// <summary>
  304. /// Delete the files
  305. /// </summary>
  306. /// <param name="status"></param>
  307. private void Delete(UploadStatus status)
  308. {
  309. var scanDataFilePath = GetScanDataFilePath(status);
  310. var vidFilePath = GetVidFilePath(status);
  311. FileHelper.DeleteFile(scanDataFilePath);
  312. FileHelper.DeleteFile(vidFilePath);
  313. }
  314. /// <summary>
  315. /// Save scan data and vid file to the disk by current status.
  316. /// </summary>
  317. private void Save()
  318. {
  319. ScanDataFilePath = GetScanDataFilePath(Status);
  320. this.Serialize(ScanDataFilePath);
  321. }
  322. /// <summary>
  323. /// Move scan data from one to another
  324. /// </summary>
  325. /// <param name="source"></param>
  326. /// <param name="dest"></param>
  327. private void Move(UploadStatus source, UploadStatus dest)
  328. {
  329. var sourceScanDataFilePath = GetScanDataFilePath(source);
  330. var sourceVidFilePath = GetVidFilePath(source);
  331. var destScanDataFilePath = GetScanDataFilePath(dest);
  332. var destVidFilePath = GetVidFilePath(dest);
  333. if (File.Exists(sourceScanDataFilePath) && !File.Exists(destScanDataFilePath))
  334. {
  335. File.Move(sourceScanDataFilePath, destScanDataFilePath);
  336. ScanDataFilePath = destScanDataFilePath;
  337. }
  338. if (File.Exists(sourceVidFilePath) && !File.Exists(destVidFilePath))
  339. {
  340. File.Move(sourceVidFilePath, destVidFilePath);
  341. VidFilePath = destVidFilePath;
  342. }
  343. }
  344. /// <summary>
  345. /// Upload the data to the vCloud server.
  346. /// </summary>
  347. public void Upload(CancellationTokenSource cancellationTokenSource)
  348. {
  349. string previewFileToken = string.Empty;
  350. string coverImageToken = string.Empty;
  351. ImageLocationDTO imageLocationDTO = null;
  352. VinnoImage vinnoImage = null;
  353. VinnoImage compressedImage = null;
  354. var vidFileNameWithoutExtension = Path.GetFileNameWithoutExtension(VidFilePath);
  355. if (vCloudServerConfig.Instance.IsUploadThumbnail)
  356. {
  357. using (var vinnoImageData = new VinnoImageData(VidFilePath, OperationMode.Open))
  358. {
  359. if (vinnoImageData.ImageCount <= 0)
  360. {
  361. throw new FileNotFoundException("The VinnoImageData Image Count <=0");
  362. }
  363. vinnoImage = vinnoImageData.GetImage(0);
  364. compressedImage = Compress(vinnoImage);
  365. }
  366. var firstImageName = "firstImage_" + vidFileNameWithoutExtension + ".jpg";
  367. var storageUrl = UploadFile(cancellationTokenSource, firstImageName, vinnoImage?.ImageData);
  368. if (string.IsNullOrWhiteSpace(storageUrl))
  369. {
  370. throw new InvalidOperationException($"Get token failed when Uploading first image");
  371. }
  372. coverImageToken = storageUrl;
  373. var previewImageName = "preview_" + vidFileNameWithoutExtension + ".jpg";
  374. storageUrl = UploadFile(cancellationTokenSource, previewImageName, compressedImage?.ImageData);
  375. if (string.IsNullOrWhiteSpace(storageUrl))
  376. {
  377. throw new InvalidOperationException($"Get token failed when Uploading preview image");
  378. }
  379. previewFileToken = storageUrl;
  380. imageLocationDTO = GetImageLocation(cancellationTokenSource);
  381. }
  382. else
  383. {
  384. using (var vinnoImageData = new VinnoImageData(VidFilePath, OperationMode.Open))
  385. {
  386. if (vinnoImageData.ImageCount <= 0)
  387. {
  388. throw new FileNotFoundException("The VinnoImageData Image Count <=0");
  389. }
  390. }
  391. }
  392. var url = UploadFile(cancellationTokenSource, VidFilePath);
  393. if (string.IsNullOrWhiteSpace(url))
  394. {
  395. throw new InvalidOperationException("Get token failed when Uploading VID file");
  396. }
  397. var fileType = RemedicalFileDataTypeEnum.VinnoVidSingle;
  398. switch (DataType)
  399. {
  400. case VidType.VinnoVidMovie:
  401. fileType = RemedicalFileDataTypeEnum.VinnoVidMovie;
  402. break;
  403. case VidType.ThirdVidSingle:
  404. fileType = RemedicalFileDataTypeEnum.ThirdVidSingle;
  405. break;
  406. case VidType.ThirdVidMovie:
  407. fileType = RemedicalFileDataTypeEnum.ThirdVidMovie;
  408. break;
  409. }
  410. var fileInfo = new FileInfo(VidFilePath);
  411. var fileSize = fileInfo.Length;
  412. if (IsConsultationData)
  413. {
  414. var uploadConsultationDataRequest = new UploadConsultationDataRequest
  415. {
  416. Token = _token,
  417. ConsultationCode = ExamRecordId,
  418. FileToken = url,
  419. FileSize = fileSize,
  420. Application = Application,
  421. ApplicationCategory = ApplicationCategory,
  422. FileDataType = fileType,
  423. PreviewFileToken = previewFileToken,
  424. CoverImageToken = coverImageToken,
  425. MeasuredResult = MeasuredResults,
  426. CommentResult = CommentResults,
  427. };
  428. if (cancellationTokenSource.IsCancellationRequested)
  429. {
  430. throw new OperationCanceledException("Cancelled");
  431. }
  432. var result = JsonRpcHelper.UploadConsultationData(_deviceService, uploadConsultationDataRequest);
  433. if (!result)
  434. {
  435. throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is false");
  436. }
  437. else
  438. {
  439. Status = UploadStatus.Uploaded;
  440. return;
  441. }
  442. }
  443. else
  444. {
  445. List<ImgItem> imgList = new List<ImgItem>();
  446. if (ImageList != null)
  447. {
  448. foreach (var image in ImageList)
  449. {
  450. var imageInfo = new FileInfo(image.FilePath);
  451. var imageSize = imageInfo.Length;
  452. var imageToken = UploadFile(cancellationTokenSource, image.FilePath);
  453. if (string.IsNullOrWhiteSpace(imageToken))
  454. {
  455. throw new InvalidOperationException($"Get token failed when Uploading image file: {image.FilePath}");
  456. }
  457. imgList.Add(new ImgItem
  458. {
  459. FileToken = imageToken,
  460. ImgType = image.ImgType,
  461. FileSize = imageSize,
  462. });
  463. }
  464. }
  465. var uploadExamDataRequest = new UploadExamDataRequest
  466. {
  467. Token = _token,
  468. ExamCode = ExamRecordId,
  469. FileToken = url,
  470. FileSize = fileSize,
  471. Application = Application,
  472. ApplicationCategory = ApplicationCategory,
  473. FileDataType = fileType,
  474. PreviewFileToken = previewFileToken,
  475. CoverImageToken = coverImageToken,
  476. MeasuredResult = MeasuredResults,
  477. CommentResult = CommentResults,
  478. ImageLocation = imageLocationDTO,
  479. ExamTime = PatientInfo.ExamTime,
  480. ProjectCode = ProjectCode,
  481. ImgList = imgList,
  482. UrmParameters = URMParameters,
  483. };
  484. if (cancellationTokenSource.IsCancellationRequested)
  485. {
  486. throw new OperationCanceledException("Cancelled");
  487. }
  488. var result = JsonRpcHelper.UploadExamData(_remedicalService, uploadExamDataRequest);
  489. if (result == null)
  490. {
  491. throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is null");
  492. }
  493. else if (result.IsSuccess)
  494. {
  495. Status = UploadStatus.Uploaded;
  496. return;
  497. }
  498. else if (!result.IsSuccess)
  499. {
  500. if (result.ErrorCode == 4002)//检查已结束扫查,无法继续传图
  501. {
  502. if (!CanRecreateExamRecord)
  503. {
  504. Status = UploadStatus.FailBecauseExamIsFinished;
  505. Save();
  506. return;
  507. }
  508. else
  509. {
  510. if (CreateExamRecord(string.Empty, out var newExamRecordId))
  511. {
  512. ExamRecordId = newExamRecordId;
  513. uploadExamDataRequest.ExamCode = ExamRecordId;
  514. if (string.IsNullOrEmpty(ProjectCode))
  515. {
  516. var examRecordCache = new ExamRecordCache(ExamId, newExamRecordId, WorkOrderId);
  517. _updateExamRecordCache(examRecordCache);
  518. }
  519. else
  520. {
  521. var examRecordCacheForResearch = new ExamRecordCacheForResearch(ExamId, newExamRecordId, WorkOrderId, ProjectCode);
  522. _updateExamRecordCacheForResearch(examRecordCacheForResearch);
  523. }
  524. result = JsonRpcHelper.UploadExamData(_remedicalService, uploadExamDataRequest);
  525. if (result == null)
  526. {
  527. throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is null");
  528. }
  529. else if (result.IsSuccess)
  530. {
  531. Status = UploadStatus.Uploaded;
  532. return;
  533. }
  534. else
  535. {
  536. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail,Error Code is {result.ErrorCode}");
  537. }
  538. }
  539. else
  540. {
  541. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail:FailBecauseExamIsFinished and ReCreateExamRecord Result is false");
  542. }
  543. }
  544. }
  545. else if (result.ErrorCode == 4015)//会诊记录不存在
  546. {
  547. if (CreateExamRecord(ExamRecordId, out var newExamRecordId))
  548. {
  549. if (ExamRecordId == newExamRecordId)
  550. {
  551. result = JsonRpcHelper.UploadExamData(_remedicalService, uploadExamDataRequest);
  552. if (result == null)
  553. {
  554. throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is null");
  555. }
  556. else if (result.IsSuccess)
  557. {
  558. Status = UploadStatus.Uploaded;
  559. return;
  560. }
  561. else
  562. {
  563. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail,Error Code is {result.ErrorCode}");
  564. }
  565. }
  566. else
  567. {
  568. if (string.IsNullOrEmpty(ProjectCode))
  569. {
  570. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail:ReCreateExamRecord Result is false,OldExamRecordCode:{ExamRecordId},newExamRecordCode:{newExamRecordId}");
  571. }
  572. else
  573. {
  574. ExamRecordId = newExamRecordId;
  575. uploadExamDataRequest.ExamCode = newExamRecordId;
  576. var examRecordCacheForResearch = new ExamRecordCacheForResearch(ExamId, newExamRecordId, WorkOrderId, ProjectCode);
  577. _updateExamRecordCacheForResearch(examRecordCacheForResearch);
  578. result = JsonRpcHelper.UploadExamData(_remedicalService, uploadExamDataRequest);
  579. if (result == null)
  580. {
  581. throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is null");
  582. }
  583. else if (result.IsSuccess)
  584. {
  585. Status = UploadStatus.Uploaded;
  586. return;
  587. }
  588. else
  589. {
  590. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail,Error Code is {result.ErrorCode}");
  591. }
  592. }
  593. }
  594. }
  595. else
  596. {
  597. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail:ReCreateExamRecord Result is false");
  598. }
  599. }
  600. else
  601. {
  602. throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail,Error Code is {result.ErrorCode}");
  603. }
  604. }
  605. }
  606. Status = UploadStatus.Fail;
  607. }
  608. private ImageLocationDTO GetImageLocation(CancellationTokenSource cancellationTokenSource)
  609. {
  610. if (cancellationTokenSource.IsCancellationRequested)
  611. {
  612. throw new OperationCanceledException("Cancelled");
  613. }
  614. using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open))
  615. {
  616. var imageLocationExtenedData = VidExtendedData.FromBytes(vinnoImage.ExtendedData);
  617. if (imageLocationExtenedData != null && imageLocationExtenedData.Data != null)
  618. {
  619. var positionTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == "Position" && t.Group == BreastImageLocation);
  620. var quadrantTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == "Quadrant" && t.Group == BreastImageLocation);
  621. if (positionTag != null && quadrantTag != null)
  622. {
  623. if (Enum.TryParse<Position>(imageLocationExtenedData.Data[positionTag].GetValue()?.ToString(), out var positionEnum)
  624. && Enum.TryParse<QuadrantEnum>(imageLocationExtenedData.Data[quadrantTag].GetValue()?.ToString(), out var quadrantEnum))
  625. {
  626. return new ImageLocationDTO
  627. {
  628. Position = positionEnum.ToString(),
  629. Quadrant = quadrantEnum.ToString(),
  630. Group = positionTag.Group,
  631. };
  632. }
  633. }
  634. positionTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == "Position" && t.Group == LiverImageLocation);
  635. if (positionTag != null)
  636. {
  637. if (Enum.TryParse<LiverPosition>(imageLocationExtenedData.Data[positionTag].GetValue()?.ToString(), out var positionEnum))
  638. {
  639. return new ImageLocationDTO
  640. {
  641. Position = positionEnum.ToString(),
  642. Group = positionTag.Group,
  643. };
  644. }
  645. }
  646. positionTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == FISTissueCategory.Position && !string.IsNullOrWhiteSpace(t.Group));
  647. var groupName = positionTag?.Group;
  648. if (positionTag != null && !imageLocationExtenedData.Data.Keys.Any(x => x.Group == groupName && x.Element != FISTissueCategory.Position))
  649. {
  650. return new ImageLocationDTO
  651. {
  652. Group = groupName,
  653. Position = imageLocationExtenedData.Data[positionTag].GetValue()?.ToString(),
  654. };
  655. }
  656. }
  657. }
  658. return null;
  659. }
  660. /// <inheritdoc />
  661. /// <summary>
  662. /// Create an exam record from vCloud.
  663. /// </summary>
  664. /// <param name="examId">The local excam id.</param>
  665. /// <param name="workOrderId">The workerId</param>
  666. /// <param name="patientInfo">The patient basic info for creating the exam record.</param>
  667. /// <returns><see cref="T:Vinno.vCloud.Terminal.Remedicals.IvCloudExamRecord" /></returns>
  668. private bool CreateExamRecord(string examRecordId, out string newExamRecordId)
  669. {
  670. CreateExaminfoRequest createExaminfoRequest = null;
  671. var patientType = vCloudServerConfig.Instance.PatientType;
  672. if (!string.IsNullOrEmpty(ProjectCode))
  673. {
  674. var researchProjectDTO = _getResearchProjectDTO.Invoke(ProjectCode);
  675. if (researchProjectDTO != null)
  676. {
  677. patientType = researchProjectDTO.PatientType;
  678. }
  679. else
  680. {
  681. newExamRecordId = string.Empty;
  682. Logger.WriteLineError($"GetResearchProjectDTO Fail, result is null, ProjectCode:{ProjectCode} ");
  683. return false;
  684. }
  685. }
  686. createExaminfoRequest = new CreateExaminfoRequest
  687. {
  688. Token = _token,
  689. PatientType = patientType.ToString(),
  690. ExamRecordCode = examRecordId,
  691. PatientInfo = DTOConverter.RenderPatientInfo(PatientInfo, ExamId, patientType),
  692. PatientScanInfoList = DTOConverter.RenderPatientScanInfo(PatientInfo),
  693. IsScreenshotVersion = IsScreenShotVersion,
  694. PatientCode = PatientIdInServer,
  695. ExamTime = PatientInfo.ExamTime,
  696. ProjectCode = ProjectCode,
  697. };
  698. CreateExaminfoResult result = JsonRpcHelper.CreateExamInfo(_remedicalService, createExaminfoRequest);
  699. if (result == null || string.IsNullOrEmpty(result.ExamCode))
  700. {
  701. newExamRecordId = string.Empty;
  702. return false;
  703. }
  704. else
  705. {
  706. newExamRecordId = result.ExamCode;
  707. return true;
  708. }
  709. }
  710. private string UploadFile(CancellationTokenSource cancellationTokenSource, string filePath)
  711. {
  712. FlieSpliceInfo fileSpliceInfo = null;
  713. var resultDic = new ConcurrentDictionary<int, PartInfo>();
  714. int divisionSize = 10 * 1024 * 1024;//10MB分一块
  715. _isUploadFail = false;
  716. try
  717. {
  718. if (cancellationTokenSource.IsCancellationRequested)
  719. {
  720. throw new OperationCanceledException("Cancelled");
  721. }
  722. var fileName = Path.GetFileName(filePath);
  723. fileSpliceInfo = UploadFileHelper.ReadFileData(filePath, divisionSize, 0);
  724. if (fileSpliceInfo == null)
  725. {
  726. throw new ArgumentNullException("fileSpliceInfo is null");
  727. }
  728. if (fileSpliceInfo.TotalCount == 1)
  729. {
  730. return UploadFile(cancellationTokenSource, filePath, fileName);
  731. }
  732. else
  733. {
  734. var extension = Path.GetExtension(filePath);
  735. var newFileName = Guid.NewGuid().ToString("N").ToUpper() + extension;
  736. var fileServiceRequest = new FileServiceRequest
  737. {
  738. FileName = newFileName,
  739. IsRechristen = false,
  740. Token = _token,
  741. RequestMethod = "post",
  742. };
  743. var authorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, fileServiceRequest);
  744. if (authorizationInfo == null)
  745. {
  746. throw new Exception("GetAuthorization Error,AuthorizationInfo is null ");
  747. }
  748. var url = authorizationInfo.StorageUrl + "?uploads";
  749. InitiateMultipartUploadResult initiateMultipartUploadResult = null;
  750. using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri(url)))
  751. {
  752. request.Headers.TryAddWithoutValidation("Authorization", authorizationInfo.Authorization);
  753. request.Headers.TryAddWithoutValidation("Content-Type", "application/x-zip-compressed");
  754. request.Headers.TryAddWithoutValidation("Content-Length", "0");
  755. using (var httpClient = new HttpClient())
  756. {
  757. httpClient.Timeout = TimeSpan.FromMinutes(5);
  758. var response = httpClient.SendAsync(request, cancellationTokenSource.Token).GetAwaiter().GetResult();
  759. if (response != null && response.StatusCode == HttpStatusCode.OK)
  760. {
  761. if (cancellationTokenSource.IsCancellationRequested)
  762. {
  763. throw new OperationCanceledException("Cancelled");
  764. }
  765. var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
  766. try
  767. {
  768. using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result.Replace("\\\"", "'").Replace("\\r", "").Replace("\\n", "").Replace("utf-16", "utf-8"))))
  769. {
  770. var xml = new XmlSerializer(typeof(InitiateMultipartUploadResult));
  771. initiateMultipartUploadResult = (InitiateMultipartUploadResult)xml.Deserialize(stream);
  772. Logger.WriteLineInfo($"InitiateMultipartUpload Success,InitiateMultipartUploadResult:{JsonSerializer.Serialize(initiateMultipartUploadResult)}");
  773. }
  774. }
  775. catch (Exception ex)
  776. {
  777. Logger.WriteLineError($"InitiateMultipartUpload Error,result:{result?.Trim()},Error:{ex.Message}");
  778. return UploadFile(cancellationTokenSource, filePath, fileName);
  779. }
  780. }
  781. else
  782. {
  783. Logger.WriteLineError($"InitiateMultipartUpload Fail,StatusCode:{response?.StatusCode},Start to normal upload.");
  784. return UploadFile(cancellationTokenSource, filePath, fileName);
  785. }
  786. }
  787. }
  788. //分块上传
  789. fileSpliceInfo.FileSpilceDic.Keys.OrderBy(c => c).AsParallel().WithDegreeOfParallelism(5).ForAll((key) =>
  790. {
  791. MultiPartUpload(cancellationTokenSource, key, resultDic, newFileName, initiateMultipartUploadResult?.UploadId, fileSpliceInfo.FileSpilceDic[key]);
  792. });
  793. bool isReadAllBytes = fileSpliceInfo.IsReadAllBytes;
  794. int fileCount = 0;
  795. while (!isReadAllBytes)
  796. {
  797. if (resultDic.Count - fileCount != fileSpliceInfo.FileSpilceDic.Count)
  798. {
  799. Logger.WriteLineInfo($"MultipartUpload Fail:FileSpilceDic Count:{fileSpliceInfo.TotalCount},resultDic Count:{resultDic.Count}");
  800. return string.Empty;
  801. }
  802. fileCount = resultDic.Count;
  803. fileSpliceInfo = UploadFileHelper.ReadFileData(filePath, divisionSize, fileCount);
  804. if (fileSpliceInfo == null)
  805. {
  806. throw new ArgumentNullException("fileSpliceInfo is null");
  807. }
  808. fileSpliceInfo.FileSpilceDic.Keys.OrderBy(c => c).AsParallel().WithDegreeOfParallelism(5).ForAll((key) =>
  809. {
  810. MultiPartUpload(cancellationTokenSource, key, resultDic, newFileName, initiateMultipartUploadResult?.UploadId, fileSpliceInfo.FileSpilceDic[key]);
  811. });
  812. isReadAllBytes = fileSpliceInfo.IsReadAllBytes;
  813. }
  814. if (resultDic.Count == fileSpliceInfo.TotalCount)
  815. {
  816. return CompleteMultipartUpload(cancellationTokenSource, resultDic, newFileName, authorizationInfo, initiateMultipartUploadResult);
  817. }
  818. else
  819. {
  820. Logger.WriteLineInfo($"MultipartUpload Fail:total Count:{fileSpliceInfo.TotalCount},resultDic Count:{resultDic.Count}");
  821. return string.Empty;
  822. }
  823. }
  824. }
  825. catch (Exception ex)
  826. {
  827. Logger.WriteLineError($"Upload File :{filePath} Error:{ex}");
  828. return string.Empty;
  829. }
  830. }
  831. private void MultiPartUpload(CancellationTokenSource cancellationTokenSource, int key, ConcurrentDictionary<int, PartInfo> resultDic, string fileName, string uploadId, byte[] bytes)
  832. {
  833. var index = key + 1;
  834. try
  835. {
  836. if (cancellationTokenSource.IsCancellationRequested || _isUploadFail)
  837. {
  838. Logger.WriteLineError($"MultiPartUpload PartNumber:{index} Cancelled");
  839. return;
  840. }
  841. var requestParams = new List<DataItemDTO>()
  842. {
  843. new DataItemDTO
  844. {
  845. Key = "partNumber",
  846. Value = index.ToString()
  847. },
  848. new DataItemDTO
  849. {
  850. Key = "uploadId",
  851. Value =uploadId
  852. }
  853. };
  854. var multipartUploadFileServiceRequest = new FileServiceRequest
  855. {
  856. FileName = fileName,
  857. IsRechristen = false,
  858. Token = _token,
  859. UrlParams = requestParams,
  860. };
  861. var multipartUploadAuthorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, multipartUploadFileServiceRequest, false);
  862. if (multipartUploadAuthorizationInfo == null)
  863. {
  864. throw new Exception("GetAuthorization Error,MultipartUploadAuthorizationInfo is null ");
  865. }
  866. if (cancellationTokenSource.IsCancellationRequested)
  867. {
  868. Logger.WriteLineError($"MultiPartUpload PartNumber:{index} Cancelled");
  869. return;
  870. }
  871. var multipartUploadUrl = $"{multipartUploadAuthorizationInfo.StorageUrl}?partNumber={index}&uploadId={uploadId}";
  872. Tuple<bool, string> uploadResult;
  873. try
  874. {
  875. uploadResult = UploadFileHelper.UploadFile(multipartUploadUrl, bytes, multipartUploadAuthorizationInfo.ContentType, multipartUploadAuthorizationInfo.Authorization, null, cancellationTokenSource);
  876. if (uploadResult == null || !uploadResult.Item1)
  877. {
  878. throw new Exception($"Upload file failed! uploadResult Result:{uploadResult?.Item1},ETag:{uploadResult?.Item2}");
  879. }
  880. }
  881. catch (Exception ex)
  882. {
  883. Logger.WriteLineError($"MultiPartUpload PartNumber:{index}, Error:{ex.Message} once, try again");
  884. try
  885. {
  886. if (cancellationTokenSource.IsCancellationRequested)
  887. {
  888. Logger.WriteLineError($"MultiPartUpload PartNumber:{index} Cancelled");
  889. return;
  890. }
  891. uploadResult = UploadFileHelper.UploadFile(multipartUploadUrl, bytes, multipartUploadAuthorizationInfo.ContentType, multipartUploadAuthorizationInfo.Authorization, null, cancellationTokenSource);
  892. if (uploadResult == null || !uploadResult.Item1)
  893. {
  894. throw new Exception($"Upload file failed! uploadResult Result:{uploadResult?.Item1},ETag:{uploadResult?.Item2}");
  895. }
  896. }
  897. catch (Exception e)
  898. {
  899. Logger.WriteLineError($"MultiPartUpload PartNumber:{index}, Error:{e.Message} again");
  900. _isUploadFail = true;
  901. return;
  902. }
  903. }
  904. if (cancellationTokenSource.IsCancellationRequested)
  905. {
  906. Logger.WriteLineError($"MultiPartUpload PartNumber:{index} Cancelled");
  907. return;
  908. }
  909. var tempEntity = new PartInfo()
  910. {
  911. PartNumber = index,
  912. ETag = uploadResult.Item2
  913. };
  914. Logger.WriteLineInfo($"MultipartUploadPart PartNumber:{index},ETag:{uploadResult.Item2} success!");
  915. resultDic.TryAdd(index, tempEntity);
  916. }
  917. catch (Exception ex)
  918. {
  919. Logger.WriteLineError($"MultiPartUpload PartNumber:{index}, Error:{ex.Message}");
  920. _isUploadFail = true;
  921. }
  922. }
  923. private string CompleteMultipartUpload(CancellationTokenSource cancellationTokenSource, ConcurrentDictionary<int, PartInfo> resultDic, string fileName, StorageServiceSettingDTO authorizationInfo, InitiateMultipartUploadResult initiateMultipartUploadResult)
  924. {
  925. var completeEntity = new CompleteMultipartUpload();
  926. foreach (var key in resultDic.Keys.OrderBy(x => x).ToList())
  927. {
  928. completeEntity.Part.Add(resultDic[key]);
  929. }
  930. var requestParams = new List<DataItemDTO>()
  931. {
  932. new DataItemDTO
  933. {
  934. Key = "uploadId",
  935. Value = initiateMultipartUploadResult.UploadId
  936. }
  937. };
  938. var completeMultipartUploadFileServiceRequest = new FileServiceRequest
  939. {
  940. FileName = fileName,
  941. IsRechristen = false,
  942. Token = _token,
  943. UrlParams = requestParams,
  944. RequestMethod = "post",
  945. IsCompleted = true,
  946. };
  947. var completeMultipartUploadAuthorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, completeMultipartUploadFileServiceRequest);
  948. if (completeMultipartUploadAuthorizationInfo == null)
  949. {
  950. throw new Exception("GetAuthorization Error,CompleteMultipartUploadAuthorizationInfo is null ");
  951. }
  952. var completeMultipartUploadUrl = $"{completeMultipartUploadAuthorizationInfo.StorageUrl}?uploadId={initiateMultipartUploadResult.UploadId}";
  953. var xml = "";
  954. if (completeEntity.Part?.Count > 0)
  955. {
  956. xml = "<CompleteMultipartUpload>";
  957. foreach (var partInfo in completeEntity.Part)
  958. {
  959. xml += "<Part>";
  960. xml += "<PartNumber>" + partInfo.PartNumber + "</PartNumber>";
  961. xml += "<ETag>" + partInfo.ETag + "</ETag>";
  962. xml += "</Part>";
  963. }
  964. xml += "</CompleteMultipartUpload>";
  965. }
  966. byte[] bytesR = Encoding.UTF8.GetBytes(xml);
  967. var byteContent = new ByteArrayContent(bytesR);
  968. var mimeType = HttpClientHelper.GetMimeType(".config");
  969. using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri(completeMultipartUploadUrl)))
  970. {
  971. request.Headers.TryAddWithoutValidation("Authorization", completeMultipartUploadAuthorizationInfo.Authorization);
  972. request.Headers.TryAddWithoutValidation("Content-Type", mimeType);
  973. request.Content = byteContent;
  974. using (var httpClient = new HttpClient())
  975. {
  976. var response = httpClient.SendAsync(request, cancellationTokenSource.Token).GetAwaiter().GetResult();
  977. if (response != null && response.StatusCode == HttpStatusCode.OK)
  978. {
  979. if (cancellationTokenSource.IsCancellationRequested)
  980. {
  981. throw new OperationCanceledException("Cancelled");
  982. }
  983. var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
  984. try
  985. {
  986. if (result.Contains(nameof(CompleteMultipartUploadResult)))
  987. {
  988. Logger.WriteLineInfo($"CompleteMultipartUpload Success,{result}");
  989. return authorizationInfo.StorageUrl;
  990. }
  991. else
  992. {
  993. Logger.WriteLineInfo($"CompleteMultipartUpload Error,{result}");
  994. return string.Empty;
  995. }
  996. }
  997. catch (Exception ex)
  998. {
  999. Logger.WriteLineInfo($"CompleteMultipartUpload Fail, result:{result?.Trim()},Error:{ex.Message}");
  1000. return string.Empty;
  1001. }
  1002. }
  1003. else
  1004. {
  1005. Logger.WriteLineError($"CompleteMultipartUpload Fail,StatusCode:{response?.StatusCode}.");
  1006. return string.Empty;
  1007. }
  1008. }
  1009. }
  1010. }
  1011. private string UploadFile(CancellationTokenSource cancellationTokenSource, string filePath, string fileName)
  1012. {
  1013. var fileServiceRequest = new FileServiceRequest
  1014. {
  1015. FileName = fileName,
  1016. IsRechristen = true,
  1017. Token = _token,
  1018. };
  1019. var authorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, fileServiceRequest);
  1020. if (authorizationInfo == null)
  1021. {
  1022. throw new Exception("GetAuthorization Error,AuthorizationInfo is null ");
  1023. }
  1024. if (cancellationTokenSource.IsCancellationRequested)
  1025. {
  1026. throw new OperationCanceledException("Cancelled");
  1027. }
  1028. var uploadResult = UploadFileHelper.UploadFile(authorizationInfo.StorageUrl, filePath, authorizationInfo.ContentType, authorizationInfo.Authorization, null, cancellationTokenSource);
  1029. if (cancellationTokenSource.IsCancellationRequested)
  1030. {
  1031. throw new OperationCanceledException("Cancelled");
  1032. }
  1033. if (uploadResult?.Item1 == true)
  1034. {
  1035. return authorizationInfo.StorageUrl;
  1036. }
  1037. else
  1038. {
  1039. return string.Empty;
  1040. }
  1041. }
  1042. private string UploadFile(CancellationTokenSource cancellationTokenSource, string fileName, byte[] fileData)
  1043. {
  1044. var fileServiceRequest = new FileServiceRequest
  1045. {
  1046. FileName = fileName,
  1047. IsRechristen = true,
  1048. Token = _token,
  1049. };
  1050. var authorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, fileServiceRequest);
  1051. if (authorizationInfo == null)
  1052. {
  1053. throw new Exception("GetAuthorization Error,AuthorizationInfo is null ");
  1054. }
  1055. if (cancellationTokenSource.IsCancellationRequested)
  1056. {
  1057. throw new OperationCanceledException("Cancelled");
  1058. }
  1059. var uploadResult = UploadFileHelper.UploadFile(authorizationInfo.StorageUrl, fileData, authorizationInfo.ContentType, authorizationInfo.Authorization, null, cancellationTokenSource);
  1060. if (cancellationTokenSource.IsCancellationRequested)
  1061. {
  1062. throw new OperationCanceledException("Cancelled");
  1063. }
  1064. if (uploadResult?.Item1 == true)
  1065. {
  1066. return authorizationInfo.StorageUrl;
  1067. }
  1068. else
  1069. {
  1070. return string.Empty;
  1071. }
  1072. }
  1073. private void DoDispose()
  1074. {
  1075. try
  1076. {
  1077. if (!_disposed)
  1078. {
  1079. //if not uploaded just save it to disk.
  1080. if (Status != UploadStatus.Uploaded && Status != UploadStatus.Deleted)
  1081. {
  1082. Save();
  1083. }
  1084. }
  1085. }
  1086. catch (Exception ex)
  1087. {
  1088. Logger.WriteLineError($"vCloudScanData: dispose upload data error:{ex}");
  1089. }
  1090. finally
  1091. {
  1092. _disposed = true;
  1093. }
  1094. }
  1095. /// <summary>
  1096. /// Compress a VinnoImage to a 100p image with {compressLevel} quality
  1097. /// </summary>
  1098. /// <param name="vinnoImage"></param>
  1099. /// <param name="compressLevel"></param>
  1100. /// <returns></returns>
  1101. /// <exception cref="Exception"></exception>
  1102. private VinnoImage Compress(VinnoImage vinnoImage, long compressLevel = 80)
  1103. {
  1104. byte[] compressedData = vinnoImage.ImageData;
  1105. using (var ms = new MemoryStream(compressedData))
  1106. {
  1107. using (var image = SKBitmap.Decode(ms))
  1108. {
  1109. var ratio = (double)100 / image.Height;
  1110. if (ratio < 1)
  1111. {
  1112. var width = (int)(image.Width * ratio);
  1113. var height = 100;
  1114. SKBitmap tempImage = image;
  1115. if (image.Width != width || image.Height != height)
  1116. {
  1117. tempImage = image.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
  1118. }
  1119. try
  1120. {
  1121. using (var map = new SKPixmap(new SKImageInfo(tempImage.Width, tempImage.Height, tempImage.ColorType), tempImage.GetPixels()))
  1122. {
  1123. using (var stream = new SKDynamicMemoryWStream())
  1124. {
  1125. SKPixmap.Encode(stream, map, SKEncodedImageFormat.Jpeg, (int)compressLevel);
  1126. compressedData = stream.CopyToData().ToArray();
  1127. }
  1128. }
  1129. }
  1130. catch (Exception exception)
  1131. {
  1132. throw new Exception($"Compress error:{exception}");
  1133. }
  1134. finally
  1135. {
  1136. tempImage.Dispose();
  1137. }
  1138. var compressImage = new VinnoImage(vinnoImage.Index, width, height, compressedData);
  1139. return compressImage;
  1140. }
  1141. }
  1142. }
  1143. return vinnoImage;
  1144. }
  1145. public void Dispose()
  1146. {
  1147. DoDispose();
  1148. GC.SuppressFinalize(this);
  1149. }
  1150. }
  1151. }