Desktop.razor.cs 30 KB

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