Desktop.razor.cs 35 KB

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