Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
RestoreConfiguration.cs
1using System;
3using System.IO;
4using System.IO.Compression;
6using System.Text;
7using System.Threading.Tasks;
8using System.Xml;
9using System.Xml.Xsl;
10using Waher.Content;
13using Waher.Events;
23using Waher.Script;
24
26{
30 public class RestoreConfiguration : SystemConfiguration, IDisposable
31 {
32 private static RestoreConfiguration instance = null;
33
34 private HttpResource uploadBackup = null;
35 private HttpResource uploadKey = null;
36 private HttpResource restore = null;
37
38 private readonly Dictionary<string, TemporaryFile> backupFilePerSession = new Dictionary<string, TemporaryFile>();
39 private readonly Dictionary<string, TemporaryFile> keyFilePerSession = new Dictionary<string, TemporaryFile>();
40 private int expectedBlockBackup = 0;
41 private int expectedBlockKey = 0;
42 private bool reloadConfiguration = false;
43
47 public static RestoreConfiguration Instance => instance;
48
52 public override string Resource => "/Settings/Restore.md";
53
57 public override int Priority => 150;
58
64 public override Task<string> Title(Language Language)
65 {
66 return Language.GetStringAsync(typeof(Gateway), 9, "Restore");
67 }
68
72 public override Task ConfigureSystem()
73 {
74 return Task.CompletedTask;
75 }
76
81 public override void SetStaticInstance(ISystemConfiguration Configuration)
82 {
83 instance = Configuration as RestoreConfiguration;
84 }
85
90 public override Task InitSetup(HttpServer WebServer)
91 {
92 this.uploadBackup = WebServer.Register("/Settings/UploadBackup", null, this.UploadBackup, true, false, true);
93 this.uploadKey = WebServer.Register("/Settings/UploadKey", null, this.UploadKey, true, false, true);
94 this.restore = WebServer.Register("/Settings/Restore", null, this.Restore, true, false, true);
95
96 WebServer.SessionRemoved += this.WebServer_SessionRemoved;
97
98 return base.InitSetup(WebServer);
99 }
100
105 public override Task UnregisterSetup(HttpServer WebServer)
106 {
107 WebServer.Unregister(this.uploadBackup);
108 WebServer.Unregister(this.uploadKey);
109 WebServer.Unregister(this.restore);
110
111 WebServer.SessionRemoved -= this.WebServer_SessionRemoved;
112
113 return base.UnregisterSetup(WebServer);
114 }
115
116 private Task WebServer_SessionRemoved(object Sender, CacheItemEventArgs<string, SessionVariables> e)
117 {
118 RemoveFile(e.Key, this.backupFilePerSession);
119 RemoveFile(e.Key, this.keyFilePerSession);
120
121 return Task.CompletedTask;
122 }
123
124 private static void RemoveFile(string Key, Dictionary<string, TemporaryFile> Files)
125 {
126 lock (Files)
127 {
128 if (Files.TryGetValue(Key, out TemporaryFile File))
129 {
130 Files.Remove(Key);
131 File.Dispose();
132 }
133 }
134 }
135
136 private async Task UploadBackup(HttpRequest Request, HttpResponse Response)
137 {
138 this.expectedBlockBackup = await this.Upload(Request, Response, this.expectedBlockBackup, this.backupFilePerSession, "backup");
139 }
140
141 private async Task UploadKey(HttpRequest Request, HttpResponse Response)
142 {
143 this.expectedBlockKey = await this.Upload(Request, Response, this.expectedBlockKey, this.keyFilePerSession, "key");
144 }
145
149 protected override string ConfigPrivilege => "Admin.Data.Restore";
150
151 private async Task<int> Upload(HttpRequest Request, HttpResponse Response, int ExpectedBlockNr, Dictionary<string, TemporaryFile> Files, string Name)
152 {
153 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
154
155 TemporaryFile File;
156 string TabID;
157 string HttpSessionID;
158
159 if (!Request.HasData ||
160 !Request.Header.TryGetHeaderField("X-TabID", out HttpField F) ||
161 string.IsNullOrEmpty(TabID = F.Value) ||
162 !Request.Header.TryGetHeaderField("X-BlockNr", out F) ||
163 !int.TryParse(F.Value, out int BlockNr) ||
164 !Request.Header.TryGetHeaderField("X-More", out F) ||
165 !CommonTypes.TryParse(F.Value, out bool More) ||
166 string.IsNullOrEmpty(HttpSessionID = HttpResource.GetSessionId(Request, Response)))
167 {
168 await Response.SendResponse(new BadRequestException());
169 return ExpectedBlockNr;
170 }
171
172 if (BlockNr == 0)
173 {
174 ExpectedBlockNr = 0;
175 RemoveFile(HttpSessionID, Files);
176
177 if (Request.Header.TryGetHeaderField("X-FileName", out F) && !string.IsNullOrEmpty(F.Value))
178 Request.Session[Name + "FileName"] = F.Value;
179 else
180 {
181 await Response.SendResponse(new BadRequestException());
182 return ExpectedBlockNr;
183 }
184 }
185
186 if (BlockNr != ExpectedBlockNr)
187 {
188 await Response.SendResponse(new BadRequestException());
189 return ExpectedBlockNr;
190 }
191
192 ExpectedBlockNr++;
193
194 lock (Files)
195 {
196 if (!Files.TryGetValue(HttpSessionID, out File))
197 {
198 File = new TemporaryFile();
199 Files[HttpSessionID] = File;
200 }
201 }
202
203 await Request.DataStream.CopyToAsync(File);
204
205 if (!More)
206 await File.FlushAsync();
207
208 ShowStatus(TabID, Name + "Bytes", Export.FormatBytes(File.Length) + " received of " + Name + " file.");
209
210 Response.StatusCode = 200;
211 Response.StatusMessage = "OK";
212
213 return ExpectedBlockNr;
214 }
215
216 internal static string[] GetTabIDs(string TabID)
217 {
218 if (string.IsNullOrEmpty(TabID))
219 return ClientEvents.GetTabIDs();
220 else
221 return new string[] { TabID };
222 }
223
224 private static void CollectionFound(string TabID, string CollectionName)
225 {
226 ClientEvents.PushEvent(GetTabIDs(TabID), "CollectionFound", CollectionName, false);
227 }
228
229 private static void ShowStatus(string TabID, string Id, string Message)
230 {
231 ClientEvents.PushEvent(GetTabIDs(TabID), "ShowStatus", JSON.Encode(new Dictionary<string, object>()
232 {
233 { "id", Id },
234 { "message", Message },
235 }, false), true);
236 }
237
238 private static void ShowStatus(string TabID, string Message)
239 {
240 ClientEvents.PushEvent(GetTabIDs(TabID), "ShowStatus", Message, false);
241 }
242
243 private async Task Restore(HttpRequest Request, HttpResponse Response)
244 {
245 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
246
247 TemporaryFile BackupFile;
248 TemporaryFile KeyFile;
249 string TabID;
250 string HttpSessionID;
251
252 if (!Request.HasData ||
253 !Request.Header.TryGetHeaderField("X-TabID", out HttpField F) ||
254 string.IsNullOrEmpty(TabID = F.Value) ||
255 string.IsNullOrEmpty(HttpSessionID = HttpResource.GetSessionId(Request, Response)))
256 {
257 await Response.SendResponse(new BadRequestException());
258 return;
259 }
260
261 ContentResponse Content = await Request.DecodeDataAsync();
262 if (Content.HasError || !(Content.Decoded is Dictionary<string, object> Parameters))
263 {
264 await Response.SendResponse(new BadRequestException());
265 return;
266 }
267
268 if (!Parameters.TryGetValue("overwrite", out object Obj) || !(Obj is bool Overwrite) ||
269 !Parameters.TryGetValue("onlySelectedCollections", out Obj) || !(Obj is bool OnlySelectedCollections) ||
270 !Parameters.TryGetValue("selectedCollections", out Obj) || !(Obj is Array SelectedCollections) ||
271 !Parameters.TryGetValue("selectedParts", out Obj) || !(Obj is Array SelectedParts))
272 {
273 await Response.SendResponse(new BadRequestException());
274 return;
275 }
276
277 BackupFile = GetAndRemoveFile(HttpSessionID, this.backupFilePerSession);
278 KeyFile = GetAndRemoveFile(HttpSessionID, this.keyFilePerSession);
279
280 Task _ = Task.Run(async () => await this.Restore(BackupFile, KeyFile, TabID, Request.Session["backupFileName"]?.ToString(),
281 Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts));
282
283 Response.StatusCode = 200;
284 Response.StatusMessage = "OK";
285 }
286
287 private static TemporaryFile GetAndRemoveFile(string SessionID, Dictionary<string, TemporaryFile> Files)
288 {
289 lock (Files)
290 {
291 if (Files.TryGetValue(SessionID, out TemporaryFile File))
292 {
293 Files.Remove(SessionID);
294 return File;
295 }
296 else
297 return null;
298 }
299 }
300
304 public void Dispose()
305 {
306 Clear(this.backupFilePerSession);
307 Clear(this.keyFilePerSession);
308 }
309
310 private static void Clear(Dictionary<string, TemporaryFile> Files)
311 {
312 if (!(Files is null))
313 {
314 lock (Files)
315 {
316 foreach (TemporaryFile File in Files.Values)
317 File.Dispose();
318
319 Files.Clear();
320 }
321 }
322 }
323
324 private async Task Restore(FileStream BackupFile, FileStream KeyFile, string TabID, string BackupFileName, bool Overwrite,
325 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
326 {
327 ICryptoTransform AesTransform1 = null;
328 ICryptoTransform AesTransform2 = null;
329 CryptoStream cs1 = null;
330 CryptoStream cs2 = null;
331
332 try
333 {
334 if (Overwrite)
335 ShowStatus(TabID, "Restoring backup.");
336 else
337 ShowStatus(TabID, "Validating backup.");
338
339 if (BackupFile is null || string.IsNullOrEmpty(BackupFileName))
340 throw new Exception("No backup file selected.");
341
342 string Extension = Path.GetExtension(BackupFileName);
343 ValidateBackupFile Import = new ValidateBackupFile(BackupFileName, null);
344
345 (AesTransform1, cs1) = await DoImport(BackupFile, KeyFile, TabID, Extension, Import, false,
346 false, Array.Empty<string>(), Array.Empty<string>());
347
348 if (Overwrite)
349 {
350 ShowStatus(TabID, "Restoring backup.");
351 Import = new RestoreBackupFile(BackupFileName, Import.ObjectIdMap);
352
353 (AesTransform2, cs2) = await DoImport(BackupFile, KeyFile, TabID, Extension, Import, true,
354 OnlySelectedCollections, SelectedCollections, SelectedParts);
355
356 this.reloadConfiguration = true;
357 await DoAnalyze(TabID);
358
359 Caches.ClearAll(false);
360 }
361
362 StringBuilder Result = new StringBuilder();
363
364 if (Overwrite)
365 Result.AppendLine("Restoration complete.");
366 else
367 Result.AppendLine("Verification complete.");
368
369 if (Import.NrCollections > 0 || Import.NrObjects > 0 || Import.NrProperties > 0 || Import.NrFiles > 0)
370 {
371 Result.AppendLine();
372 Result.AppendLine("Contents of file:");
373 Result.AppendLine();
374
375 if (Import.NrCollections > 0)
376 {
377 Result.Append(Import.NrCollections.ToString());
378 if (Import.NrCollections > 1)
379 Result.AppendLine(" collections.");
380 else
381 Result.AppendLine(" collection.");
382 }
383
384 if (Import.NrIndices > 0)
385 {
386 Result.Append(Import.NrIndices.ToString());
387 if (Import.NrIndices > 1)
388 Result.AppendLine(" indices.");
389 else
390 Result.AppendLine(" index.");
391 }
392
393 if (Import.NrBlocks > 0)
394 {
395 Result.Append(Import.NrBlocks.ToString());
396 if (Import.NrBlocks > 1)
397 Result.AppendLine(" blocks.");
398 else
399 Result.AppendLine(" block.");
400 }
401
402 if (Import.NrObjects > 0)
403 {
404 Result.Append(Import.NrObjects.ToString());
405 if (Import.NrObjects > 1)
406 Result.AppendLine(" objects.");
407 else
408 Result.AppendLine(" object.");
409 }
410
411 if (Import.NrEntries > 0)
412 {
413 Result.Append(Import.NrEntries.ToString());
414 if (Import.NrEntries > 1)
415 Result.AppendLine(" entries.");
416 else
417 Result.AppendLine(" entry.");
418 }
419
420 if (Import.NrProperties > 0)
421 {
422 Result.Append(Import.NrProperties.ToString());
423 if (Import.NrProperties > 1)
424 Result.AppendLine(" properties.");
425 else
426 Result.AppendLine(" property.");
427 }
428
429 if (Import.NrFiles > 0)
430 {
431 Result.Append(Import.NrFiles.ToString());
432 if (Import.NrFiles > 1)
433 Result.Append(" files");
434 else
435 Result.Append(" file");
436
437 Result.Append(" (");
438 Result.Append(Export.FormatBytes(Import.NrFileBytes));
439 Result.AppendLine(").");
440 }
441
442 if (Import is RestoreBackupFile Restore && Restore.NrObjectsFailed > 0)
443 {
444 Result.Append(Restore.NrObjectsFailed.ToString());
445 if (Import.NrProperties > 1)
446 Result.Append(" objects");
447 else
448 Result.Append(" object");
449
450 Result.AppendLine(" failed.");
451 }
452 }
453
454 if (Overwrite)
455 {
456 Result.AppendLine();
457 Result.Append("Click on the Next button to continue.");
458 }
459
460 await ClientEvents.PushEvent(GetTabIDs(TabID), "RestoreFinished", JSON.Encode(new Dictionary<string, object>()
461 {
462 { "ok", true },
463 { "message", Result.ToString() }
464 }, false), true);
465 }
466 catch (Exception ex)
467 {
468 Log.Exception(ex);
469 ShowStatus(TabID, "Failure: " + ex.Message);
470
471 await ClientEvents.PushEvent(GetTabIDs(TabID), "RestoreFinished", JSON.Encode(new Dictionary<string, object>()
472 {
473 { "ok", false },
474 { "message", ex.Message }
475 }, false), true);
476 }
477 finally
478 {
479 AesTransform1?.Dispose();
480 AesTransform2?.Dispose();
481 cs1?.Dispose();
482 cs2?.Dispose();
483 BackupFile?.Dispose();
484 KeyFile?.Dispose();
485 }
486 }
487
488 private static async Task<(ICryptoTransform, CryptoStream)> DoImport(FileStream BackupFile, FileStream KeyFile, string TabID,
489 string Extension, ValidateBackupFile Import, bool Overwrite, bool OnlySelectedCollections, Array SelectedCollections,
490 Array SelectedParts)
491 {
492 ICryptoTransform AesTransform = null;
493 CryptoStream cs = null;
494
495 BackupFile.Position = 0;
496
497 switch (Extension.ToLower())
498 {
499 case ".xml":
500 await RestoreXml(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
501 break;
502
503 case ".bin":
504 await RestoreBinary(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
505 break;
506
507 case ".gz":
508 await RestoreCompressed(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
509 break;
510
511 case ".bak":
512 if (KeyFile is null)
513 throw new Exception("No key file provided.");
514
515 KeyFile.Position = 0;
516
517 (AesTransform, cs) = await RestoreEncrypted(BackupFile, KeyFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
518 break;
519
520 default:
521 throw new Exception("Unrecognized file extension: " + Extension);
522 }
523
524 return (AesTransform, cs);
525 }
526
527 private static async Task RestoreXml(Stream BackupFile, string TabID, ValidateBackupFile Import, bool Overwrite,
528 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
529 {
530 XmlReaderSettings Settings = new XmlReaderSettings()
531 {
532 Async = true,
533 CloseInput = true,
534 ConformanceLevel = ConformanceLevel.Document,
535 CheckCharacters = true,
536 DtdProcessing = DtdProcessing.Prohibit,
537 IgnoreComments = true,
538 IgnoreProcessingInstructions = true,
539 IgnoreWhitespace = true
540 };
541 XmlReader r = XmlReader.Create(BackupFile, Settings);
542 DateTime LastReport = DateTime.Now;
543 KeyValuePair<string, object> P;
544 bool ImportCollection = !OnlySelectedCollections;
545 bool ImportPart = !OnlySelectedCollections;
546 bool DatabaseStarted = false;
547 bool LedgerStarted = false;
548 bool CollectionStarted = false;
549 bool IndexStarted = false;
550 bool BlockStarted = false;
551 bool FilesStarted = false;
552 bool FirstFile = true;
553
554 if (!r.ReadToFollowing("Export", XmlFileLedger.Namespace))
555 throw new Exception("Invalid backup XML file.");
556
557 await Import.Start();
558
559 while (await r.ReadAsync())
560 {
561 if (r.IsStartElement())
562 {
563 switch (r.LocalName)
564 {
565 case "Database":
566 if (r.Depth != 1)
567 throw new Exception("Database element not expected.");
568
569 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts, "Database") >= 0 || SelectedParts.Length == 0;
570
571 if (!ImportPart)
572 ShowStatus(TabID, "Skipping database section.");
573 else if (Overwrite)
574 ShowStatus(TabID, "Restoring database section.");
575 else
576 ShowStatus(TabID, "Validating database section.");
577
578 await Import.StartDatabase(null);
579 DatabaseStarted = true;
580 break;
581
582 case "Ledger":
583 if (r.Depth != 1)
584 throw new Exception("Ledger element not expected.");
585
586 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts, "Ledger") >= 0 || SelectedParts.Length == 0;
587
588 if (!ImportPart)
589 ShowStatus(TabID, "Skipping ledger section.");
590 else if (Overwrite)
591 ShowStatus(TabID, "Restoring ledger section.");
592 else
593 ShowStatus(TabID, "Validating ledger section.");
594
595 await Import.StartLedger(null);
596 LedgerStarted = true;
597 break;
598
599 case "Collection":
600 if (r.Depth != 2 || (!DatabaseStarted && !LedgerStarted))
601 throw new Exception("Collection element not expected.");
602
603 if (!r.MoveToAttribute("name"))
604 throw new Exception("Collection name missing.");
605
606 string CollectionName = r.Value;
607
608 if (IndexStarted)
609 {
610 await Import.EndIndex();
611 IndexStarted = false;
612 }
613 else if (BlockStarted)
614 {
615 await Import.EndBlock();
616 BlockStarted = false;
617 }
618
619 if (CollectionStarted)
620 await Import.EndCollection();
621
622 if (OnlySelectedCollections)
623 {
624 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
625 SelectedCollections.Length == 0);
626 }
627
628 if (ImportCollection)
629 {
630 await Import.StartCollection(CollectionName);
631 CollectionStarted = true;
632
633 CollectionFound(TabID, CollectionName);
634 }
635 else
636 CollectionStarted = false;
637 break;
638
639 case "Index":
640 if (r.Depth != 3 || !CollectionStarted)
641 throw new Exception("Index element not expected.");
642
643 if (ImportCollection)
644 {
645 await Import.StartIndex();
646 IndexStarted = true;
647 }
648 break;
649
650 case "Field":
651 if (r.Depth != 4 || !IndexStarted)
652 throw new Exception("Field element not expected.");
653
654 if (r.MoveToFirstAttribute())
655 {
656 string FieldName = null;
657 bool Ascending = true;
658
659 do
660 {
661 switch (r.LocalName)
662 {
663 case "name":
664 FieldName = r.Value;
665 break;
666
667 case "ascending":
668 if (!CommonTypes.TryParse(r.Value, out Ascending))
669 throw new Exception("Invalid boolean value.");
670 break;
671
672 case "xmlns":
673 break;
674
675 default:
676 throw new Exception("Unexpected attribute: " + r.LocalName);
677 }
678 }
679 while (r.MoveToNextAttribute());
680
681 if (string.IsNullOrEmpty(FieldName))
682 throw new Exception("Invalid field name.");
683
684 if (ImportCollection)
685 await Import.ReportIndexField(FieldName, Ascending);
686 }
687 else
688 throw new Exception("Field attributes expected.");
689
690 break;
691
692 case "Obj":
693 if (r.Depth == 3 && CollectionStarted)
694 {
695 if (IndexStarted)
696 {
697 await Import.EndIndex();
698 IndexStarted = false;
699 }
700
701 using (XmlReader r2 = r.ReadSubtree())
702 {
703 await r2.ReadAsync();
704
705 if (!r2.MoveToFirstAttribute())
706 throw new Exception("Object attributes missing.");
707
708 string ObjectId = null;
709 string TypeName = string.Empty;
710
711 do
712 {
713 switch (r2.LocalName)
714 {
715 case "id":
716 ObjectId = r2.Value;
717 break;
718
719 case "type":
720 TypeName = r2.Value;
721 break;
722
723 case "xmlns":
724 break;
725
726 default:
727 throw new Exception("Unexpected attribute: " + r2.LocalName);
728 }
729 }
730 while (r2.MoveToNextAttribute());
731
732 if (ImportCollection)
733 await Import.StartObject(ObjectId, TypeName);
734
735 while (await r2.ReadAsync())
736 {
737 if (r2.IsStartElement())
738 {
739 P = await ReadValue(r2);
740
741 if (ImportCollection)
742 await Import.ReportProperty(P.Key, P.Value);
743 }
744 }
745 }
746
747 if (ImportCollection)
748 await Import.EndObject();
749 }
750 else
751 throw new Exception("Obj element not expected.");
752
753 break;
754
755 case "Block":
756 if (r.Depth != 3 || !CollectionStarted)
757 throw new Exception("Block element not expected.");
758
759 if (!r.MoveToAttribute("id"))
760 throw new Exception("Block ID missing.");
761
762 string BlockID = r.Value;
763
764 if (ImportCollection)
765 {
766 await Import.StartBlock(BlockID);
767 BlockStarted = true;
768 }
769 break;
770
771 case "MetaData":
772 if (r.Depth == 4 && BlockStarted)
773 {
774 using XmlReader r2 = r.ReadSubtree();
775
776 await r2.ReadAsync();
777
778 while (await r2.ReadAsync())
779 {
780 if (r2.IsStartElement())
781 {
782 P = await ReadValue(r2);
783
784 if (ImportCollection)
785 await Import.BlockMetaData(P.Key, P.Value);
786 }
787 }
788 }
789 else
790 throw new Exception("MetaData element not expected.");
791 break;
792
793 case "New":
794 case "Update":
795 case "Delete":
796 case "Clear":
797 if (r.Depth != 4 || !CollectionStarted || !BlockStarted)
798 throw new Exception("Entry element not expected.");
799
800 EntryType EntryType = r.LocalName switch
801 {
802 "New" => EntryType.New,
803 "Update" => EntryType.Update,
804 "Delete" => EntryType.Delete,
805 "Clear" => EntryType.Clear,
806 _ => throw new Exception("Unexpected element: " + r.LocalName),
807 };
808
809 using (XmlReader r2 = r.ReadSubtree())
810 {
811 await r2.ReadAsync();
812
813 if (!r2.MoveToFirstAttribute())
814 throw new Exception("Object attributes missing.");
815
816 string ObjectId = null;
817 string TypeName = string.Empty;
818 DateTimeOffset EntryTimestamp = DateTimeOffset.MinValue;
819
820 do
821 {
822 switch (r2.LocalName)
823 {
824 case "id":
825 ObjectId = r2.Value;
826 break;
827
828 case "type":
829 TypeName = r2.Value;
830 break;
831
832 case "ts":
833 if (!XML.TryParse(r2.Value, out EntryTimestamp))
834 throw new Exception("Invalid Entry Timestamp: " + r2.Value);
835 break;
836
837 case "xmlns":
838 break;
839
840 default:
841 throw new Exception("Unexpected attribute: " + r2.LocalName);
842 }
843 }
844 while (r2.MoveToNextAttribute());
845
846 if (ImportCollection)
847 await Import.StartEntry(ObjectId, TypeName, EntryType, EntryTimestamp);
848
849 while (await r2.ReadAsync())
850 {
851 if (r2.IsStartElement())
852 {
853 P = await ReadValue(r2);
854
855 if (ImportCollection)
856 await Import.ReportProperty(P.Key, P.Value);
857 }
858 }
859 }
860
861 if (ImportCollection)
862 await Import.EndEntry();
863
864 break;
865
866 case "Files":
867 if (r.Depth != 1)
868 throw new Exception("Files element not expected.");
869
870 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts, "Files") >= 0 || SelectedParts.Length == 0;
871
872 if (IndexStarted)
873 {
874 await Import.EndIndex();
875 IndexStarted = false;
876 }
877 else if (BlockStarted)
878 {
879 await Import.EndBlock();
880 BlockStarted = false;
881 }
882
883 if (CollectionStarted)
884 {
885 await Import.EndCollection();
886 CollectionStarted = false;
887 }
888
889 if (DatabaseStarted)
890 {
891 await Import.EndDatabase();
892 DatabaseStarted = false;
893 }
894 else if (LedgerStarted)
895 {
896 await Import.EndLedger();
897 LedgerStarted = false;
898 }
899
900 if (!ImportPart)
901 ShowStatus(TabID, "Skipping files section.");
902 else if (Overwrite)
903 ShowStatus(TabID, "Restoring files section.");
904 else
905 ShowStatus(TabID, "Validating files section.");
906
907 await Import.StartFiles();
908 FilesStarted = true;
909 break;
910
911 case "File":
912 if (r.Depth != 2 || !FilesStarted)
913 throw new Exception("File element not expected.");
914
915 using (XmlReader r2 = r.ReadSubtree())
916 {
917 if (ImportPart)
918 {
919 await r2.ReadAsync();
920
921 if (!r2.MoveToAttribute("fileName"))
922 throw new Exception("File name missing.");
923
924 string FileName = r.Value;
925
926 if (Path.IsPathRooted(FileName))
927 {
928 if (FileName.StartsWith(Gateway.AppDataFolder))
929 FileName = FileName[Gateway.AppDataFolder.Length..];
930 else
931 throw new Exception("Absolute path names not allowed: " + FileName);
932 }
933
934 FileName = Path.Combine(Gateway.AppDataFolder, FileName);
935
936 using TemporaryFile fs = new TemporaryFile();
937
938 while (await r2.ReadAsync())
939 {
940 if (r2.IsStartElement())
941 {
942 while (r2.LocalName == "Chunk")
943 {
944 string Base64 = await r2.ReadElementContentAsStringAsync();
945 byte[] Data = Convert.FromBase64String(Base64);
946 fs.Write(Data, 0, Data.Length);
947 }
948 }
949 }
950
951 fs.Position = 0;
952
953 if (!OnlySelectedCollections)
954 {
955 if (FirstFile && FileName.EndsWith(Gateway.GatewayConfigLocalFileName, StringComparison.CurrentCultureIgnoreCase))
956 ImportGatewayConfig(fs);
957 else
958 await Import.ExportFile(FileName, fs);
959 }
960
961 FirstFile = false;
962 }
963 }
964 break;
965
966 default:
967 throw new Exception("Unexpected element: " + r.LocalName);
968 }
969 }
970
971 ShowReport(TabID, Import, ref LastReport, Overwrite);
972 }
973
974 if (IndexStarted)
975 await Import.EndIndex();
976 else if (BlockStarted)
977 await Import.EndBlock();
978
979 if (CollectionStarted)
980 await Import.EndCollection();
981
982 if (DatabaseStarted)
983 await Import.EndDatabase();
984 else if (LedgerStarted)
985 await Import.EndLedger();
986
987 if (FilesStarted)
988 await Import.EndFiles();
989
990 await Import.End();
991 ShowReport(TabID, Import, Overwrite);
992 }
993
994 private static void ShowReport(string TabID, ValidateBackupFile Import, ref DateTime LastReport, bool Overwrite)
995 {
996 DateTime Now = DateTime.Now;
997 if ((Now - LastReport).TotalSeconds >= 1)
998 {
999 LastReport = Now;
1000 ShowReport(TabID, Import, Overwrite);
1001 }
1002 }
1003
1004 private static void ShowReport(string TabID, ValidateBackupFile Import, bool Overwrite)
1005 {
1006 string Suffix = Overwrite ? "2" : "1";
1007
1008 if (Import.NrCollections > 0)
1009 ShowStatus(TabID, "NrCollections" + Suffix, Import.NrCollections.ToString() + " collections.");
1010
1011 if (Import.NrIndices > 0)
1012 ShowStatus(TabID, "NrIndices" + Suffix, Import.NrIndices.ToString() + " indices.");
1013
1014 if (Import.NrBlocks > 0)
1015 ShowStatus(TabID, "NrBlocks" + Suffix, Import.NrBlocks.ToString() + " blocks.");
1016
1017 if (Import.NrObjects > 0)
1018 ShowStatus(TabID, "NrObjects" + Suffix, Import.NrObjects.ToString() + " objects.");
1019
1020 if (Import.NrEntries > 0)
1021 ShowStatus(TabID, "NrEntries" + Suffix, Import.NrEntries.ToString() + " entries.");
1022
1023 if (Import.NrProperties > 0)
1024 ShowStatus(TabID, "NrProperties" + Suffix, Import.NrProperties.ToString() + " properties.");
1025
1026 if (Import.NrFiles > 0)
1027 ShowStatus(TabID, "NrFiles" + Suffix, Import.NrFiles.ToString() + " files (" + Export.FormatBytes(Import.NrFileBytes) + ").");
1028
1029 if (Import is RestoreBackupFile Restore && Restore.NrObjectsFailed > 0)
1030 ShowStatus(TabID, "NrFailed" + Suffix, Restore.NrObjectsFailed.ToString() + " objects failed.");
1031 }
1032
1033 private static async Task<KeyValuePair<string, object>> ReadValue(XmlReader r)
1034 {
1035 string PropertyType = r.LocalName;
1036 bool ReadSubtree = (PropertyType == "Bin" || PropertyType == "Array" || PropertyType == "Obj");
1037
1038 if (ReadSubtree)
1039 {
1040 r = r.ReadSubtree();
1041 await r.ReadAsync();
1042 }
1043
1044 if (!r.MoveToFirstAttribute())
1045 {
1046 if (ReadSubtree)
1047 r.Dispose();
1048
1049 throw new Exception("Property attributes missing.");
1050 }
1051
1052 string ElementType = null;
1053 string PropertyName = null;
1054 object Value = null;
1055
1056 do
1057 {
1058 switch (r.LocalName)
1059 {
1060 case "n":
1061 PropertyName = r.Value;
1062 break;
1063
1064 case "v":
1065 switch (PropertyType)
1066 {
1067 case "S":
1068 case "En":
1069 Value = r.Value;
1070 break;
1071
1072 case "S64":
1073 Value = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(r.Value));
1074 break;
1075
1076 case "Null":
1077 Value = null;
1078 break;
1079
1080 case "Bl":
1081 if (CommonTypes.TryParse(r.Value, out bool bl))
1082 Value = bl;
1083 else
1084 {
1085 if (ReadSubtree)
1086 r.Dispose();
1087
1088 throw new Exception("Invalid boolean value.");
1089 }
1090 break;
1091
1092 case "B":
1093 if (byte.TryParse(r.Value, out byte b))
1094 Value = b;
1095 else
1096 {
1097 if (ReadSubtree)
1098 r.Dispose();
1099
1100 throw new Exception("Invalid byte value.");
1101 }
1102 break;
1103
1104 case "Ch":
1105 string s = r.Value;
1106 if (s.Length == 1)
1107 Value = s[0];
1108 else
1109 {
1110 if (ReadSubtree)
1111 r.Dispose();
1112
1113 throw new Exception("Invalid character value.");
1114 }
1115 break;
1116
1117 case "CIS":
1118 Value = (CaseInsensitiveString)r.Value;
1119 break;
1120
1121 case "CIS64":
1122 Value = (CaseInsensitiveString)System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(r.Value));
1123 break;
1124
1125 case "DT":
1126 if (XML.TryParse(r.Value, out DateTime DT))
1127 Value = DT;
1128 else
1129 {
1130 if (ReadSubtree)
1131 r.Dispose();
1132
1133 throw new Exception("Invalid DateTime value.");
1134 }
1135 break;
1136
1137 case "DTO":
1138 if (XML.TryParse(r.Value, out DateTimeOffset DTO))
1139 Value = DTO;
1140 else
1141 {
1142 if (ReadSubtree)
1143 r.Dispose();
1144
1145 throw new Exception("Invalid DateTimeOffset value.");
1146 }
1147 break;
1148
1149 case "Dc":
1150 if (CommonTypes.TryParse(r.Value, out decimal dc))
1151 Value = dc;
1152 else
1153 {
1154 if (ReadSubtree)
1155 r.Dispose();
1156
1157 throw new Exception("Invalid Decimal value.");
1158 }
1159 break;
1160
1161 case "Db":
1162 if (CommonTypes.TryParse(r.Value, out double db))
1163 Value = db;
1164 else
1165 {
1166 if (ReadSubtree)
1167 r.Dispose();
1168
1169 throw new Exception("Invalid Double value.");
1170 }
1171 break;
1172
1173 case "I2":
1174 if (short.TryParse(r.Value, out short i2))
1175 Value = i2;
1176 else
1177 {
1178 if (ReadSubtree)
1179 r.Dispose();
1180
1181 throw new Exception("Invalid Int16 value.");
1182 }
1183 break;
1184
1185 case "I4":
1186 if (int.TryParse(r.Value, out int i4))
1187 Value = i4;
1188 else
1189 {
1190 if (ReadSubtree)
1191 r.Dispose();
1192
1193 throw new Exception("Invalid Int32 value.");
1194 }
1195 break;
1196
1197 case "I8":
1198 if (long.TryParse(r.Value, out long i8))
1199 Value = i8;
1200 else
1201 {
1202 if (ReadSubtree)
1203 r.Dispose();
1204
1205 throw new Exception("Invalid Int64 value.");
1206 }
1207 break;
1208
1209 case "I1":
1210 if (sbyte.TryParse(r.Value, out sbyte i1))
1211 Value = i1;
1212 else
1213 {
1214 if (ReadSubtree)
1215 r.Dispose();
1216
1217 throw new Exception("Invalid SByte value.");
1218 }
1219 break;
1220
1221 case "Fl":
1222 if (CommonTypes.TryParse(r.Value, out float fl))
1223 Value = fl;
1224 else
1225 {
1226 if (ReadSubtree)
1227 r.Dispose();
1228
1229 throw new Exception("Invalid Single value.");
1230 }
1231 break;
1232
1233 case "U2":
1234 if (ushort.TryParse(r.Value, out ushort u2))
1235 Value = u2;
1236 else
1237 {
1238 if (ReadSubtree)
1239 r.Dispose();
1240
1241 throw new Exception("Invalid UInt16 value.");
1242 }
1243 break;
1244
1245 case "U4":
1246 if (uint.TryParse(r.Value, out uint u4))
1247 Value = u4;
1248 else
1249 {
1250 if (ReadSubtree)
1251 r.Dispose();
1252
1253 throw new Exception("Invalid UInt32 value.");
1254 }
1255 break;
1256
1257 case "U8":
1258 if (ulong.TryParse(r.Value, out ulong u8))
1259 Value = u8;
1260 else
1261 {
1262 if (ReadSubtree)
1263 r.Dispose();
1264
1265 throw new Exception("Invalid UInt64 value.");
1266 }
1267 break;
1268
1269 case "TS":
1270 if (TimeSpan.TryParse(r.Value, out TimeSpan TS))
1271 Value = TS;
1272 else
1273 {
1274 if (ReadSubtree)
1275 r.Dispose();
1276
1277 throw new Exception("Invalid TimeSpan value.");
1278 }
1279 break;
1280
1281 case "Bin":
1282 if (ReadSubtree)
1283 r.Dispose();
1284
1285 throw new Exception("Binary member values are reported using child elements.");
1286
1287 case "ID":
1288 if (Guid.TryParse(r.Value, out Guid Id))
1289 Value = Id;
1290 else
1291 {
1292 if (ReadSubtree)
1293 r.Dispose();
1294
1295 throw new Exception("Invalid GUID value.");
1296 }
1297 break;
1298
1299 case "Array":
1300 if (ReadSubtree)
1301 r.Dispose();
1302
1303 throw new Exception("Arrays report values as child elements.");
1304
1305 case "Obj":
1306 if (ReadSubtree)
1307 r.Dispose();
1308
1309 throw new Exception("Objects report member values as child elements.");
1310
1311 default:
1312 if (ReadSubtree)
1313 r.Dispose();
1314
1315 throw new Exception("Unexpected property type: " + PropertyType);
1316 }
1317 break;
1318
1319 case "elementType":
1320 case "type":
1321 ElementType = r.Value;
1322 break;
1323
1324 case "xmlns":
1325 break;
1326
1327 default:
1328 if (ReadSubtree)
1329 r.Dispose();
1330
1331 throw new Exception("Unexpected attribute: " + r.LocalName);
1332 }
1333 }
1334 while (r.MoveToNextAttribute());
1335
1336 if (!(ElementType is null))
1337 {
1338 switch (PropertyType)
1339 {
1340 case "Array":
1341 List<object> List = new List<object>();
1342
1343 while (await r.ReadAsync())
1344 {
1345 if (r.IsStartElement())
1346 {
1347 KeyValuePair<string, object> P = await ReadValue(r);
1348 if (!string.IsNullOrEmpty(P.Key))
1349 {
1350 if (ReadSubtree)
1351 r.Dispose();
1352
1353 throw new Exception("Arrays do not contain property names.");
1354 }
1355
1356 List.Add(P.Value);
1357 }
1358 else if (r.NodeType == XmlNodeType.EndElement)
1359 break;
1360 }
1361
1362 Value = List.ToArray();
1363 break;
1364
1365 case "Obj":
1366 GenericObject GenObj = new GenericObject(string.Empty, ElementType, Guid.Empty);
1367 Value = GenObj;
1368
1369 while (await r.ReadAsync())
1370 {
1371 if (r.IsStartElement())
1372 {
1373 KeyValuePair<string, object> P = await ReadValue(r);
1374 GenObj[P.Key] = P.Value;
1375 }
1376 else if (r.NodeType == XmlNodeType.EndElement)
1377 break;
1378 }
1379 break;
1380
1381 default:
1382 if (ReadSubtree)
1383 r.Dispose();
1384
1385 throw new Exception("Type only valid option for arrays and objects.");
1386 }
1387 }
1388 else if (PropertyType == "Bin")
1389 {
1390 MemoryStream Bin = new MemoryStream();
1391
1392 while (await r.ReadAsync())
1393 {
1394 if (r.IsStartElement())
1395 {
1396 try
1397 {
1398 while (r.LocalName == "Chunk")
1399 {
1400 string Base64 = await r.ReadElementContentAsStringAsync();
1401 byte[] Data = Convert.FromBase64String(Base64);
1402 Bin.Write(Data, 0, Data.Length);
1403 }
1404 }
1405 catch (Exception ex)
1406 {
1407 if (ReadSubtree)
1408 r.Dispose();
1409
1410 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
1411 }
1412 }
1413 else if (r.NodeType == XmlNodeType.EndElement)
1414 break;
1415 }
1416
1417 Value = Bin.ToArray();
1418 }
1419
1420 if (ReadSubtree)
1421 r.Dispose();
1422
1423 return new KeyValuePair<string, object>(PropertyName, Value);
1424 }
1425
1426 private static async Task RestoreBinary(Stream BackupFile, string TabID, ValidateBackupFile Import, bool Overwrite,
1427 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1428 {
1429 int Version = BackupFile.ReadByte();
1430 if (Version != 1)
1431 throw new Exception("File version not supported.");
1432
1433 DateTime LastReport = DateTime.Now;
1434 byte Command;
1435 bool ImportCollection = !OnlySelectedCollections;
1436 bool ImportPart;
1437
1438 using BinaryReader r = new BinaryReader(BackupFile, System.Text.Encoding.UTF8, true);
1439 string s = r.ReadString();
1440 if (s != BinaryExportFormat.Preamble)
1441 throw new Exception("Invalid backup file.");
1442
1443 await Import.Start();
1444
1445 while ((Command = r.ReadByte()) != 0)
1446 {
1447 switch (Command)
1448 {
1449 case 1:
1450 throw new Exception("Obsolete file."); // 1 is obsolete (previously XMPP Credentials)
1451
1452 case 2: // Database
1453 string CollectionName;
1454 string ObjectId;
1455 string TypeName;
1456 string FieldName;
1457 bool Ascending;
1458
1459 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts, "Database") >= 0 || SelectedParts.Length == 0;
1460
1461 if (!ImportPart)
1462 ShowStatus(TabID, "Skipping database section.");
1463 else if (Overwrite)
1464 ShowStatus(TabID, "Restoring database section.");
1465 else
1466 ShowStatus(TabID, "Validating database section.");
1467
1468 await Import.StartDatabase(null);
1469
1470 while (!string.IsNullOrEmpty(CollectionName = r.ReadString()))
1471 {
1472 if (OnlySelectedCollections)
1473 {
1474 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
1475 SelectedCollections.Length == 0);
1476 }
1477
1478 if (ImportCollection)
1479 {
1480 await Import.StartCollection(CollectionName);
1481 CollectionFound(TabID, CollectionName);
1482 }
1483
1484 byte b;
1485
1486 while ((b = r.ReadByte()) != 0)
1487 {
1488 switch (b)
1489 {
1490 case 1:
1491 if (ImportCollection)
1492 await Import.StartIndex();
1493
1494 while (!string.IsNullOrEmpty(FieldName = r.ReadString()))
1495 {
1496 Ascending = r.ReadBoolean();
1497
1498 if (ImportCollection)
1499 await Import.ReportIndexField(FieldName, Ascending);
1500 }
1501
1502 if (ImportCollection)
1503 await Import.EndIndex();
1504 break;
1505
1506 case 2:
1507 ObjectId = r.ReadString();
1508 TypeName = r.ReadString();
1509
1510 if (ImportCollection)
1511 await Import.StartObject(ObjectId, TypeName);
1512
1513 byte PropertyType = r.ReadByte();
1514 string PropertyName = r.ReadString();
1515 object PropertyValue;
1516
1517 while (!string.IsNullOrEmpty(PropertyName))
1518 {
1519 PropertyValue = ReadValue(r, PropertyType);
1520
1521 if (ImportCollection)
1522 await Import.ReportProperty(PropertyName, PropertyValue);
1523
1524 PropertyType = r.ReadByte();
1525 PropertyName = r.ReadString();
1526 }
1527
1528 if (ImportCollection)
1529 await Import.EndObject();
1530 break;
1531
1532 default:
1533 throw new Exception("Unsupported collection section: " + b.ToString());
1534 }
1535
1536 ShowReport(TabID, Import, ref LastReport, Overwrite);
1537 }
1538
1539 if (ImportCollection)
1540 await Import.EndCollection();
1541 }
1542
1543 await Import.EndDatabase();
1544 break;
1545
1546 case 3: // Files
1547 string FileName;
1548 int MaxLen = 256 * 1024;
1549 byte[] Buffer = new byte[MaxLen];
1550
1551 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts, "Files") >= 0 || SelectedParts.Length == 0;
1552
1553 if (!ImportPart)
1554 ShowStatus(TabID, "Skipping files section.");
1555 else if (Overwrite)
1556 ShowStatus(TabID, "Restoring files section.");
1557 else
1558 ShowStatus(TabID, "Validating files section.");
1559
1560 await Import.StartFiles();
1561
1562 bool FirstFile = true;
1563
1564 while (!string.IsNullOrEmpty(FileName = r.ReadString()))
1565 {
1566 long Length = r.ReadInt64();
1567
1568 if (Path.IsPathRooted(FileName))
1569 {
1570 if (FileName.StartsWith(Gateway.AppDataFolder))
1571 FileName = FileName[Gateway.AppDataFolder.Length..];
1572 else
1573 throw new Exception("Absolute path names not allowed: " + FileName);
1574 }
1575
1576 FileName = Path.Combine(Gateway.AppDataFolder, FileName);
1577
1578 using TemporaryFile File = new TemporaryFile();
1579
1580 while (Length > 0)
1581 {
1582 int Nr = r.Read(Buffer, 0, (int)Math.Min(Length, MaxLen));
1583 Length -= Nr;
1584 await File.WriteAsync(Buffer, 0, Nr);
1585 }
1586
1587 File.Position = 0;
1588 if (ImportPart)
1589 {
1590 try
1591 {
1592 if (FirstFile && FileName.EndsWith(Gateway.GatewayConfigLocalFileName, StringComparison.CurrentCultureIgnoreCase))
1593 ImportGatewayConfig(File);
1594 else
1595 await Import.ExportFile(FileName, File);
1596
1597 ShowReport(TabID, Import, ref LastReport, Overwrite);
1598 }
1599 catch (Exception ex)
1600 {
1601 ShowStatus(TabID, "Unable to restore " + FileName + ": " + ex.Message);
1602 }
1603 }
1604
1605 FirstFile = false;
1606 }
1607
1608 await Import.EndFiles();
1609 break;
1610
1611 case 4:
1612 throw new Exception("Export file contains reported errors.");
1613
1614 case 5:
1615 throw new Exception("Export file contains reported exceptions.");
1616
1617 case 6: // Ledger
1618
1619 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts, "Ledger") >= 0 || SelectedParts.Length == 0;
1620
1621 if (!ImportPart)
1622 ShowStatus(TabID, "Skipping ledger section.");
1623 else if (Overwrite)
1624 ShowStatus(TabID, "Restoring ledger section.");
1625 else
1626 ShowStatus(TabID, "Validating ledger section.");
1627
1628 await Import.StartLedger(null);
1629
1630 while (!string.IsNullOrEmpty(CollectionName = r.ReadString()))
1631 {
1632 if (OnlySelectedCollections)
1633 {
1634 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
1635 SelectedCollections.Length == 0);
1636 }
1637
1638 if (ImportCollection)
1639 {
1640 await Import.StartCollection(CollectionName);
1641 CollectionFound(TabID, CollectionName);
1642 }
1643
1644 byte b;
1645
1646 while ((b = r.ReadByte()) != 0)
1647 {
1648 switch (b)
1649 {
1650 case 1:
1651 string BlockID = r.ReadString();
1652 if (ImportCollection)
1653 await Import.StartBlock(BlockID);
1654 break;
1655
1656 case 2:
1657 ObjectId = r.ReadString();
1658 TypeName = r.ReadString();
1659 EntryType EntryType = (EntryType)r.ReadByte();
1660 DateTimeKind Kind = (DateTimeKind)r.ReadByte();
1661 long Ticks = r.ReadInt64();
1662 DateTime DT = new DateTime(Ticks, Kind);
1663 Ticks = r.ReadInt64();
1664 Ticks -= Ticks % 600000000; // Offsets must be in whole minutes.
1665 TimeSpan TS = new TimeSpan(Ticks);
1666 DateTimeOffset EntryTimestamp = new DateTimeOffset(DT, TS);
1667
1668 if (ImportCollection)
1669 await Import.StartEntry(ObjectId, TypeName, EntryType, EntryTimestamp);
1670
1671 byte PropertyType = r.ReadByte();
1672 string PropertyName = r.ReadString();
1673 object PropertyValue;
1674
1675 while (!string.IsNullOrEmpty(PropertyName))
1676 {
1677 PropertyValue = ReadValue(r, PropertyType);
1678
1679 if (ImportCollection)
1680 await Import.ReportProperty(PropertyName, PropertyValue);
1681
1682 PropertyType = r.ReadByte();
1683 PropertyName = r.ReadString();
1684 }
1685
1686 if (ImportCollection)
1687 await Import.EndEntry();
1688 break;
1689
1690 case 3:
1691 if (ImportCollection)
1692 await Import.EndBlock();
1693 break;
1694
1695 case 4:
1696 PropertyName = r.ReadString();
1697 PropertyType = r.ReadByte();
1698 PropertyValue = ReadValue(r, PropertyType);
1699
1700 await Import.BlockMetaData(PropertyName, PropertyValue);
1701 break;
1702
1703 default:
1704 throw new Exception("Unsupported collection section: " + b.ToString());
1705 }
1706
1707 ShowReport(TabID, Import, ref LastReport, Overwrite);
1708 }
1709
1710 if (ImportCollection)
1711 await Import.EndCollection();
1712 }
1713
1714 await Import.EndLedger();
1715 break;
1716
1717 default:
1718 throw new Exception("Unsupported section: " + Command.ToString());
1719 }
1720 }
1721
1722 await Import.End();
1723 ShowReport(TabID, Import, Overwrite);
1724 }
1725
1726 private static void ImportGatewayConfig(Stream File)
1727 {
1728 XmlDocument Doc = new XmlDocument()
1729 {
1730 PreserveWhitespace = true
1731 };
1732 Doc.Load(File);
1733
1734 string OriginalFileName = Gateway.ConfigFilePath;
1735 XmlDocument Original = XML.LoadFromFile(OriginalFileName, true);
1736
1737 if (!(Doc.DocumentElement is null) && Doc.DocumentElement.LocalName == "GatewayConfiguration")
1738 {
1739 List<KeyValuePair<string, string>> DefaultPages = null;
1740
1741 foreach (XmlNode N in Doc.DocumentElement.ChildNodes)
1742 {
1743 if (N is XmlElement E)
1744 {
1745 switch (E.LocalName)
1746 {
1747 case "ApplicationName":
1748 string s = E.InnerText;
1749 Gateway.ApplicationName = s;
1750 Original.DocumentElement["ApplicationName"].InnerText = s;
1751 break;
1752
1753 case "DefaultPage":
1754 s = E.InnerText;
1755 string Host = XML.Attribute(E, "host");
1756
1757 DefaultPages ??= new List<KeyValuePair<string, string>>();
1758 DefaultPages.Add(new KeyValuePair<string, string>(Host, s));
1759 break;
1760
1761 // TODO: Ports ?
1762 // TODO: FileFolders ?
1763 }
1764 }
1765 }
1766
1767 if (!(DefaultPages is null))
1768 {
1769 Gateway.SetDefaultPages(DefaultPages.ToArray());
1770
1771 foreach (XmlNode N in Original.DocumentElement.ChildNodes)
1772 {
1773 if (N is XmlElement E && E.LocalName == "DefaultPage")
1774 {
1775 string Host = XML.Attribute(E, "host");
1776 if (Gateway.TryGetDefaultPage(Host, out string DefaultPage))
1777 E.InnerText = DefaultPage;
1778 }
1779 }
1780 }
1781 }
1782
1783 Original.Save(OriginalFileName);
1784 }
1785
1786 private static object ReadValue(BinaryReader r, byte PropertyType)
1787 {
1788 switch (PropertyType)
1789 {
1790 case BinaryExportFormat.TYPE_BOOLEAN: return r.ReadBoolean();
1791 case BinaryExportFormat.TYPE_BYTE: return r.ReadByte();
1792 case BinaryExportFormat.TYPE_INT16: return r.ReadInt16();
1793 case BinaryExportFormat.TYPE_INT32: return r.ReadInt32();
1794 case BinaryExportFormat.TYPE_INT64: return r.ReadInt64();
1795 case BinaryExportFormat.TYPE_SBYTE: return r.ReadSByte();
1796 case BinaryExportFormat.TYPE_UINT16: return r.ReadUInt16();
1797 case BinaryExportFormat.TYPE_UINT32: return r.ReadUInt32();
1798 case BinaryExportFormat.TYPE_UINT64: return r.ReadUInt64();
1799 case BinaryExportFormat.TYPE_DECIMAL: return r.ReadDecimal();
1800 case BinaryExportFormat.TYPE_DOUBLE: return r.ReadDouble();
1801 case BinaryExportFormat.TYPE_SINGLE: return r.ReadSingle();
1802 case BinaryExportFormat.TYPE_CHAR: return r.ReadChar();
1803 case BinaryExportFormat.TYPE_STRING: return r.ReadString();
1804 case BinaryExportFormat.TYPE_ENUM: return r.ReadString();
1805 case BinaryExportFormat.TYPE_NULL: return null;
1806
1808 DateTimeKind Kind = (DateTimeKind)((int)r.ReadByte());
1809 long Ticks = r.ReadInt64();
1810 return new DateTime(Ticks, Kind);
1811
1813 Ticks = r.ReadInt64();
1814 return new TimeSpan(Ticks);
1815
1817 int Count = r.ReadInt32();
1818 return r.ReadBytes(Count);
1819
1821 byte[] Bin = r.ReadBytes(16);
1822 return new Guid(Bin);
1823
1825 Kind = (DateTimeKind)((int)r.ReadByte());
1826 Ticks = r.ReadInt64();
1827 DateTime DT = new DateTime(Ticks, Kind);
1828 Ticks = r.ReadInt64();
1829 Ticks -= Ticks % 600000000; // Offsets must be in whole minutes.
1830 TimeSpan TS = new TimeSpan(Ticks);
1831 return new DateTimeOffset(DT, TS);
1832
1834 return (CaseInsensitiveString)r.ReadString();
1835
1837 r.ReadString(); // Type name
1838 long NrElements = r.ReadInt64();
1839
1840 List<object> List = new List<object>();
1841
1842 while (NrElements > 0)
1843 {
1844 NrElements--;
1845 PropertyType = r.ReadByte();
1846 List.Add(ReadValue(r, PropertyType));
1847 }
1848
1849 return List.ToArray();
1850
1852 string TypeName = r.ReadString();
1853 GenericObject Object = new GenericObject(string.Empty, TypeName, Guid.Empty);
1854
1855 PropertyType = r.ReadByte();
1856 string PropertyName = r.ReadString();
1857
1858 while (!string.IsNullOrEmpty(PropertyName))
1859 {
1860 Object[PropertyName] = ReadValue(r, PropertyType);
1861
1862 PropertyType = r.ReadByte();
1863 PropertyName = r.ReadString();
1864 }
1865
1866 return Object;
1867
1868 default:
1869 throw new Exception("Unsupported property type: " + PropertyType.ToString());
1870 }
1871 }
1872
1873 private static async Task RestoreCompressed(Stream BackupFile, string TabID, ValidateBackupFile Import, bool Overwrite,
1874 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1875 {
1876 using GZipStream gz = new GZipStream(BackupFile, CompressionMode.Decompress, true);
1877
1878 await RestoreBinary(gz, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
1879 }
1880
1881 private static async Task<(ICryptoTransform, CryptoStream)> RestoreEncrypted(Stream BackupFile, Stream KeyFile, string TabID, ValidateBackupFile Import,
1882 bool Overwrite, bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1883 {
1884 XmlDocument Doc = new XmlDocument()
1885 {
1886 PreserveWhitespace = true
1887 };
1888
1889 try
1890 {
1891 Doc.Load(KeyFile);
1892 }
1893 catch (Exception)
1894 {
1895 throw new Exception("Invalid key file.");
1896 }
1897
1898 XmlElement KeyAes256 = Doc.DocumentElement;
1899 if (KeyAes256.LocalName != "KeyAes256" ||
1900 KeyAes256.NamespaceURI != XmlFileLedger.Namespace ||
1901 !KeyAes256.HasAttribute("key") ||
1902 !KeyAes256.HasAttribute("iv"))
1903 {
1904 throw new Exception("Invalid key file.");
1905 }
1906
1907 byte[] Key = Convert.FromBase64String(KeyAes256.Attributes["key"].Value);
1908 byte[] IV = Convert.FromBase64String(KeyAes256.Attributes["iv"].Value);
1909
1910 ICryptoTransform AesTransform = WebResources.StartExport.aes.CreateDecryptor(Key, IV);
1911 CryptoStream cs = new CryptoStream(BackupFile, AesTransform, CryptoStreamMode.Read);
1912
1913 await RestoreCompressed(cs, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
1914
1915 return (AesTransform, cs);
1916 }
1917
1918 private static async Task DoAnalyze(string TabID)
1919 {
1920 ShowStatus(TabID, "Analyzing database.");
1921
1922 string XmlPath = Path.Combine(Gateway.AppDataFolder, "Restore.xml");
1923 string HtmlPath = Path.Combine(Gateway.AppDataFolder, "Restore.html");
1924 string XsltPath = Path.Combine(Gateway.AppDataFolder, "Transforms", "DbStatXmlToHtml.xslt");
1925 using (FileStream fs = File.Create(XmlPath))
1926 {
1927 XmlWriterSettings Settings = XML.WriterSettings(true, false);
1928 using XmlWriter w = XmlWriter.Create(fs, Settings);
1929 await Database.Analyze(w, XsltPath, Gateway.AppDataFolder, false);
1930 w.Flush();
1931 fs.Flush();
1932 }
1933
1934 XslCompiledTransform Xslt = XSL.LoadTransform(typeof(Gateway).Namespace + ".Transforms.DbStatXmlToHtml.xslt");
1935
1936 string s = await Files.ReadAllTextAsync(XmlPath);
1937 s = XSL.Transform(s, Xslt);
1938 byte[] Bin = Strings.Utf8WithBom.GetBytes(s);
1939
1940 await Files.WriteAllBytesAsync(HtmlPath, Bin);
1941
1942 ShowStatus(TabID, "Database analysis successfully completed.");
1943
1944 /*
1945 int i = s.IndexOf("<body>");
1946 if (i > 0)
1947 s = s.Substring(i + 6);
1948
1949 i = s.IndexOf("</body>");
1950 if (i > 0)
1951 s = s.Substring(0, i);
1952
1953 ClientEvents.PushEvent(GetTabIDs(TabID), "ShowStatus", JSON.Encode(new Dictionary<string, object>()
1954 {
1955 { "html", s },
1956 }, false), true);
1957 */
1958 }
1959
1963 public override Task MakeCompleted()
1964 {
1965 return this.MakeCompleted(this.reloadConfiguration);
1966 }
1967
1972 public override Task<bool> SimplifiedConfiguration()
1973 {
1974 return Task.FromResult(true);
1975 }
1976
1980 public const string GATEWAY_RESTORE = nameof(GATEWAY_RESTORE);
1981
1985 public const string GATEWAY_RESTORE_BAKFILE = nameof(GATEWAY_RESTORE_BAKFILE);
1986
1990 public const string GATEWAY_RESTORE_KEYFILE = nameof(GATEWAY_RESTORE_KEYFILE);
1991
1995 public const string GATEWAY_RESTORE_OVERWRITE = nameof(GATEWAY_RESTORE_OVERWRITE);
1996
2000 public const string GATEWAY_RESTORE_COLLECTIONS = nameof(GATEWAY_RESTORE_COLLECTIONS);
2001
2005 public const string GATEWAY_RESTORE_PARTS = nameof(GATEWAY_RESTORE_PARTS);
2006
2011 public override async Task<bool> EnvironmentConfiguration()
2012 {
2013 if (!this.TryGetEnvironmentVariable(GATEWAY_RESTORE, false, out bool Restore))
2014 return false;
2015
2016 if (!Restore)
2017 return true;
2018
2019 if (!this.TryGetEnvironmentVariable(GATEWAY_RESTORE_BAKFILE, true, out string BakFileName) ||
2020 !this.TryGetEnvironmentVariable(GATEWAY_RESTORE_OVERWRITE, true, out bool OverWrite))
2021 {
2022 return false;
2023 }
2024
2025 string KeyFileName = Environment.GetEnvironmentVariable(GATEWAY_RESTORE_KEYFILE);
2026 string CollectionsStr = Environment.GetEnvironmentVariable(GATEWAY_RESTORE_COLLECTIONS);
2027 string PartsStr = Environment.GetEnvironmentVariable(GATEWAY_RESTORE_PARTS);
2028 string[] Collections;
2029 string[] Parts;
2030
2031 if (string.IsNullOrEmpty(CollectionsStr))
2032 Collections = Array.Empty<string>();
2033 else
2034 Collections = CollectionsStr.Split(',');
2035
2036 if (string.IsNullOrEmpty(PartsStr))
2037 Parts = Array.Empty<string>();
2038 else
2039 Parts = PartsStr.Split(',');
2040
2041 FileStream BakFile = null;
2042 FileStream KeyFile = null;
2043
2044 try
2045 {
2046 try
2047 {
2048 BakFile = File.OpenRead(BakFileName);
2049 }
2050 catch (Exception ex)
2051 {
2052 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_BAKFILE, BakFileName);
2053 return false;
2054 }
2055
2056 if (!string.IsNullOrEmpty(KeyFileName))
2057 {
2058 try
2059 {
2060 KeyFile = File.OpenRead(KeyFileName);
2061 }
2062 catch (Exception ex)
2063 {
2064 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_KEYFILE, KeyFileName);
2065 return false;
2066 }
2067 }
2068
2069 await this.Restore(BakFile, KeyFile, string.Empty, BakFileName, OverWrite, Collections.Length > 0, Collections, Parts);
2070 }
2071 catch (Exception ex)
2072 {
2073 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_BAKFILE, BakFileName);
2074 return false;
2075 }
2076 finally
2077 {
2078 BakFile?.Dispose();
2079 KeyFile?.Dispose();
2080 }
2081
2082 return true;
2083 }
2084
2085 }
2086}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static XmlDocument LoadFromFile(string FileName)
Loads an XML document from a file.
Definition: XML.cs:1808
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:892
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Definition: XML.cs:1351
Static class managing loading of XSL resources stored as embedded resources or in content files.
Definition: XSL.cs:16
static XslCompiledTransform LoadTransform(string ResourceName)
Loads an XSL transformation from an embedded resource.
Definition: XSL.cs:86
static string Transform(string XML, XslCompiledTransform Transform)
Transforms an XML document using an XSL transform.
Definition: XSL.cs:197
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
Definition: ClientEvents.cs:51
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
static string[] GetTabIDs()
Gets all open Tab IDs.
Static class managing data export.
Definition: Export.cs:18
static string FormatBytes(double Bytes)
Formats a file size using appropriate unit.
Definition: Export.cs:125
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
Definition: Gateway.cs:3868
static string ConfigFilePath
Full path to Gateway.config file.
Definition: Gateway.cs:3181
static bool TryGetDefaultPage(HttpRequest Request, out string DefaultPage)
Tries to get the default page of a host.
Definition: Gateway.cs:3251
static string AppDataFolder
Application data folder.
Definition: Gateway.cs:3132
const string GatewayConfigLocalFileName
Gateway.config
Definition: Gateway.cs:151
override Task ConfigureSystem()
Is called during startup to configure the system.
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
override async Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
override Task MakeCompleted()
Sets the configuration task as completed.
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
static RestoreConfiguration Instance
Current instance of configuration.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
Abstract base class for system configurations.
const byte TYPE_CI_STRING
Represents a CaseInsensitiveString
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP fields.
Definition: HttpField.cs:7
bool TryGetHeaderField(string FieldName, out HttpField Field)
Tries to get a named header field.
Definition: HttpHeader.cs:247
Represents an HTTP request.
Definition: HttpRequest.cs:22
Stream DataStream
Data stream, if data is available, or null if data is not available.
Definition: HttpRequest.cs:187
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
SessionVariables Session
Contains session states, if the resource requires sessions, or null otherwise.
Definition: HttpRequest.cs:212
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
Base class for all HTTP resources.
Definition: HttpResource.cs:23
static string GetSessionId(HttpRequest Request, HttpResponse Response)
Gets the session ID used for a request.
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Implements an HTTP server.
Definition: HttpServer.cs:41
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
Definition: HttpServer.cs:1568
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Definition: HttpServer.cs:1743
Represents a case-insensitive string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData)
Analyzes the database and exports findings to XML.
Definition: Database.cs:1985
Generic object. Contains a sequence of properties.
Simple ledger that records anything that happens in the database to XML files in the program data fol...
const string Namespace
http://waher.se/Schema/Export.xsd
Event arguments for cache item removal events.
KeyType Key
Key of item that was removed.
Repository of all active caches.
Definition: Caches.cs:11
static void ClearAll()
Clears all active caches.
Definition: Caches.cs:65
Contains static methods
Definition: Files.cs:14
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Definition: Files.cs:33
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Definition: Files.cs:84
Static class managing binary representations of strings.
Definition: Strings.cs:10
static Encoding Utf8WithBom
UTF-8 encoding with Byte Order Mark (BOM)
Definition: Strings.cs:231
Contains information about a language.
Definition: Language.cs:17
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Definition: Language.cs:209
Class managing the contents of a temporary file. When the class is disposed, the temporary file is de...
override void Dispose(bool disposing)
Disposes of the object, and deletes the temporary file.
Interface for system configurations. The gateway will scan all module for system configuration classe...
Definition: ImplTypes.g.cs:58
PropertyType
Type of indexed property.
EntryType
Ledger entry type.
Definition: ILedgerEntry.cs:9