4using System.IO.Compression;
7using System.Threading.Tasks;
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;
52 public override string Resource =>
"/Settings/Restore.md";
74 return Task.CompletedTask;
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);
96 WebServer.SessionRemoved += this.WebServer_SessionRemoved;
98 return base.InitSetup(WebServer);
111 WebServer.SessionRemoved -= this.WebServer_SessionRemoved;
113 return base.UnregisterSetup(WebServer);
118 RemoveFile(e.
Key,
this.backupFilePerSession);
119 RemoveFile(e.
Key,
this.keyFilePerSession);
121 return Task.CompletedTask;
124 private static void RemoveFile(
string Key, Dictionary<string, TemporaryFile>
Files)
138 this.expectedBlockBackup = await this.Upload(Request, Response, this.expectedBlockBackup, this.backupFilePerSession,
"backup");
143 this.expectedBlockKey = await this.Upload(Request, Response, this.expectedBlockKey, this.keyFilePerSession,
"key");
151 private async Task<int> Upload(
HttpRequest Request,
HttpResponse Response,
int ExpectedBlockNr, Dictionary<string, TemporaryFile>
Files,
string Name)
157 string HttpSessionID;
161 string.IsNullOrEmpty(TabID = F.Value) ||
163 !
int.TryParse(F.Value, out
int BlockNr) ||
169 return ExpectedBlockNr;
175 RemoveFile(HttpSessionID,
Files);
178 Request.
Session[Name +
"FileName"] = F.Value;
182 return ExpectedBlockNr;
186 if (BlockNr != ExpectedBlockNr)
189 return ExpectedBlockNr;
196 if (!
Files.TryGetValue(HttpSessionID, out File))
199 Files[HttpSessionID] = File;
206 await File.FlushAsync();
208 ShowStatus(TabID, Name +
"Bytes",
Export.
FormatBytes(File.Length) +
" received of " + Name +
" file.");
210 Response.StatusCode = 200;
211 Response.StatusMessage =
"OK";
213 return ExpectedBlockNr;
216 internal static string[] GetTabIDs(
string TabID)
218 if (
string.IsNullOrEmpty(TabID))
221 return new string[] { TabID };
224 private static void CollectionFound(
string TabID,
string CollectionName)
229 private static void ShowStatus(
string TabID,
string Id,
string Message)
234 {
"message", Message },
238 private static void ShowStatus(
string TabID,
string Message)
250 string HttpSessionID;
254 string.IsNullOrEmpty(TabID = F.Value) ||
262 if (Content.
HasError || !(Content.
Decoded is Dictionary<string, object> Parameters))
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))
277 BackupFile = GetAndRemoveFile(HttpSessionID, this.backupFilePerSession);
278 KeyFile = GetAndRemoveFile(HttpSessionID, this.keyFilePerSession);
280 Task
_ = Task.Run(async () => await this.Restore(BackupFile, KeyFile, TabID, Request.
Session[
"backupFileName"]?.ToString(),
281 Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts));
283 Response.StatusCode = 200;
284 Response.StatusMessage =
"OK";
287 private static TemporaryFile GetAndRemoveFile(
string SessionID, Dictionary<string, TemporaryFile>
Files)
293 Files.Remove(SessionID);
306 Clear(this.backupFilePerSession);
307 Clear(this.keyFilePerSession);
310 private static void Clear(Dictionary<string, TemporaryFile>
Files)
312 if (!(
Files is
null))
324 private async Task Restore(FileStream BackupFile, FileStream KeyFile,
string TabID,
string BackupFileName,
bool Overwrite,
325 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
327 ICryptoTransform AesTransform1 =
null;
328 ICryptoTransform AesTransform2 =
null;
329 CryptoStream cs1 =
null;
330 CryptoStream cs2 =
null;
335 ShowStatus(TabID,
"Restoring backup.");
337 ShowStatus(TabID,
"Validating backup.");
339 if (BackupFile is
null ||
string.IsNullOrEmpty(BackupFileName))
340 throw new Exception(
"No backup file selected.");
342 string Extension = Path.GetExtension(BackupFileName);
343 ValidateBackupFile Import =
new ValidateBackupFile(BackupFileName,
null);
345 (AesTransform1, cs1) = await DoImport(BackupFile, KeyFile, TabID, Extension, Import,
false,
346 false, Array.Empty<
string>(), Array.Empty<
string>());
350 ShowStatus(TabID,
"Restoring backup.");
351 Import =
new RestoreBackupFile(BackupFileName, Import.ObjectIdMap);
353 (AesTransform2, cs2) = await DoImport(BackupFile, KeyFile, TabID, Extension, Import,
true,
354 OnlySelectedCollections, SelectedCollections, SelectedParts);
356 this.reloadConfiguration =
true;
357 await DoAnalyze(TabID);
362 StringBuilder Result =
new StringBuilder();
365 Result.AppendLine(
"Restoration complete.");
367 Result.AppendLine(
"Verification complete.");
369 if (Import.NrCollections > 0 || Import.NrObjects > 0 || Import.NrProperties > 0 || Import.NrFiles > 0)
372 Result.AppendLine(
"Contents of file:");
375 if (Import.NrCollections > 0)
377 Result.Append(Import.NrCollections.ToString());
378 if (Import.NrCollections > 1)
379 Result.AppendLine(
" collections.");
381 Result.AppendLine(
" collection.");
384 if (Import.NrIndices > 0)
386 Result.Append(Import.NrIndices.ToString());
387 if (Import.NrIndices > 1)
388 Result.AppendLine(
" indices.");
390 Result.AppendLine(
" index.");
393 if (Import.NrBlocks > 0)
395 Result.Append(Import.NrBlocks.ToString());
396 if (Import.NrBlocks > 1)
397 Result.AppendLine(
" blocks.");
399 Result.AppendLine(
" block.");
402 if (Import.NrObjects > 0)
404 Result.Append(Import.NrObjects.ToString());
405 if (Import.NrObjects > 1)
406 Result.AppendLine(
" objects.");
408 Result.AppendLine(
" object.");
411 if (Import.NrEntries > 0)
413 Result.Append(Import.NrEntries.ToString());
414 if (Import.NrEntries > 1)
415 Result.AppendLine(
" entries.");
417 Result.AppendLine(
" entry.");
420 if (Import.NrProperties > 0)
422 Result.Append(Import.NrProperties.ToString());
423 if (Import.NrProperties > 1)
424 Result.AppendLine(
" properties.");
426 Result.AppendLine(
" property.");
429 if (Import.NrFiles > 0)
431 Result.Append(Import.NrFiles.ToString());
432 if (Import.NrFiles > 1)
433 Result.Append(
" files");
435 Result.Append(
" file");
439 Result.AppendLine(
").");
442 if (Import is RestoreBackupFile Restore && Restore.NrObjectsFailed > 0)
444 Result.Append(Restore.NrObjectsFailed.ToString());
445 if (Import.NrProperties > 1)
446 Result.Append(
" objects");
448 Result.Append(
" object");
450 Result.AppendLine(
" failed.");
457 Result.Append(
"Click on the Next button to continue.");
463 {
"message", Result.ToString() }
469 ShowStatus(TabID,
"Failure: " + ex.Message);
474 {
"message", ex.Message }
479 AesTransform1?.Dispose();
480 AesTransform2?.Dispose();
483 BackupFile?.Dispose();
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,
492 ICryptoTransform AesTransform =
null;
493 CryptoStream cs =
null;
495 BackupFile.Position = 0;
497 switch (Extension.ToLower())
500 await RestoreXml(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
504 await RestoreBinary(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
508 await RestoreCompressed(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
513 throw new Exception(
"No key file provided.");
515 KeyFile.Position = 0;
517 (AesTransform, cs) = await RestoreEncrypted(BackupFile, KeyFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
521 throw new Exception(
"Unrecognized file extension: " + Extension);
524 return (AesTransform, cs);
527 private static async Task RestoreXml(Stream BackupFile,
string TabID, ValidateBackupFile Import,
bool Overwrite,
528 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
530 XmlReaderSettings Settings =
new XmlReaderSettings()
534 ConformanceLevel = ConformanceLevel.Document,
535 CheckCharacters =
true,
536 DtdProcessing = DtdProcessing.Prohibit,
537 IgnoreComments =
true,
538 IgnoreProcessingInstructions =
true,
539 IgnoreWhitespace =
true
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;
555 throw new Exception(
"Invalid backup XML file.");
557 await Import.Start();
559 while (await r.ReadAsync())
561 if (r.IsStartElement())
567 throw new Exception(
"Database element not expected.");
569 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Database") >= 0 || SelectedParts.Length == 0;
572 ShowStatus(TabID,
"Skipping database section.");
574 ShowStatus(TabID,
"Restoring database section.");
576 ShowStatus(TabID,
"Validating database section.");
578 await Import.StartDatabase(
null);
579 DatabaseStarted =
true;
584 throw new Exception(
"Ledger element not expected.");
586 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Ledger") >= 0 || SelectedParts.Length == 0;
589 ShowStatus(TabID,
"Skipping ledger section.");
591 ShowStatus(TabID,
"Restoring ledger section.");
593 ShowStatus(TabID,
"Validating ledger section.");
595 await Import.StartLedger(
null);
596 LedgerStarted =
true;
600 if (r.Depth != 2 || (!DatabaseStarted && !LedgerStarted))
601 throw new Exception(
"Collection element not expected.");
603 if (!r.MoveToAttribute(
"name"))
604 throw new Exception(
"Collection name missing.");
606 string CollectionName = r.Value;
610 await Import.EndIndex();
611 IndexStarted =
false;
613 else if (BlockStarted)
615 await Import.EndBlock();
616 BlockStarted =
false;
619 if (CollectionStarted)
620 await Import.EndCollection();
622 if (OnlySelectedCollections)
624 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
625 SelectedCollections.Length == 0);
628 if (ImportCollection)
630 await Import.StartCollection(CollectionName);
631 CollectionStarted =
true;
633 CollectionFound(TabID, CollectionName);
636 CollectionStarted =
false;
640 if (r.Depth != 3 || !CollectionStarted)
641 throw new Exception(
"Index element not expected.");
643 if (ImportCollection)
645 await Import.StartIndex();
651 if (r.Depth != 4 || !IndexStarted)
652 throw new Exception(
"Field element not expected.");
654 if (r.MoveToFirstAttribute())
656 string FieldName =
null;
657 bool Ascending =
true;
669 throw new Exception(
"Invalid boolean value.");
676 throw new Exception(
"Unexpected attribute: " + r.LocalName);
679 while (r.MoveToNextAttribute());
681 if (
string.IsNullOrEmpty(FieldName))
682 throw new Exception(
"Invalid field name.");
684 if (ImportCollection)
685 await Import.ReportIndexField(FieldName, Ascending);
688 throw new Exception(
"Field attributes expected.");
693 if (r.Depth == 3 && CollectionStarted)
697 await Import.EndIndex();
698 IndexStarted =
false;
701 using (XmlReader r2 = r.ReadSubtree())
703 await r2.ReadAsync();
705 if (!r2.MoveToFirstAttribute())
706 throw new Exception(
"Object attributes missing.");
708 string ObjectId =
null;
709 string TypeName =
string.Empty;
713 switch (r2.LocalName)
727 throw new Exception(
"Unexpected attribute: " + r2.LocalName);
730 while (r2.MoveToNextAttribute());
732 if (ImportCollection)
733 await Import.StartObject(ObjectId, TypeName);
735 while (await r2.ReadAsync())
737 if (r2.IsStartElement())
739 P = await ReadValue(r2);
741 if (ImportCollection)
742 await Import.ReportProperty(P.Key, P.Value);
747 if (ImportCollection)
748 await Import.EndObject();
751 throw new Exception(
"Obj element not expected.");
756 if (r.Depth != 3 || !CollectionStarted)
757 throw new Exception(
"Block element not expected.");
759 if (!r.MoveToAttribute(
"id"))
760 throw new Exception(
"Block ID missing.");
762 string BlockID = r.Value;
764 if (ImportCollection)
766 await Import.StartBlock(BlockID);
772 if (r.Depth == 4 && BlockStarted)
774 using XmlReader r2 = r.ReadSubtree();
776 await r2.ReadAsync();
778 while (await r2.ReadAsync())
780 if (r2.IsStartElement())
782 P = await ReadValue(r2);
784 if (ImportCollection)
785 await Import.BlockMetaData(P.Key, P.Value);
790 throw new Exception(
"MetaData element not expected.");
797 if (r.Depth != 4 || !CollectionStarted || !BlockStarted)
798 throw new Exception(
"Entry element not expected.");
806 _ =>
throw new Exception(
"Unexpected element: " + r.LocalName),
809 using (XmlReader r2 = r.ReadSubtree())
811 await r2.ReadAsync();
813 if (!r2.MoveToFirstAttribute())
814 throw new Exception(
"Object attributes missing.");
816 string ObjectId =
null;
817 string TypeName =
string.Empty;
818 DateTimeOffset EntryTimestamp = DateTimeOffset.MinValue;
822 switch (r2.LocalName)
834 throw new Exception(
"Invalid Entry Timestamp: " + r2.Value);
841 throw new Exception(
"Unexpected attribute: " + r2.LocalName);
844 while (r2.MoveToNextAttribute());
846 if (ImportCollection)
847 await Import.StartEntry(ObjectId, TypeName,
EntryType, EntryTimestamp);
849 while (await r2.ReadAsync())
851 if (r2.IsStartElement())
853 P = await ReadValue(r2);
855 if (ImportCollection)
856 await Import.ReportProperty(P.Key, P.Value);
861 if (ImportCollection)
862 await Import.EndEntry();
868 throw new Exception(
"Files element not expected.");
870 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Files") >= 0 || SelectedParts.Length == 0;
874 await Import.EndIndex();
875 IndexStarted =
false;
877 else if (BlockStarted)
879 await Import.EndBlock();
880 BlockStarted =
false;
883 if (CollectionStarted)
885 await Import.EndCollection();
886 CollectionStarted =
false;
891 await Import.EndDatabase();
892 DatabaseStarted =
false;
894 else if (LedgerStarted)
896 await Import.EndLedger();
897 LedgerStarted =
false;
901 ShowStatus(TabID,
"Skipping files section.");
903 ShowStatus(TabID,
"Restoring files section.");
905 ShowStatus(TabID,
"Validating files section.");
907 await Import.StartFiles();
912 if (r.Depth != 2 || !FilesStarted)
913 throw new Exception(
"File element not expected.");
915 using (XmlReader r2 = r.ReadSubtree())
919 await r2.ReadAsync();
921 if (!r2.MoveToAttribute(
"fileName"))
922 throw new Exception(
"File name missing.");
924 string FileName = r.Value;
926 if (Path.IsPathRooted(FileName))
931 throw new Exception(
"Absolute path names not allowed: " + FileName);
938 while (await r2.ReadAsync())
940 if (r2.IsStartElement())
942 while (r2.LocalName ==
"Chunk")
944 string Base64 = await r2.ReadElementContentAsStringAsync();
945 byte[] Data = Convert.FromBase64String(Base64);
946 fs.Write(Data, 0, Data.Length);
953 if (!OnlySelectedCollections)
956 ImportGatewayConfig(fs);
958 await Import.ExportFile(FileName, fs);
967 throw new Exception(
"Unexpected element: " + r.LocalName);
971 ShowReport(TabID, Import, ref LastReport, Overwrite);
975 await Import.EndIndex();
976 else if (BlockStarted)
977 await Import.EndBlock();
979 if (CollectionStarted)
980 await Import.EndCollection();
983 await Import.EndDatabase();
984 else if (LedgerStarted)
985 await Import.EndLedger();
988 await Import.EndFiles();
991 ShowReport(TabID, Import, Overwrite);
994 private static void ShowReport(
string TabID, ValidateBackupFile Import, ref DateTime LastReport,
bool Overwrite)
996 DateTime Now = DateTime.Now;
997 if ((Now - LastReport).TotalSeconds >= 1)
1000 ShowReport(TabID, Import, Overwrite);
1004 private static void ShowReport(
string TabID, ValidateBackupFile Import,
bool Overwrite)
1006 string Suffix = Overwrite ?
"2" :
"1";
1008 if (Import.NrCollections > 0)
1009 ShowStatus(TabID,
"NrCollections" + Suffix, Import.NrCollections.ToString() +
" collections.");
1011 if (Import.NrIndices > 0)
1012 ShowStatus(TabID,
"NrIndices" + Suffix, Import.NrIndices.ToString() +
" indices.");
1014 if (Import.NrBlocks > 0)
1015 ShowStatus(TabID,
"NrBlocks" + Suffix, Import.NrBlocks.ToString() +
" blocks.");
1017 if (Import.NrObjects > 0)
1018 ShowStatus(TabID,
"NrObjects" + Suffix, Import.NrObjects.ToString() +
" objects.");
1020 if (Import.NrEntries > 0)
1021 ShowStatus(TabID,
"NrEntries" + Suffix, Import.NrEntries.ToString() +
" entries.");
1023 if (Import.NrProperties > 0)
1024 ShowStatus(TabID,
"NrProperties" + Suffix, Import.NrProperties.ToString() +
" properties.");
1026 if (Import.NrFiles > 0)
1027 ShowStatus(TabID,
"NrFiles" + Suffix, Import.NrFiles.ToString() +
" files (" +
Export.
FormatBytes(Import.NrFileBytes) +
").");
1029 if (Import is RestoreBackupFile Restore && Restore.NrObjectsFailed > 0)
1030 ShowStatus(TabID,
"NrFailed" + Suffix, Restore.NrObjectsFailed.ToString() +
" objects failed.");
1033 private static async Task<KeyValuePair<string, object>> ReadValue(XmlReader r)
1040 r = r.ReadSubtree();
1041 await r.ReadAsync();
1044 if (!r.MoveToFirstAttribute())
1049 throw new Exception(
"Property attributes missing.");
1052 string ElementType =
null;
1053 string PropertyName =
null;
1054 object Value =
null;
1058 switch (r.LocalName)
1061 PropertyName = r.Value;
1065 switch (PropertyType)
1073 Value =
System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(r.Value));
1088 throw new Exception(
"Invalid boolean value.");
1093 if (
byte.TryParse(r.Value, out
byte b))
1100 throw new Exception(
"Invalid byte value.");
1113 throw new Exception(
"Invalid character value.");
1133 throw new Exception(
"Invalid DateTime value.");
1138 if (
XML.
TryParse(r.Value, out DateTimeOffset DTO))
1145 throw new Exception(
"Invalid DateTimeOffset value.");
1157 throw new Exception(
"Invalid Decimal value.");
1169 throw new Exception(
"Invalid Double value.");
1174 if (
short.TryParse(r.Value, out
short i2))
1181 throw new Exception(
"Invalid Int16 value.");
1186 if (
int.TryParse(r.Value, out
int i4))
1193 throw new Exception(
"Invalid Int32 value.");
1198 if (
long.TryParse(r.Value, out
long i8))
1205 throw new Exception(
"Invalid Int64 value.");
1210 if (sbyte.TryParse(r.Value, out sbyte i1))
1217 throw new Exception(
"Invalid SByte value.");
1229 throw new Exception(
"Invalid Single value.");
1234 if (ushort.TryParse(r.Value, out ushort u2))
1241 throw new Exception(
"Invalid UInt16 value.");
1246 if (uint.TryParse(r.Value, out uint u4))
1253 throw new Exception(
"Invalid UInt32 value.");
1258 if (ulong.TryParse(r.Value, out ulong u8))
1265 throw new Exception(
"Invalid UInt64 value.");
1270 if (TimeSpan.TryParse(r.Value, out TimeSpan TS))
1277 throw new Exception(
"Invalid TimeSpan value.");
1285 throw new Exception(
"Binary member values are reported using child elements.");
1288 if (Guid.TryParse(r.Value, out Guid Id))
1295 throw new Exception(
"Invalid GUID value.");
1303 throw new Exception(
"Arrays report values as child elements.");
1309 throw new Exception(
"Objects report member values as child elements.");
1315 throw new Exception(
"Unexpected property type: " + PropertyType);
1321 ElementType = r.Value;
1331 throw new Exception(
"Unexpected attribute: " + r.LocalName);
1334 while (r.MoveToNextAttribute());
1336 if (!(ElementType is
null))
1338 switch (PropertyType)
1341 List<object> List =
new List<object>();
1343 while (await r.ReadAsync())
1345 if (r.IsStartElement())
1347 KeyValuePair<string, object> P = await ReadValue(r);
1348 if (!
string.IsNullOrEmpty(P.Key))
1353 throw new Exception(
"Arrays do not contain property names.");
1358 else if (r.NodeType == XmlNodeType.EndElement)
1362 Value = List.ToArray();
1369 while (await r.ReadAsync())
1371 if (r.IsStartElement())
1373 KeyValuePair<string, object> P = await ReadValue(r);
1374 GenObj[P.Key] = P.Value;
1376 else if (r.NodeType == XmlNodeType.EndElement)
1385 throw new Exception(
"Type only valid option for arrays and objects.");
1388 else if (PropertyType ==
"Bin")
1390 MemoryStream Bin =
new MemoryStream();
1392 while (await r.ReadAsync())
1394 if (r.IsStartElement())
1398 while (r.LocalName ==
"Chunk")
1400 string Base64 = await r.ReadElementContentAsStringAsync();
1401 byte[] Data = Convert.FromBase64String(Base64);
1402 Bin.Write(Data, 0, Data.Length);
1405 catch (Exception ex)
1410 System.
Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
1413 else if (r.NodeType == XmlNodeType.EndElement)
1417 Value = Bin.ToArray();
1423 return new KeyValuePair<string, object>(PropertyName, Value);
1426 private static async Task RestoreBinary(Stream BackupFile,
string TabID, ValidateBackupFile Import,
bool Overwrite,
1427 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1429 int Version = BackupFile.ReadByte();
1431 throw new Exception(
"File version not supported.");
1433 DateTime LastReport = DateTime.Now;
1435 bool ImportCollection = !OnlySelectedCollections;
1438 using BinaryReader r =
new BinaryReader(BackupFile,
System.Text.Encoding.UTF8,
true);
1439 string s = r.ReadString();
1441 throw new Exception(
"Invalid backup file.");
1443 await Import.Start();
1445 while ((Command = r.ReadByte()) != 0)
1450 throw new Exception(
"Obsolete file.");
1453 string CollectionName;
1459 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Database") >= 0 || SelectedParts.Length == 0;
1462 ShowStatus(TabID,
"Skipping database section.");
1464 ShowStatus(TabID,
"Restoring database section.");
1466 ShowStatus(TabID,
"Validating database section.");
1468 await Import.StartDatabase(
null);
1470 while (!
string.IsNullOrEmpty(CollectionName = r.ReadString()))
1472 if (OnlySelectedCollections)
1474 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
1475 SelectedCollections.Length == 0);
1478 if (ImportCollection)
1480 await Import.StartCollection(CollectionName);
1481 CollectionFound(TabID, CollectionName);
1486 while ((b = r.ReadByte()) != 0)
1491 if (ImportCollection)
1492 await Import.StartIndex();
1494 while (!
string.IsNullOrEmpty(FieldName = r.ReadString()))
1496 Ascending = r.ReadBoolean();
1498 if (ImportCollection)
1499 await Import.ReportIndexField(FieldName, Ascending);
1502 if (ImportCollection)
1503 await Import.EndIndex();
1507 ObjectId = r.ReadString();
1508 TypeName = r.ReadString();
1510 if (ImportCollection)
1511 await Import.StartObject(ObjectId, TypeName);
1514 string PropertyName = r.ReadString();
1515 object PropertyValue;
1517 while (!
string.IsNullOrEmpty(PropertyName))
1519 PropertyValue = ReadValue(r, PropertyType);
1521 if (ImportCollection)
1522 await Import.ReportProperty(PropertyName, PropertyValue);
1525 PropertyName = r.ReadString();
1528 if (ImportCollection)
1529 await Import.EndObject();
1533 throw new Exception(
"Unsupported collection section: " + b.ToString());
1536 ShowReport(TabID, Import, ref LastReport, Overwrite);
1539 if (ImportCollection)
1540 await Import.EndCollection();
1543 await Import.EndDatabase();
1548 int MaxLen = 256 * 1024;
1549 byte[] Buffer =
new byte[MaxLen];
1551 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Files") >= 0 || SelectedParts.Length == 0;
1554 ShowStatus(TabID,
"Skipping files section.");
1556 ShowStatus(TabID,
"Restoring files section.");
1558 ShowStatus(TabID,
"Validating files section.");
1560 await Import.StartFiles();
1562 bool FirstFile =
true;
1564 while (!
string.IsNullOrEmpty(FileName = r.ReadString()))
1566 long Length = r.ReadInt64();
1568 if (Path.IsPathRooted(FileName))
1573 throw new Exception(
"Absolute path names not allowed: " + FileName);
1582 int Nr = r.Read(Buffer, 0, (
int)Math.Min(Length, MaxLen));
1584 await File.WriteAsync(Buffer, 0, Nr);
1593 ImportGatewayConfig(File);
1595 await Import.ExportFile(FileName, File);
1597 ShowReport(TabID, Import, ref LastReport, Overwrite);
1599 catch (Exception ex)
1601 ShowStatus(TabID,
"Unable to restore " + FileName +
": " + ex.Message);
1608 await Import.EndFiles();
1612 throw new Exception(
"Export file contains reported errors.");
1615 throw new Exception(
"Export file contains reported exceptions.");
1619 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Ledger") >= 0 || SelectedParts.Length == 0;
1622 ShowStatus(TabID,
"Skipping ledger section.");
1624 ShowStatus(TabID,
"Restoring ledger section.");
1626 ShowStatus(TabID,
"Validating ledger section.");
1628 await Import.StartLedger(
null);
1630 while (!
string.IsNullOrEmpty(CollectionName = r.ReadString()))
1632 if (OnlySelectedCollections)
1634 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
1635 SelectedCollections.Length == 0);
1638 if (ImportCollection)
1640 await Import.StartCollection(CollectionName);
1641 CollectionFound(TabID, CollectionName);
1646 while ((b = r.ReadByte()) != 0)
1651 string BlockID = r.ReadString();
1652 if (ImportCollection)
1653 await Import.StartBlock(BlockID);
1657 ObjectId = r.ReadString();
1658 TypeName = r.ReadString();
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;
1665 TimeSpan TS =
new TimeSpan(Ticks);
1666 DateTimeOffset EntryTimestamp =
new DateTimeOffset(DT, TS);
1668 if (ImportCollection)
1669 await Import.StartEntry(ObjectId, TypeName,
EntryType, EntryTimestamp);
1672 string PropertyName = r.ReadString();
1673 object PropertyValue;
1675 while (!
string.IsNullOrEmpty(PropertyName))
1677 PropertyValue = ReadValue(r, PropertyType);
1679 if (ImportCollection)
1680 await Import.ReportProperty(PropertyName, PropertyValue);
1683 PropertyName = r.ReadString();
1686 if (ImportCollection)
1687 await Import.EndEntry();
1691 if (ImportCollection)
1692 await Import.EndBlock();
1696 PropertyName = r.ReadString();
1698 PropertyValue = ReadValue(r, PropertyType);
1700 await Import.BlockMetaData(PropertyName, PropertyValue);
1704 throw new Exception(
"Unsupported collection section: " + b.ToString());
1707 ShowReport(TabID, Import, ref LastReport, Overwrite);
1710 if (ImportCollection)
1711 await Import.EndCollection();
1714 await Import.EndLedger();
1718 throw new Exception(
"Unsupported section: " +
Command.ToString());
1723 ShowReport(TabID, Import, Overwrite);
1726 private static void ImportGatewayConfig(Stream File)
1728 XmlDocument Doc =
new XmlDocument()
1730 PreserveWhitespace =
true
1737 if (!(Doc.DocumentElement is
null) && Doc.DocumentElement.LocalName ==
"GatewayConfiguration")
1739 List<KeyValuePair<string, string>> DefaultPages =
null;
1741 foreach (XmlNode N
in Doc.DocumentElement.ChildNodes)
1743 if (N is XmlElement E)
1745 switch (E.LocalName)
1747 case "ApplicationName":
1748 string s = E.InnerText;
1749 Gateway.ApplicationName = s;
1750 Original.DocumentElement[
"ApplicationName"].InnerText = s;
1757 DefaultPages ??=
new List<KeyValuePair<string, string>>();
1758 DefaultPages.Add(
new KeyValuePair<string, string>(Host, s));
1767 if (!(DefaultPages is
null))
1769 Gateway.SetDefaultPages(DefaultPages.ToArray());
1771 foreach (XmlNode N
in Original.DocumentElement.ChildNodes)
1773 if (N is XmlElement E && E.LocalName ==
"DefaultPage")
1777 E.InnerText = DefaultPage;
1783 Original.Save(OriginalFileName);
1786 private static object ReadValue(BinaryReader r,
byte PropertyType)
1788 switch (PropertyType)
1808 DateTimeKind Kind = (DateTimeKind)((
int)r.ReadByte());
1809 long Ticks = r.ReadInt64();
1810 return new DateTime(Ticks, Kind);
1813 Ticks = r.ReadInt64();
1814 return new TimeSpan(Ticks);
1817 int Count = r.ReadInt32();
1818 return r.ReadBytes(Count);
1821 byte[] Bin = r.ReadBytes(16);
1822 return new Guid(Bin);
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;
1830 TimeSpan TS =
new TimeSpan(Ticks);
1831 return new DateTimeOffset(DT, TS);
1838 long NrElements = r.ReadInt64();
1840 List<object> List =
new List<object>();
1842 while (NrElements > 0)
1846 List.Add(ReadValue(r, PropertyType));
1849 return List.ToArray();
1852 string TypeName = r.ReadString();
1856 string PropertyName = r.ReadString();
1858 while (!
string.IsNullOrEmpty(PropertyName))
1860 Object[PropertyName] = ReadValue(r, PropertyType);
1863 PropertyName = r.ReadString();
1869 throw new Exception(
"Unsupported property type: " +
PropertyType.ToString());
1873 private static async Task RestoreCompressed(Stream BackupFile,
string TabID, ValidateBackupFile Import,
bool Overwrite,
1874 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1876 using GZipStream gz =
new GZipStream(BackupFile, CompressionMode.Decompress,
true);
1878 await RestoreBinary(gz, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
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)
1884 XmlDocument Doc =
new XmlDocument()
1886 PreserveWhitespace =
true
1895 throw new Exception(
"Invalid key file.");
1898 XmlElement KeyAes256 = Doc.DocumentElement;
1899 if (KeyAes256.LocalName !=
"KeyAes256" ||
1901 !KeyAes256.HasAttribute(
"key") ||
1902 !KeyAes256.HasAttribute(
"iv"))
1904 throw new Exception(
"Invalid key file.");
1907 byte[] Key = Convert.FromBase64String(KeyAes256.Attributes[
"key"].Value);
1908 byte[] IV = Convert.FromBase64String(KeyAes256.Attributes[
"iv"].Value);
1910 ICryptoTransform AesTransform = WebResources.StartExport.aes.CreateDecryptor(Key, IV);
1911 CryptoStream cs =
new CryptoStream(BackupFile, AesTransform, CryptoStreamMode.Read);
1913 await RestoreCompressed(cs, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
1915 return (AesTransform, cs);
1918 private static async Task DoAnalyze(
string TabID)
1920 ShowStatus(TabID,
"Analyzing database.");
1925 using (FileStream fs = File.Create(XmlPath))
1928 using XmlWriter w = XmlWriter.Create(fs, Settings);
1934 XslCompiledTransform Xslt =
XSL.
LoadTransform(typeof(
Gateway).Namespace +
".Transforms.DbStatXmlToHtml.xslt");
1942 ShowStatus(TabID,
"Database analysis successfully completed.");
1965 return this.MakeCompleted(this.reloadConfiguration);
1974 return Task.FromResult(
true);
1980 public const string GATEWAY_RESTORE = nameof(GATEWAY_RESTORE);
1985 public const string GATEWAY_RESTORE_BAKFILE = nameof(GATEWAY_RESTORE_BAKFILE);
1990 public const string GATEWAY_RESTORE_KEYFILE = nameof(GATEWAY_RESTORE_KEYFILE);
1995 public const string GATEWAY_RESTORE_OVERWRITE = nameof(GATEWAY_RESTORE_OVERWRITE);
2000 public const string GATEWAY_RESTORE_COLLECTIONS = nameof(GATEWAY_RESTORE_COLLECTIONS);
2005 public const string GATEWAY_RESTORE_PARTS = nameof(GATEWAY_RESTORE_PARTS);
2013 if (!this.TryGetEnvironmentVariable(GATEWAY_RESTORE,
false, out
bool Restore))
2019 if (!this.TryGetEnvironmentVariable(GATEWAY_RESTORE_BAKFILE,
true, out
string BakFileName) ||
2020 !this.TryGetEnvironmentVariable(GATEWAY_RESTORE_OVERWRITE,
true, out
bool OverWrite))
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;
2031 if (
string.IsNullOrEmpty(CollectionsStr))
2032 Collections = Array.Empty<
string>();
2034 Collections = CollectionsStr.Split(
',');
2036 if (
string.IsNullOrEmpty(PartsStr))
2037 Parts = Array.Empty<
string>();
2039 Parts = PartsStr.Split(
',');
2041 FileStream BakFile =
null;
2042 FileStream KeyFile =
null;
2048 BakFile = File.OpenRead(BakFileName);
2050 catch (Exception ex)
2052 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_BAKFILE, BakFileName);
2056 if (!
string.IsNullOrEmpty(KeyFileName))
2060 KeyFile = File.OpenRead(KeyFileName);
2062 catch (Exception ex)
2064 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_KEYFILE, KeyFileName);
2069 await this.Restore(BakFile, KeyFile,
string.Empty, BakFileName, OverWrite, Collections.Length > 0, Collections, Parts);
2071 catch (Exception ex)
2073 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_BAKFILE, BakFileName);
Helps with parsing of commong data types.
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
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.
static string Encode(string s)
Encodes a string for inclusion in JSON.
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
static XmlDocument LoadFromFile(string FileName)
Loads an XML document from a file.
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Static class managing loading of XSL resources stored as embedded resources or in content files.
static XslCompiledTransform LoadTransform(string ResourceName)
Loads an XSL transformation from an embedded resource.
static string Transform(string XML, XslCompiledTransform Transform)
Transforms an XML document using an XSL transform.
Static class managing the application event log. Applications and services log events on this static ...
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.
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
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.
static string FormatBytes(double Bytes)
Formats a file size using appropriate unit.
Static class managing the runtime environment of the IoT Gateway.
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
static string ConfigFilePath
Full path to Gateway.config file.
static bool TryGetDefaultPage(HttpRequest Request, out string DefaultPage)
Tries to get the default page of a host.
static string AppDataFolder
Application data folder.
const string GatewayConfigLocalFileName
Gateway.config
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.
void Dispose()
IDisposable.Dispose
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.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP fields.
Represents an HTTP request.
Stream DataStream
Data stream, if data is available, or null if data is not available.
HttpRequestHeader Header
Request header.
bool HasData
If the request has data.
SessionVariables Session
Contains session states, if the resource requires sessions, or null otherwise.
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Base class for all HTTP resources.
static string GetSessionId(HttpRequest Request, HttpResponse Response)
Gets the session ID used for a request.
Represets a response of an HTTP client request.
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.
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Represents a case-insensitive string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
static Task< string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData)
Analyzes the database and exports findings to XML.
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.
static void ClearAll()
Clears all active caches.
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Static class managing binary representations of strings.
static Encoding Utf8WithBom
UTF-8 encoding with Byte Order Mark (BOM)
Contains information about a language.
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 ...
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...
PropertyType
Type of indexed property.
EntryType
Ledger entry type.