Desktop.razor.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. using HyperCube.Models;
  2. using Microsoft.AspNetCore.Components;
  3. using Microsoft.AspNetCore.Components.Authorization;
  4. using Microsoft.AspNetCore.Components.Forms;
  5. using Microsoft.AspNetCore.Identity;
  6. using Microsoft.JSInterop;
  7. using Pullenti.Unitext;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.ComponentModel.DataAnnotations;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Reflection;
  14. using System.Security.Cryptography;
  15. using System.Text;
  16. using System.Threading.Tasks;
  17. using System.Xml;
  18. using Console = HyperCube.Utils.AdvConsole;
  19. namespace HyperCube.Pages
  20. {
  21. public partial class Desktop : ComponentBase
  22. {
  23. //[Parameter]
  24. //public int DocID { get; set; }
  25. [Inject]
  26. NavigationManager NavigationManager { get; set; }
  27. [Inject]
  28. AuthenticationStateProvider AuthenticationStateProvider { get; set; }
  29. [Inject]
  30. UserManager<IdentityUser> UserManager { get; set; }
  31. [Inject]
  32. RoleManager<IdentityRole> RoleManager { get; set; }
  33. [Inject]
  34. AppData AppData { get; set; }
  35. [Inject]
  36. public IJSRuntime JsRuntime { get; set; }
  37. const string STORAGE_FOLDER_NAME = "articles_storage";
  38. const long MAX_FILE_SIZE = 5120000; //bytes
  39. const string ACTIVE_BUTTON_CLASS = "btn_white tab-button active";
  40. const string ACTIVE_TAB_CLASS = "second-block__form visible";
  41. const string BUTTON_CLASS = "btn_white tab-button";
  42. const string TAB_CLASS = "second-block__form";
  43. string _uploadButtonClass = ACTIVE_BUTTON_CLASS;
  44. string _uploadTabClass = ACTIVE_TAB_CLASS;
  45. string _verifyButtonClass = BUTTON_CLASS;
  46. string _verifyTabClass = TAB_CLASS;
  47. string _otherButtonClass = BUTTON_CLASS;
  48. string _otherTabClass = TAB_CLASS;
  49. //int _counter = 1;
  50. //string _event = "";
  51. string _status;
  52. string _articleDropdownOption = "";
  53. //string _storageFolderPath;
  54. MemoryStream _memoryStream;
  55. ModalInfo _modalInfo { get; set; }
  56. ModalLoading _modalLoading { get; set; }
  57. ModalSurvey _modalSurvey { get; set; }
  58. ArticleModel _articleClone;
  59. ArticleModel _article;
  60. Dictionary<int, ArticleModel> _articles = new();
  61. ReportModel _report = new();
  62. UnitextDocument _document;
  63. AccountModel _currentAccount;
  64. VerificationPoint _verificationPoint = new();
  65. bool loadButtonDisable { get; set; }
  66. bool verifyButtonDisable { get; set; }
  67. bool rejectReasonDisable { get; set; } = true;
  68. protected override async Task OnInitializedAsync()
  69. {
  70. _currentAccount = (AppData.CurrentAccount != null) ? AppData.CurrentAccount : await GetCurrentAcc();
  71. _currentAccount.LoadRoles();
  72. Console.WriteLine($"Desktop OnInitializedAsync, CurrentAccount: {_currentAccount.Name}");
  73. ///tmp
  74. await LoadArticles();
  75. _article = AppData.CurrentArticle ?? (new());
  76. _articleClone = AppData.CurrentArticleClone ?? (new());
  77. }
  78. protected override void OnAfterRender(bool firstRender)
  79. {
  80. if (firstRender)
  81. JsRuntime.InvokeVoidAsync("InitializeDesktopSlick");
  82. if (_verificationPoint.RulesViolation || _verificationPoint.NonExpert || _verificationPoint.AdditionalVerificationRequired || _verificationPoint.Rejected)
  83. {
  84. if (rejectReasonDisable)
  85. {
  86. rejectReasonDisable = false;
  87. StateHasChanged();
  88. }
  89. }
  90. if (!_verificationPoint.RulesViolation && !_verificationPoint.NonExpert && !_verificationPoint.AdditionalVerificationRequired && !_verificationPoint.Rejected)
  91. {
  92. if (!rejectReasonDisable)
  93. {
  94. rejectReasonDisable = true;
  95. StateHasChanged();
  96. }
  97. }
  98. }
  99. void SwitchDesktopTab(int tabIndex)
  100. {
  101. switch(tabIndex)
  102. {
  103. case 0:
  104. _uploadButtonClass = ACTIVE_BUTTON_CLASS;
  105. _uploadTabClass = ACTIVE_TAB_CLASS;
  106. _verifyButtonClass = BUTTON_CLASS;
  107. _verifyTabClass = TAB_CLASS;
  108. _otherButtonClass = BUTTON_CLASS;
  109. _otherTabClass = TAB_CLASS;
  110. break;
  111. case 1:
  112. _uploadButtonClass = BUTTON_CLASS;
  113. _uploadTabClass = TAB_CLASS;
  114. _verifyButtonClass = ACTIVE_BUTTON_CLASS;
  115. _verifyTabClass = ACTIVE_TAB_CLASS;
  116. _otherButtonClass = BUTTON_CLASS;
  117. _otherTabClass = TAB_CLASS;
  118. break;
  119. case 2:
  120. _uploadButtonClass = BUTTON_CLASS;
  121. _uploadTabClass = TAB_CLASS;
  122. _verifyButtonClass = BUTTON_CLASS;
  123. _verifyTabClass = TAB_CLASS;
  124. _otherButtonClass = ACTIVE_BUTTON_CLASS;
  125. _otherTabClass = ACTIVE_TAB_CLASS;
  126. break;
  127. }
  128. }
  129. async Task WidgetMenuClick(string menuname, string elementid)
  130. {
  131. await JsRuntime.InvokeVoidAsync("WidgetMenuClick", menuname, elementid);
  132. //_counter = 1;
  133. }
  134. async Task HandleUpload(InputFileChangeEventArgs e)
  135. {
  136. _modalLoading.Open();
  137. IBrowserFile file = e.File;
  138. if (file != null)
  139. {
  140. Stream stream = file.OpenReadStream(MAX_FILE_SIZE);
  141. _memoryStream = new();
  142. await stream.CopyToAsync(_memoryStream);
  143. _status = $"Finished loading {_memoryStream.Length} bytes from {file.Name}";
  144. Console.WriteLine(_status);
  145. /// calculating hash
  146. string hash = await CalculateHashSum(_memoryStream);
  147. Console.WriteLine($"Hash: {hash}");
  148. /// checking hash
  149. MySQLConnector dbCon = MySQLConnector.Instance();
  150. string stringSQL;
  151. stringSQL = $"SELECT COUNT(*) FROM articles WHERE file_hash='{hash}'";
  152. int count = await dbCon.SQLSelectCount(stringSQL);
  153. if (count < 1)
  154. {
  155. _report = new();
  156. _report.FileName = file.Name;
  157. _report.FileSize = _memoryStream.Length.ToString();
  158. _memoryStream.Position = 0;
  159. byte[] content = _memoryStream.ToArray();
  160. _document = UnitextService.CreateDocument(null, content, null);
  161. if (_document.ErrorMessage != null)
  162. {
  163. // скорее всего, этот формат не поддерживается на данный момент
  164. Console.WriteLine($"error, sorry: {_document.ErrorMessage}");
  165. _memoryStream.Close();
  166. stream.Close();
  167. _modalLoading.Close();
  168. _modalInfo.Open("Не удается прочитать документ, формат не поддерживается или файл поврежден.");
  169. return;
  170. }
  171. // восстанавливаем имя исходного файла, извлечённого из ресурсов
  172. _document.SourceFileName = file.Name;
  173. for (int i = file.Name.Length - 7; i > 0; i--)
  174. {
  175. if (file.Name[i] == '.')
  176. {
  177. _document.SourceFileName = file.Name.Substring(i + 1);
  178. break;
  179. }
  180. }
  181. //// записываем результат в XML
  182. //using (FileStream fs = new(doc.SourceFileName + ".xml", FileMode.Create, FileAccess.Write))
  183. //{
  184. // XmlWriterSettings xmlParams = new();
  185. // xmlParams.Encoding = Encoding.UTF8;
  186. // xmlParams.Indent = true;
  187. // xmlParams.IndentChars = " ";
  188. // using (XmlWriter xml = XmlWriter.Create(fs, xmlParams))
  189. // {
  190. // xml.WriteStartDocument();
  191. // doc.GetXml(xml);
  192. // xml.WriteEndDocument();
  193. // }
  194. //}
  195. //Console.WriteLine("XML write done");
  196. // получаем плоский текст
  197. string plainText = _document.GetPlaintextString(null);
  198. if (plainText == null)
  199. plainText = "Текст не выделен";
  200. /// todo обработка, если статус != новый
  201. _articleClone = DocParse.GetBaseProperties(plainText);
  202. _articleClone.Filename = file.Name;
  203. _articleClone.HashSum = hash;
  204. _articleClone.CharCount = plainText.Length;
  205. _article = (ArticleModel)_articleClone.Clone();
  206. ///tmp
  207. AppData.CurrentArticle = _article;
  208. Console.WriteLine($"Initializing SDK Pullenti ver {Pullenti.Sdk.Version} ({Pullenti.Sdk.VersionDate})... ");
  209. Pullenti.Sdk.InitializeAll();
  210. //Console.WriteLine($"OK (by ... ms), version {Pullenti.Ner.ProcessorService.Version}");
  211. List<string> npt_tokens = new();
  212. // запускаем обработку на пустом процессоре (без анализаторов NER)
  213. Pullenti.Ner.AnalysisResult are = Pullenti.Ner.ProcessorService.EmptyProcessor.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
  214. //System.Console.Write("Noun groups: ");
  215. // перебираем токены
  216. for (Pullenti.Ner.Token t = are.FirstToken; t != null; t = t.Next)
  217. {
  218. // выделяем именную группу с текущего токена
  219. Pullenti.Ner.Core.NounPhraseToken npt = Pullenti.Ner.Core.NounPhraseHelper.TryParse(t, Pullenti.Ner.Core.NounPhraseParseAttr.No, 0, null);
  220. // не получилось
  221. if (npt == null)
  222. continue;
  223. // получилось, выводим в нормализованном виде
  224. //System.Console.Write($"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ");
  225. //_report.NounGroups += $"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ";
  226. _article.NounGroups += $"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ";
  227. npt_tokens.Add(npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false));
  228. // указатель на последний токен именной группы
  229. t = npt.EndToken;
  230. }
  231. using (Pullenti.Ner.Processor proc = Pullenti.Ner.ProcessorService.CreateProcessor())
  232. {
  233. // анализируем текст
  234. Pullenti.Ner.AnalysisResult ar = proc.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
  235. // результирующие сущности
  236. //Console.WriteLine("\r\n==========================================\r\nEntities: ");
  237. foreach (Pullenti.Ner.Referent en in ar.Entities)
  238. {
  239. //Console.WriteLine($"{en.TypeName}: {en}");
  240. //_report.Entities += $"{en.TypeName}: {en}\r\n";
  241. _article.Entities += $"{en.TypeName}: {en}\r\n";
  242. foreach (Pullenti.Ner.Slot s in en.Slots)
  243. {
  244. //Console.WriteLine($" {s.TypeName}: {s.Value}");
  245. //_report.Entities += $" {s.TypeName}: {s.Value}<br>";
  246. _article.Entities += $" {s.TypeName}: {s.Value}<br>";
  247. }
  248. }
  249. // пример выделения именных групп
  250. //Console.WriteLine("\r\n==========================================\r\nNoun groups: ");
  251. for (Pullenti.Ner.Token t = ar.FirstToken; t != null; t = t.Next)
  252. {
  253. // токены с сущностями игнорируем
  254. if (t.GetReferent() != null)
  255. continue;
  256. // пробуем создать именную группу
  257. Pullenti.Ner.Core.NounPhraseToken npt = Pullenti.Ner.Core.NounPhraseHelper.TryParse(t, Pullenti.Ner.Core.NounPhraseParseAttr.AdjectiveCanBeLast, 0, null);
  258. // не получилось
  259. if (npt == null)
  260. continue;
  261. //Console.WriteLine(npt.ToString());
  262. //_report.EntitiesNounGroups += $"{npt}<br>";
  263. _article.Morph += $"{npt}<br>";
  264. // указатель перемещаем на последний токен группы
  265. t = npt.EndToken;
  266. }
  267. }
  268. using (Pullenti.Ner.Processor proc = Pullenti.Ner.ProcessorService.CreateSpecificProcessor(Pullenti.Ner.Keyword.KeywordAnalyzer.ANALYZER_NAME))
  269. {
  270. Pullenti.Ner.AnalysisResult ar = proc.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
  271. //Console.WriteLine("\r\n==========================================\r\nKeywords1: ");
  272. foreach (Pullenti.Ner.Referent en in ar.Entities)
  273. {
  274. if (en is Pullenti.Ner.Keyword.KeywordReferent)
  275. //Console.WriteLine(en.ToString());
  276. //_report.Keywords1 += $"{en}<br>";
  277. _article.Keywords1 += $"{en}<br>";
  278. }
  279. //Console.WriteLine("\r\n==========================================\r\nKeywords2: ");
  280. for (Pullenti.Ner.Token t = ar.FirstToken; t != null; t = t.Next)
  281. {
  282. if (t is Pullenti.Ner.ReferentToken)
  283. {
  284. Pullenti.Ner.Keyword.KeywordReferent kw = t.GetReferent() as Pullenti.Ner.Keyword.KeywordReferent;
  285. if (kw == null)
  286. continue;
  287. string kwstr = Pullenti.Ner.Core.MiscHelper.GetTextValueOfMetaToken(t as Pullenti.Ner.ReferentToken, Pullenti.Ner.Core.GetTextAttr.FirstNounGroupToNominativeSingle | Pullenti.Ner.Core.GetTextAttr.KeepRegister);
  288. //Console.WriteLine($"{kwstr} = {kw}");
  289. //_report.Keywords2 += $"{kwstr} = {kw}<br>";
  290. _article.Keywords2 += $"{kwstr} = {kw}<br>";
  291. }
  292. }
  293. }
  294. int res = (from x in npt_tokens
  295. select x).Distinct().Count();
  296. Console.WriteLine($"npt_tokens.count={npt_tokens.Count}, distinct.Count={res}");
  297. Console.WriteLine("Analysis is over!");
  298. var query = from x in npt_tokens
  299. group x by x into g
  300. let count1 = g.Count()
  301. orderby count1 descending
  302. select new { Name = g.Key, Count = count1 };
  303. foreach (var result in query)
  304. {
  305. _report.NounGroupsSorted += $"{result.Name}, Count: {result.Count}<br>";
  306. //Console.WriteLine($"Name: {result.Name}, Count: {result.Count}");
  307. }
  308. //AppData.Report = _report;
  309. }
  310. else
  311. {
  312. _status = $"File duplicate founded, hash: {hash}.";
  313. Console.WriteLine(_status);
  314. _article = new();
  315. _articleClone = new();
  316. _document = null;
  317. _memoryStream.Close();
  318. _modalInfo.Open("Загрузка не удалась, такой документ уже есть в системе.");
  319. }
  320. file = null;
  321. stream.Close();
  322. }
  323. _modalLoading.Close();
  324. }
  325. async Task NewDocument()
  326. {
  327. _article = new();
  328. _articleClone = new();
  329. _document = null;
  330. _memoryStream = null;
  331. _status = "Blank document created";
  332. await JsRuntime.InvokeVoidAsync("WidgetMenuClick", ".block__dropbox", "desktop_menu");
  333. //_counter = 1;
  334. }
  335. async Task SaveDocument_OnClick(ArticleStatus articleNewStatus)
  336. {
  337. Console.WriteLine($"SaveDocument_OnClick. DocID: {_article.ID}, Status: {_article.Status}");
  338. if (_article.Name == null || _article.Name.Length == 0)
  339. {
  340. Console.WriteLine($"SaveDocument, empty article name. DocID: {_article.ID}, Status: {_article.Status}, filename: {_article.Filename}");
  341. _modalInfo.Open( $"Для сохранения документа необходимо ввести название.");
  342. return;
  343. }
  344. if (_article.Status == ArticleStatus.New && _memoryStream == null)
  345. {
  346. Console.WriteLine($"SaveDocument, empty source file. DocID: {_article.ID}, Status: {_article.Status}, filename: {_article.Filename}");
  347. _modalInfo.Open($"Для сохранения документа необходимо прикрепить исходный файл.");
  348. return;
  349. }
  350. if (_article.Status == ArticleStatus.New && articleNewStatus == ArticleStatus.Verifying
  351. || _article.Status == ArticleStatus.AwatingVerify && articleNewStatus == ArticleStatus.Saved
  352. || _article.Status == ArticleStatus.Verifying && articleNewStatus == ArticleStatus.Saved
  353. || _article.Status == ArticleStatus.Verified && articleNewStatus == ArticleStatus.Saved
  354. || _article.Status == ArticleStatus.Verified && articleNewStatus == ArticleStatus.Verifying)
  355. {
  356. Console.WriteLine($"SaveDocument, wrong status. DocID: {_article.ID}, Status: {_article.Status}");
  357. _modalInfo.Open($"Текущий статус документа не позволяет сохранение.<br>DocID: {_article.ID}, Status: {GetDisplayName(_article.Status)}");
  358. }
  359. else
  360. {
  361. /// all is fine, continue
  362. MySQLConnector dbCon = MySQLConnector.Instance();
  363. long id;
  364. string stringSQL;
  365. if (_article.Status == ArticleStatus.New)
  366. {
  367. _modalLoading.Open();
  368. stringSQL = $"INSERT INTO articles (filename, article_name, authors, date_publish, annotation, keywords, file_hash, " +
  369. $"doc_noungroups, doc_entities, doc_morph, doc_keywords1, doc_keywords2, char_count) " +
  370. $"VALUES ('{_article.Filename}', '{_article.Name}', '{_article.Authors}', '{_article.PublishDate:yyyy-MM-dd}'," +
  371. $"'{_article.Annotation}', '{_article.Keywords}', '{_article.HashSum}'," +
  372. $"'{_article.NounGroups}', '{_article.Entities}', '{_article.Morph}', '{_article.Keywords1}', '{_article.Keywords2}', '{_article.CharCount}' )";
  373. id = await dbCon.SQLInsert(stringSQL);
  374. _article.ID = (int)id;
  375. ///todo добавлять новый док в массив AppData.Articles
  376. stringSQL = $"INSERT INTO actions_history (article_id, action_type, acc_id) " +
  377. $"VALUES ('{id}', '{(int)articleNewStatus}', '{_currentAccount.UUID}')";
  378. await dbCon.SQLInsert(stringSQL);
  379. await SaveFiles((int)id);
  380. _article.Status = ArticleStatus.Saved;
  381. _articleClone = (ArticleModel)_article.Clone();
  382. /// reloading articles
  383. await LoadArticles();
  384. //_counter = 1;
  385. _modalLoading.Close();
  386. _modalInfo.Open("Документ успешно создан.");
  387. }
  388. else
  389. {
  390. await UpdateDocument(articleNewStatus);
  391. //await SaveFiles(_article.ID);
  392. if (_article.Status == ArticleStatus.AwatingVerify || _article.Status == ArticleStatus.Verifying)
  393. await _article.SaveLastVerificationPoint(_verificationPoint, _currentAccount.UUID);
  394. }
  395. }
  396. }
  397. async Task LoadDocument(int docid)
  398. {
  399. ///todo загружать из массива AppData.Articles
  400. Console.WriteLine($"LoadDocument, docid: {docid}.");
  401. _modalLoading.Open();
  402. if (docid > 0)
  403. {
  404. MySQLConnector dbCon = MySQLConnector.Instance();
  405. AppData.CurrentArticleClone = await dbCon.SQLSelectArticle(docid);
  406. await AppData.CurrentArticleClone.GetVerificationHistory(_currentAccount.UUID);
  407. if (AppData.CurrentArticleClone.VerificationHistory.Count > 0)
  408. _verificationPoint = AppData.CurrentArticleClone.VerificationHistory.Values.Last();
  409. else
  410. _verificationPoint = new();
  411. AppData.CurrentArticle = (ArticleModel)AppData.CurrentArticleClone.Clone();
  412. //string initiator = await _article.GetInitiatorUUID();
  413. //initiatorAcc = AccountModel.Find(initiator);
  414. //_status = $"Article ID={DocID} loaded, status: {_article.Status}, initiator: {initiatorAcc.Name}";
  415. _status = $"Article ID={docid} loaded, status: {AppData.CurrentArticle.Status}.";
  416. _article = AppData.CurrentArticle ?? (new());
  417. _articleClone = AppData.CurrentArticleClone ?? (new());
  418. }
  419. _modalLoading.Close();
  420. }
  421. async Task SendToVerify_OnClick()
  422. {
  423. if (_article.Status == ArticleStatus.New || _article.Status == ArticleStatus.Saved)
  424. {
  425. Console.WriteLine($"SendToVerify, DocID: {_article.ID}, Status: {_article.Status}");
  426. _modalLoading.Open();
  427. /// form validation
  428. List<string> errorFields = ValidateForm<ArticleModel>(_article);
  429. if (errorFields.Count > 0)
  430. {
  431. _modalLoading.Close();
  432. string invalid_fields = string.Join(", ", errorFields);
  433. _modalInfo.Open($"Не заполнены поля: {invalid_fields}");
  434. Console.WriteLine($"SendToVerify. Required fields: '{invalid_fields}' is not filled.");
  435. return;
  436. }
  437. await UpdateDocument(ArticleStatus.AwatingVerify);
  438. _modalLoading.Close();
  439. }
  440. else
  441. {
  442. Console.WriteLine($"SendToVerify, wrong status. DocID: {_article.ID}, Status: {_article.Status}");
  443. _modalInfo.Open($"Документ не может быть отправлен на верификацию.<br>DocID: {_article.ID}, Status: {GetDisplayName(_article.Status)}");
  444. }
  445. }
  446. async Task Verify_OnClick()
  447. {
  448. //if (DocID > 0)
  449. //{
  450. // status = propDict.Count > 0 ? "All changes saved, article has veryfied." : "Article verifyed without any changes.";
  451. // transactionId = await Verify();
  452. // Console.WriteLine("transactionId found " + transactionId);
  453. // ///tmp
  454. // editsCount = await article.GetEditsCount(currentAcc.UUID);
  455. // modalInfo_transac.Open();
  456. //}
  457. if (_article.Status == ArticleStatus.AwatingVerify || _article.Status == ArticleStatus.Verifying)
  458. {
  459. Console.WriteLine($"Verify, DocID: {_article.ID}, Status: {_article.Status}");
  460. _modalLoading.Open();
  461. /// form validation
  462. List<string> errorFields = ValidateForm<ArticleModel>(_article);
  463. if (errorFields.Count > 0)
  464. {
  465. _modalLoading.Close();
  466. string invalid_fields = string.Join(", ", errorFields);
  467. _modalInfo.Open($"Не заполнены поля: {invalid_fields}");
  468. Console.WriteLine($"Verify. Required fields: '{invalid_fields}' is not filled.");
  469. return;
  470. }
  471. if (!rejectReasonDisable)
  472. {
  473. if (_verificationPoint.RejectReason == null || _verificationPoint.RejectReason.Length == 0)
  474. {
  475. _modalLoading.Close();
  476. _modalInfo.Open($"Не заполнена причина отклонения.");
  477. Console.WriteLine($"Verify. Reject reason is not filled.");
  478. return;
  479. }
  480. }
  481. var bcMain = await _currentAccount.GetSelectedBlockChain();
  482. var transactionId = await bcMain.Verify(_currentAccount, _article);
  483. Console.WriteLine("transactionId found " + transactionId);
  484. await UpdateDocument(ArticleStatus.Verified);
  485. _modalLoading.Close();
  486. }
  487. else
  488. {
  489. Console.WriteLine($"Verify, wrong status. DocID: {_article.ID}, Status: {_article.Status}");
  490. _modalInfo.Open($"Документ не может быть верифицирован.<br>DocID: {_article.ID}, Status: {GetDisplayName(_article.Status)}");
  491. }
  492. }
  493. async Task UpdateDocument(ArticleStatus articleStatus)
  494. {
  495. _modalLoading.Open();
  496. MySQLConnector dbCon = MySQLConnector.Instance();
  497. string stringSQL;
  498. string rating = (_article.Rating == null) ? "NULL" : _article.Rating.ToString();
  499. stringSQL = $"UPDATE articles " +
  500. $"SET filename='{_article.Filename}', article_name='{_article.Name}', authors='{_article.Authors}', " +
  501. $"date_publish='{_article.PublishDate:yyyy-MM-dd}', annotation='{_article.Annotation}', " +
  502. $"keywords='{_article.Keywords}', rating={rating}, file_hash='{_article.HashSum}' " +
  503. $"WHERE id={_article.ID}";
  504. await dbCon.SQLInsert(stringSQL);
  505. ///todo обновлять док в массиве AppData.Articles
  506. stringSQL = $"INSERT INTO actions_history (article_id, action_type, acc_id) " +
  507. $"VALUES ('{_article.ID}', '{(int)articleStatus}', '{_currentAccount.UUID}')";
  508. await dbCon.SQLInsert(stringSQL);
  509. ///если нужно фиксировать изменение статуса в поле (табл articles_edit_log)
  510. //_article.Status = articleStatus;
  511. Dictionary<string, PropertyInfo> propDict = Compare.SimpleCompare<ArticleModel>(_article, _articleClone);
  512. foreach (KeyValuePair<string, PropertyInfo> prop in propDict)
  513. {
  514. //Console.WriteLine($"property name: {prop.Key}, value: {prop.Value.GetValue(articleModel, null)}");
  515. stringSQL = $"INSERT INTO articles_edit_log (article_id, acc_id, field_name, field_prevvalue, field_newvalue) " +
  516. $"VALUES ('{_article.ID}', '{_currentAccount.UUID}', '{prop.Key}', '{prop.Value.GetValue(_articleClone, null)}', '{prop.Value.GetValue(_article, null)}')";
  517. await dbCon.SQLInsert(stringSQL);
  518. }
  519. _article.Status = articleStatus;
  520. _articleClone = (ArticleModel)_article.Clone();
  521. /// reloading articles
  522. await LoadArticles();
  523. _modalLoading.Close();
  524. string message = propDict.Count > 0 ? "Все изменения успешно сохранены." : "Изменения не были найдены при сохранении.";
  525. if (_article.Status == ArticleStatus.Verified)
  526. message = "Документ успешно валидирован";
  527. _modalInfo.Open(message);
  528. }
  529. async Task DocSelect_OnChange(ChangeEventArgs e)
  530. {
  531. int docid = int.Parse(e.Value.ToString());
  532. Console.WriteLine($"DocSelect_OnChange. docid: {docid}");
  533. await LoadDocument(docid);
  534. }
  535. async Task SaveFiles(int docid)
  536. {
  537. if (_document != null )
  538. {
  539. string fullpath;
  540. string htmldirectorypath;
  541. string docdirectorypath;
  542. #if DEBUG
  543. htmldirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
  544. docdirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
  545. #else
  546. htmldirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
  547. docdirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
  548. #endif
  549. try
  550. {
  551. /// получаем html, сохраняем файлы
  552. GetHtmlParam htmlParams = new();
  553. htmlParams.OutHtmlAndBodyTags = true;
  554. string html = _document.GetHtmlString(htmlParams);
  555. ///saving html
  556. fullpath = Path.Combine(htmldirectorypath, $"{docid}_{_document.SourceFileName}.html");
  557. Console.WriteLine($"Saving file [{fullpath}]");
  558. Directory.CreateDirectory(htmldirectorypath);
  559. File.WriteAllBytes(fullpath, Encoding.UTF8.GetBytes(html));
  560. }
  561. catch (Exception ex)
  562. {
  563. Console.WriteLine($"{ex.GetType()}\r\n{ex.Message}\r\n{ex.StackTrace}");
  564. }
  565. ///saving original files
  566. fullpath = Path.Combine(docdirectorypath, $"{docid}_{_article.Filename}");
  567. Console.WriteLine($"Saving file [{fullpath}]");
  568. Directory.CreateDirectory(docdirectorypath);
  569. FileStream fs = new(fullpath, FileMode.Create, FileAccess.Write);
  570. _memoryStream.Position = 0;
  571. await _memoryStream.CopyToAsync(fs);
  572. _status = $"User has saved new article data: [{docid}_{_article.Filename}], memory size:{_memoryStream.Length}b, file size: {fs.Length}b";
  573. Console.WriteLine(_status);
  574. _memoryStream.Close();
  575. fs.Close();
  576. _document = null;
  577. }
  578. }
  579. async Task GetArticleFile(string option)
  580. {
  581. string fullpath;
  582. string htmldirectorypath;
  583. string docdirectorypath;
  584. #if DEBUG
  585. htmldirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
  586. docdirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
  587. #else
  588. htmldirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
  589. docdirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
  590. #endif
  591. if (option == "source")
  592. {
  593. fullpath = Path.Combine(docdirectorypath, $"{_article.ID}_{_article.Filename}");
  594. if (fullpath.Length > 0 && File.Exists(fullpath))
  595. NavigationManager.NavigateTo($"{STORAGE_FOLDER_NAME}/source/{_article.ID}_{_article.Filename}", true);
  596. else
  597. {
  598. Console.WriteLine($"GetArticleFile, file not exists: [{fullpath}].");
  599. _modalInfo.Open("Файл документа не найден.");
  600. }
  601. }
  602. else if (option == "html")
  603. {
  604. fullpath = Path.Combine(docdirectorypath, $"{_article.ID}_{_article.Filename}.html");
  605. if (fullpath.Length > 0 && File.Exists(fullpath))
  606. NavigationManager.NavigateTo($"{STORAGE_FOLDER_NAME}/html/{_article.ID}_{_article.Filename}.html", true);
  607. else
  608. {
  609. Console.WriteLine($"GetArticleFile, file not exists: [{fullpath}].");
  610. _modalInfo.Open("Файл документа не найден.");
  611. }
  612. }
  613. else
  614. {
  615. Console.WriteLine($"GetArticleFile, unknown method option: [{option}].");
  616. //return;
  617. }
  618. }
  619. async Task LoadArticles()
  620. {
  621. /// reloading articles
  622. await AppData.LoadArticles();
  623. _articles = new();
  624. ///updating local articles and UI on role claims
  625. if (_currentAccount.Roles.Contains(Role.Admin))
  626. {
  627. loadButtonDisable = false;
  628. verifyButtonDisable = false;
  629. int counter = 1;
  630. foreach (ArticleModel article in AppData.Articles.Values) { _articles.Add(counter++, article); }
  631. }
  632. else if (_currentAccount.Roles.Contains(Role.Expert))
  633. {
  634. loadButtonDisable = true;
  635. verifyButtonDisable = false;
  636. int counter = 1;
  637. foreach (ArticleModel article in AppData.Articles.Values)
  638. {
  639. if (article.Status == ArticleStatus.AwatingVerify || article.Status == ArticleStatus.Verifying || article.Status == ArticleStatus.Verified)
  640. _articles.Add(counter++, article);
  641. }
  642. SwitchDesktopTab(1);
  643. }
  644. else if (_currentAccount.Roles.Contains(Role.Miner))
  645. {
  646. loadButtonDisable = false;
  647. verifyButtonDisable = true;
  648. int counter = 1;
  649. foreach (ArticleModel article in AppData.Articles.Values)
  650. {
  651. if (article.Status == ArticleStatus.New || article.Status == ArticleStatus.Saved || article.Status == ArticleStatus.Verified)
  652. _articles.Add(counter++, article);
  653. }
  654. }
  655. else
  656. {
  657. loadButtonDisable = true;
  658. verifyButtonDisable = true;
  659. int counter = 1;
  660. foreach (ArticleModel article in AppData.Articles.Values) { _articles.Add(counter++, article); }
  661. SwitchDesktopTab(2);
  662. }
  663. }
  664. async Task<string> CalculateHashSum(MemoryStream ms)
  665. {
  666. MD5CryptoServiceProvider md5Provider = new();
  667. ms.Position = 0;
  668. byte[] hash = await md5Provider.ComputeHashAsync(ms);
  669. return Convert.ToBase64String(hash);
  670. }
  671. List<string> ValidateForm<T>(T obj)
  672. {
  673. var props = typeof(T).GetProperties().Where(pi => Attribute.IsDefined(pi, typeof(RequiredAttribute)));
  674. List<string> result = new();
  675. foreach (var prop in props)
  676. {
  677. var val = prop.GetValue(obj, null);
  678. if (val == null || val?.ToString().Length == 0)
  679. result.Add(prop.Name);
  680. //Console.WriteLine($"Required field '{prop.Name}' is not filled.");
  681. }
  682. return result;
  683. }
  684. async Task RequestParticipation()
  685. {
  686. await _modalSurvey.Open(_currentAccount.Name, 0);
  687. }
  688. static string GetDisplayName(Enum enumValue)
  689. {
  690. return enumValue.GetType()
  691. .GetMember(enumValue.ToString())
  692. .First()
  693. .GetCustomAttribute<DisplayAttribute>()
  694. .GetName();
  695. }
  696. async Task<AccountModel> GetCurrentAcc()
  697. {
  698. Console.WriteLine($"Desktop GetCurrentAcc");
  699. if (_currentAccount == null)
  700. {
  701. var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
  702. var user = authState.User;
  703. if (user.Identity.IsAuthenticated)
  704. {
  705. var currentUser = await UserManager.GetUserAsync(user);
  706. var acc = AccountModel.Find(currentUser.Id);
  707. if (acc != null)
  708. {
  709. _currentAccount = AccountModel.Loaded[currentUser.Id];
  710. }
  711. else
  712. {
  713. Dictionary<string, AccountModel> accounts = await MySQLConnector.Instance().SQLSelectASPUsers();
  714. if (accounts.ContainsKey(currentUser.Id))
  715. _currentAccount = accounts[currentUser.Id];
  716. }
  717. /// asp roles
  718. //var role1 = new IdentityRole { Name = "admin" };
  719. //var result = await RoleManager.CreateAsync(role1);
  720. //Console.WriteLine($"RoleManager create result: {result}");
  721. //result = await UserManager.AddToRoleAsync(currentUser, role1.Name);
  722. //Console.WriteLine($"RoleManager add role result: {result}");
  723. }
  724. }
  725. return _currentAccount;
  726. }
  727. }
  728. }