2using System.Collections.Generic;
4using System.IO.Compression;
5using System.Security.Cryptography;
7using System.Threading.Tasks;
37 private readonly Dictionary<string, TemporaryFile> backupFilePerSession =
new Dictionary<string, TemporaryFile>();
38 private readonly Dictionary<string, TemporaryFile> keyFilePerSession =
new Dictionary<string, TemporaryFile>();
39 private int expectedBlockBackup = 0;
40 private int expectedBlockKey = 0;
41 private bool reloadConfiguration =
false;
51 public override string Resource =>
"/Settings/Restore.md";
73 return Task.CompletedTask;
91 this.uploadBackup = WebServer.
Register(
"/Settings/UploadBackup",
null, this.UploadBackup,
true,
false,
true);
92 this.uploadKey = WebServer.
Register(
"/Settings/UploadKey",
null, this.UploadKey,
true,
false,
true);
93 this.restore = WebServer.
Register(
"/Settings/Restore",
null, this.Restore,
true,
false,
true);
95 WebServer.SessionRemoved += this.WebServer_SessionRemoved;
97 return base.InitSetup(WebServer);
110 WebServer.SessionRemoved -= this.WebServer_SessionRemoved;
112 return base.UnregisterSetup(WebServer);
117 RemoveFile(e.
Key,
this.backupFilePerSession);
118 RemoveFile(e.
Key,
this.keyFilePerSession);
120 return Task.CompletedTask;
123 private static void RemoveFile(
string Key, Dictionary<string, TemporaryFile> Files)
137 this.Upload(Request, Response, ref this.expectedBlockBackup, this.backupFilePerSession,
"backup");
138 return Task.CompletedTask;
143 this.Upload(Request, Response, ref this.expectedBlockKey, this.keyFilePerSession,
"key");
144 return Task.CompletedTask;
152 private void Upload(
HttpRequest Request,
HttpResponse Response, ref
int ExpectedBlockNr, Dictionary<string, TemporaryFile> Files,
string Name)
158 string HttpSessionID;
162 string.IsNullOrEmpty(TabID = F.Value) ||
164 !
int.TryParse(F.Value, out
int BlockNr) ||
175 RemoveFile(HttpSessionID, Files);
178 Request.
Session[Name +
"FileName"] = F.Value;
183 if (BlockNr != ExpectedBlockNr)
190 if (!Files.TryGetValue(HttpSessionID, out File))
193 Files[HttpSessionID] = File;
202 ShowStatus(TabID, Name +
"Bytes",
Export.
FormatBytes(File.Length) +
" received of " + Name +
" file.");
204 Response.StatusCode = 200;
207 internal static string[] GetTabIDs(
string TabID)
209 if (
string.IsNullOrEmpty(TabID))
212 return new string[] { TabID };
215 private static void CollectionFound(
string TabID,
string CollectionName)
220 private static void ShowStatus(
string TabID,
string Id,
string Message)
225 {
"message", Message },
229 private static void ShowStatus(
string TabID,
string Message)
241 string HttpSessionID;
245 string.IsNullOrEmpty(TabID = F.Value) ||
252 if (!(Obj is Dictionary<string, object> Parameters))
255 if (!Parameters.TryGetValue(
"overwrite", out Obj) || !(Obj is
bool Overwrite) ||
256 !Parameters.TryGetValue(
"onlySelectedCollections", out Obj) || !(Obj is
bool OnlySelectedCollections) ||
257 !Parameters.TryGetValue(
"selectedCollections", out Obj) || !(Obj is Array SelectedCollections) ||
258 !Parameters.TryGetValue(
"selectedParts", out Obj) || !(Obj is Array SelectedParts))
263 BackupFile = GetAndRemoveFile(HttpSessionID, this.backupFilePerSession);
264 KeyFile = GetAndRemoveFile(HttpSessionID, this.keyFilePerSession);
266 Task _ = Task.Run(async () => await this.Restore(BackupFile, KeyFile, TabID, Request.
Session[
"backupFileName"]?.ToString(),
267 Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts));
269 Response.StatusCode = 200;
272 private static TemporaryFile GetAndRemoveFile(
string SessionID, Dictionary<string, TemporaryFile> Files)
278 Files.Remove(SessionID);
291 Clear(this.backupFilePerSession);
292 Clear(this.keyFilePerSession);
295 private static void Clear(Dictionary<string, TemporaryFile> Files)
297 if (!(Files is
null))
309 private async Task Restore(FileStream BackupFile, FileStream KeyFile,
string TabID,
string BackupFileName,
bool Overwrite,
310 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
312 ICryptoTransform AesTransform1 =
null;
313 ICryptoTransform AesTransform2 =
null;
314 CryptoStream cs1 =
null;
315 CryptoStream cs2 =
null;
320 ShowStatus(TabID,
"Restoring backup.");
322 ShowStatus(TabID,
"Validating backup.");
324 if (BackupFile is
null ||
string.IsNullOrEmpty(BackupFileName))
325 throw new Exception(
"No backup file selected.");
327 string Extension = Path.GetExtension(BackupFileName);
328 ValidateBackupFile Import =
new ValidateBackupFile(BackupFileName,
null);
330 (AesTransform1, cs1) = await DoImport(BackupFile, KeyFile, TabID, Extension, Import,
false,
331 false,
new string[0],
new string[0]);
335 ShowStatus(TabID,
"Restoring backup.");
336 Import =
new RestoreBackupFile(BackupFileName, Import.ObjectIdMap);
338 (AesTransform2, cs2) = await DoImport(BackupFile, KeyFile, TabID, Extension, Import,
true,
339 OnlySelectedCollections, SelectedCollections, SelectedParts);
341 this.reloadConfiguration =
true;
342 await DoAnalyze(TabID);
347 StringBuilder Result =
new StringBuilder();
350 Result.AppendLine(
"Restoration complete.");
352 Result.AppendLine(
"Verification complete.");
354 if (Import.NrCollections > 0 || Import.NrObjects > 0 || Import.NrProperties > 0 || Import.NrFiles > 0)
357 Result.AppendLine(
"Contents of file:");
360 if (Import.NrCollections > 0)
362 Result.Append(Import.NrCollections.ToString());
363 if (Import.NrCollections > 1)
364 Result.AppendLine(
" collections.");
366 Result.AppendLine(
" collection.");
369 if (Import.NrIndices > 0)
371 Result.Append(Import.NrIndices.ToString());
372 if (Import.NrIndices > 1)
373 Result.AppendLine(
" indices.");
375 Result.AppendLine(
" index.");
378 if (Import.NrBlocks > 0)
380 Result.Append(Import.NrBlocks.ToString());
381 if (Import.NrBlocks > 1)
382 Result.AppendLine(
" blocks.");
384 Result.AppendLine(
" block.");
387 if (Import.NrObjects > 0)
389 Result.Append(Import.NrObjects.ToString());
390 if (Import.NrObjects > 1)
391 Result.AppendLine(
" objects.");
393 Result.AppendLine(
" object.");
396 if (Import.NrEntries > 0)
398 Result.Append(Import.NrEntries.ToString());
399 if (Import.NrEntries > 1)
400 Result.AppendLine(
" entries.");
402 Result.AppendLine(
" entry.");
405 if (Import.NrProperties > 0)
407 Result.Append(Import.NrProperties.ToString());
408 if (Import.NrProperties > 1)
409 Result.AppendLine(
" properties.");
411 Result.AppendLine(
" property.");
414 if (Import.NrFiles > 0)
416 Result.Append(Import.NrFiles.ToString());
417 if (Import.NrFiles > 1)
418 Result.Append(
" files");
420 Result.Append(
" file");
424 Result.AppendLine(
").");
427 if (Import is RestoreBackupFile Restore && Restore.NrObjectsFailed > 0)
429 Result.Append(Restore.NrObjectsFailed.ToString());
430 if (Import.NrProperties > 1)
431 Result.Append(
" objects");
433 Result.Append(
" object");
435 Result.AppendLine(
" failed.");
442 Result.Append(
"Click on the Next button to continue.");
448 {
"message", Result.ToString() }
454 ShowStatus(TabID,
"Failure: " + ex.Message);
459 {
"message", ex.Message }
464 AesTransform1?.Dispose();
465 AesTransform2?.Dispose();
468 BackupFile?.Dispose();
473 private static async Task<(ICryptoTransform, CryptoStream)> DoImport(FileStream BackupFile, FileStream KeyFile,
string TabID,
474 string Extension, ValidateBackupFile Import,
bool Overwrite,
bool OnlySelectedCollections, Array SelectedCollections,
477 ICryptoTransform AesTransform =
null;
478 CryptoStream cs =
null;
480 BackupFile.Position = 0;
482 switch (Extension.ToLower())
485 await RestoreXml(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
489 await RestoreBinary(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
493 await RestoreCompressed(BackupFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
498 throw new Exception(
"No key file provided.");
500 KeyFile.Position = 0;
502 (AesTransform, cs) = await RestoreEncrypted(BackupFile, KeyFile, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
506 throw new Exception(
"Unrecognized file extension: " + Extension);
509 return (AesTransform, cs);
512 private static async Task RestoreXml(Stream BackupFile,
string TabID, ValidateBackupFile Import,
bool Overwrite,
513 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
515 XmlReaderSettings Settings =
new XmlReaderSettings()
519 ConformanceLevel = ConformanceLevel.Document,
520 CheckCharacters =
true,
521 DtdProcessing = DtdProcessing.Prohibit,
522 IgnoreComments =
true,
523 IgnoreProcessingInstructions =
true,
524 IgnoreWhitespace =
true
526 XmlReader r = XmlReader.Create(BackupFile, Settings);
527 DateTime LastReport = DateTime.Now;
528 KeyValuePair<string, object> P;
529 bool ImportCollection = !OnlySelectedCollections;
530 bool ImportPart = !OnlySelectedCollections;
531 bool DatabaseStarted =
false;
532 bool LedgerStarted =
false;
533 bool CollectionStarted =
false;
534 bool IndexStarted =
false;
535 bool BlockStarted =
false;
536 bool FilesStarted =
false;
537 bool FirstFile =
true;
540 throw new Exception(
"Invalid backup XML file.");
542 await Import.Start();
544 while (await r.ReadAsync())
546 if (r.IsStartElement())
552 throw new Exception(
"Database element not expected.");
554 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Database") >= 0 || SelectedParts.Length == 0;
557 ShowStatus(TabID,
"Skipping database section.");
559 ShowStatus(TabID,
"Restoring database section.");
561 ShowStatus(TabID,
"Validating database section.");
563 await Import.StartDatabase();
564 DatabaseStarted =
true;
569 throw new Exception(
"Ledger element not expected.");
571 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Ledger") >= 0 || SelectedParts.Length == 0;
574 ShowStatus(TabID,
"Skipping ledger section.");
576 ShowStatus(TabID,
"Restoring ledger section.");
578 ShowStatus(TabID,
"Validating ledger section.");
580 await Import.StartLedger();
581 LedgerStarted =
true;
585 if (r.Depth != 2 || (!DatabaseStarted && !LedgerStarted))
586 throw new Exception(
"Collection element not expected.");
588 if (!r.MoveToAttribute(
"name"))
589 throw new Exception(
"Collection name missing.");
591 string CollectionName = r.Value;
595 await Import.EndIndex();
596 IndexStarted =
false;
598 else if (BlockStarted)
600 await Import.EndBlock();
601 BlockStarted =
false;
604 if (CollectionStarted)
605 await Import.EndCollection();
607 if (OnlySelectedCollections)
609 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
610 SelectedCollections.Length == 0);
613 if (ImportCollection)
615 await Import.StartCollection(CollectionName);
616 CollectionStarted =
true;
618 CollectionFound(TabID, CollectionName);
621 CollectionStarted =
false;
625 if (r.Depth != 3 || !CollectionStarted)
626 throw new Exception(
"Index element not expected.");
628 if (ImportCollection)
630 await Import.StartIndex();
636 if (r.Depth != 4 || !IndexStarted)
637 throw new Exception(
"Field element not expected.");
639 if (r.MoveToFirstAttribute())
641 string FieldName =
null;
642 bool Ascending =
true;
654 throw new Exception(
"Invalid boolean value.");
661 throw new Exception(
"Unexpected attribute: " + r.LocalName);
664 while (r.MoveToNextAttribute());
666 if (
string.IsNullOrEmpty(FieldName))
667 throw new Exception(
"Invalid field name.");
669 if (ImportCollection)
670 await Import.ReportIndexField(FieldName, Ascending);
673 throw new Exception(
"Field attributes expected.");
678 if (r.Depth == 3 && CollectionStarted)
682 await Import.EndIndex();
683 IndexStarted =
false;
686 using (XmlReader r2 = r.ReadSubtree())
688 await r2.ReadAsync();
690 if (!r2.MoveToFirstAttribute())
691 throw new Exception(
"Object attributes missing.");
693 string ObjectId =
null;
694 string TypeName =
string.Empty;
698 switch (r2.LocalName)
712 throw new Exception(
"Unexpected attribute: " + r2.LocalName);
715 while (r2.MoveToNextAttribute());
717 if (ImportCollection)
718 await Import.StartObject(ObjectId, TypeName);
720 while (await r2.ReadAsync())
722 if (r2.IsStartElement())
724 P = await ReadValue(r2);
726 if (ImportCollection)
727 await Import.ReportProperty(P.Key, P.Value);
732 if (ImportCollection)
733 await Import.EndObject();
736 throw new Exception(
"Obj element not expected.");
741 if (r.Depth != 3 || !CollectionStarted)
742 throw new Exception(
"Block element not expected.");
744 if (!r.MoveToAttribute(
"id"))
745 throw new Exception(
"Block ID missing.");
747 string BlockID = r.Value;
749 if (ImportCollection)
751 await Import.StartBlock(BlockID);
757 if (r.Depth == 4 && BlockStarted)
759 using (XmlReader r2 = r.ReadSubtree())
761 await r2.ReadAsync();
763 while (await r2.ReadAsync())
765 if (r2.IsStartElement())
767 P = await ReadValue(r2);
769 if (ImportCollection)
770 await Import.BlockMetaData(P.Key, P.Value);
776 throw new Exception(
"MetaData element not expected.");
783 if (r.Depth != 4 || !CollectionStarted || !BlockStarted)
784 throw new Exception(
"Entry element not expected.");
807 throw new Exception(
"Unexpected element: " + r.LocalName);
810 using (XmlReader r2 = r.ReadSubtree())
812 await r2.ReadAsync();
814 if (!r2.MoveToFirstAttribute())
815 throw new Exception(
"Object attributes missing.");
817 string ObjectId =
null;
818 string TypeName =
string.Empty;
819 DateTimeOffset EntryTimestamp = DateTimeOffset.MinValue;
823 switch (r2.LocalName)
835 throw new Exception(
"Invalid Entry Timestamp: " + r2.Value);
842 throw new Exception(
"Unexpected attribute: " + r2.LocalName);
845 while (r2.MoveToNextAttribute());
847 if (ImportCollection)
848 await Import.StartEntry(ObjectId, TypeName,
EntryType, EntryTimestamp);
850 while (await r2.ReadAsync())
852 if (r2.IsStartElement())
854 P = await ReadValue(r2);
856 if (ImportCollection)
857 await Import.ReportProperty(P.Key, P.Value);
862 if (ImportCollection)
863 await Import.EndEntry();
869 throw new Exception(
"Files element not expected.");
871 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Files") >= 0 || SelectedParts.Length == 0;
875 await Import.EndIndex();
876 IndexStarted =
false;
878 else if (BlockStarted)
880 await Import.EndBlock();
881 BlockStarted =
false;
884 if (CollectionStarted)
886 await Import.EndCollection();
887 CollectionStarted =
false;
892 await Import.EndDatabase();
893 DatabaseStarted =
false;
895 else if (LedgerStarted)
897 await Import.EndLedger();
898 LedgerStarted =
false;
902 ShowStatus(TabID,
"Skipping files section.");
904 ShowStatus(TabID,
"Restoring files section.");
906 ShowStatus(TabID,
"Validating files section.");
908 await Import.StartFiles();
913 if (r.Depth != 2 || !FilesStarted)
914 throw new Exception(
"File element not expected.");
916 using (XmlReader r2 = r.ReadSubtree())
920 await r2.ReadAsync();
922 if (!r2.MoveToAttribute(
"fileName"))
923 throw new Exception(
"File name missing.");
925 string FileName = r.Value;
927 if (Path.IsPathRooted(FileName))
932 throw new Exception(
"Absolute path names not allowed: " + FileName);
939 while (await r2.ReadAsync())
941 if (r2.IsStartElement())
943 while (r2.LocalName ==
"Chunk")
945 string Base64 = await r2.ReadElementContentAsStringAsync();
946 byte[] Data = Convert.FromBase64String(Base64);
947 fs.Write(Data, 0, Data.Length);
954 if (!OnlySelectedCollections)
957 ImportGatewayConfig(fs);
959 await Import.ExportFile(FileName, fs);
969 throw new Exception(
"Unexpected element: " + r.LocalName);
973 ShowReport(TabID, Import, ref LastReport, Overwrite);
977 await Import.EndIndex();
978 else if (BlockStarted)
979 await Import.EndBlock();
981 if (CollectionStarted)
982 await Import.EndCollection();
985 await Import.EndDatabase();
986 else if (LedgerStarted)
987 await Import.EndLedger();
990 await Import.EndFiles();
993 ShowReport(TabID, Import, Overwrite);
996 private static void ShowReport(
string TabID, ValidateBackupFile Import, ref DateTime LastReport,
bool Overwrite)
998 DateTime Now = DateTime.Now;
999 if ((Now - LastReport).TotalSeconds >= 1)
1002 ShowReport(TabID, Import, Overwrite);
1006 private static void ShowReport(
string TabID, ValidateBackupFile Import,
bool Overwrite)
1008 string Suffix = Overwrite ?
"2" :
"1";
1010 if (Import.NrCollections > 0)
1011 ShowStatus(TabID,
"NrCollections" + Suffix, Import.NrCollections.ToString() +
" collections.");
1013 if (Import.NrIndices > 0)
1014 ShowStatus(TabID,
"NrIndices" + Suffix, Import.NrIndices.ToString() +
" indices.");
1016 if (Import.NrBlocks > 0)
1017 ShowStatus(TabID,
"NrBlocks" + Suffix, Import.NrBlocks.ToString() +
" blocks.");
1019 if (Import.NrObjects > 0)
1020 ShowStatus(TabID,
"NrObjects" + Suffix, Import.NrObjects.ToString() +
" objects.");
1022 if (Import.NrEntries > 0)
1023 ShowStatus(TabID,
"NrEntries" + Suffix, Import.NrEntries.ToString() +
" entries.");
1025 if (Import.NrProperties > 0)
1026 ShowStatus(TabID,
"NrProperties" + Suffix, Import.NrProperties.ToString() +
" properties.");
1028 if (Import.NrFiles > 0)
1029 ShowStatus(TabID,
"NrFiles" + Suffix, Import.NrFiles.ToString() +
" files (" +
Export.
FormatBytes(Import.NrFileBytes) +
").");
1031 if (Import is RestoreBackupFile Restore && Restore.NrObjectsFailed > 0)
1032 ShowStatus(TabID,
"NrFailed" + Suffix, Restore.NrObjectsFailed.ToString() +
" objects failed.");
1035 private static async Task<KeyValuePair<string, object>> ReadValue(XmlReader r)
1042 r = r.ReadSubtree();
1043 await r.ReadAsync();
1046 if (!r.MoveToFirstAttribute())
1051 throw new Exception(
"Property attributes missing.");
1054 string ElementType =
null;
1055 string PropertyName =
null;
1056 object Value =
null;
1060 switch (r.LocalName)
1063 PropertyName = r.Value;
1067 switch (PropertyType)
1075 Value = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(r.Value));
1090 throw new Exception(
"Invalid boolean value.");
1095 if (
byte.TryParse(r.Value, out
byte b))
1102 throw new Exception(
"Invalid byte value.");
1115 throw new Exception(
"Invalid character value.");
1124 Value = (
CaseInsensitiveString)System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(r.Value));
1135 throw new Exception(
"Invalid DateTime value.");
1140 if (
XML.
TryParse(r.Value, out DateTimeOffset DTO))
1147 throw new Exception(
"Invalid DateTimeOffset value.");
1159 throw new Exception(
"Invalid Decimal value.");
1171 throw new Exception(
"Invalid Double value.");
1176 if (
short.TryParse(r.Value, out
short i2))
1183 throw new Exception(
"Invalid Int16 value.");
1188 if (
int.TryParse(r.Value, out
int i4))
1195 throw new Exception(
"Invalid Int32 value.");
1200 if (
long.TryParse(r.Value, out
long i8))
1207 throw new Exception(
"Invalid Int64 value.");
1212 if (sbyte.TryParse(r.Value, out sbyte i1))
1219 throw new Exception(
"Invalid SByte value.");
1231 throw new Exception(
"Invalid Single value.");
1236 if (ushort.TryParse(r.Value, out ushort u2))
1243 throw new Exception(
"Invalid UInt16 value.");
1248 if (uint.TryParse(r.Value, out uint u4))
1255 throw new Exception(
"Invalid UInt32 value.");
1260 if (ulong.TryParse(r.Value, out ulong u8))
1267 throw new Exception(
"Invalid UInt64 value.");
1272 if (TimeSpan.TryParse(r.Value, out TimeSpan TS))
1279 throw new Exception(
"Invalid TimeSpan value.");
1287 throw new Exception(
"Binary member values are reported using child elements.");
1290 if (Guid.TryParse(r.Value, out Guid Id))
1297 throw new Exception(
"Invalid GUID value.");
1305 throw new Exception(
"Arrays report values as child elements.");
1311 throw new Exception(
"Objects report member values as child elements.");
1317 throw new Exception(
"Unexpected property type: " + PropertyType);
1323 ElementType = r.Value;
1333 throw new Exception(
"Unexpected attribute: " + r.LocalName);
1336 while (r.MoveToNextAttribute());
1338 if (!(ElementType is
null))
1340 switch (PropertyType)
1343 List<object> List =
new List<object>();
1345 while (await r.ReadAsync())
1347 if (r.IsStartElement())
1349 KeyValuePair<string, object> P = await ReadValue(r);
1350 if (!
string.IsNullOrEmpty(P.Key))
1355 throw new Exception(
"Arrays do not contain property names.");
1360 else if (r.NodeType == XmlNodeType.EndElement)
1364 Value = List.ToArray();
1371 while (await r.ReadAsync())
1373 if (r.IsStartElement())
1375 KeyValuePair<string, object> P = await ReadValue(r);
1376 GenObj[P.Key] = P.Value;
1378 else if (r.NodeType == XmlNodeType.EndElement)
1387 throw new Exception(
"Type only valid option for arrays and objects.");
1390 else if (PropertyType ==
"Bin")
1392 MemoryStream Bin =
new MemoryStream();
1394 while (await r.ReadAsync())
1396 if (r.IsStartElement())
1400 while (r.LocalName ==
"Chunk")
1402 string Base64 = await r.ReadElementContentAsStringAsync();
1403 byte[] Data = Convert.FromBase64String(Base64);
1404 Bin.Write(Data, 0, Data.Length);
1407 catch (Exception ex)
1412 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
1415 else if (r.NodeType == XmlNodeType.EndElement)
1419 Value = Bin.ToArray();
1425 return new KeyValuePair<string, object>(PropertyName, Value);
1428 private static async Task RestoreBinary(Stream BackupFile,
string TabID, ValidateBackupFile Import,
bool Overwrite,
1429 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1431 int Version = BackupFile.ReadByte();
1433 throw new Exception(
"File version not supported.");
1435 DateTime LastReport = DateTime.Now;
1437 bool ImportCollection = !OnlySelectedCollections;
1440 using (BinaryReader r =
new BinaryReader(BackupFile, System.Text.Encoding.UTF8,
true))
1442 string s = r.ReadString();
1444 throw new Exception(
"Invalid backup file.");
1446 await Import.Start();
1448 while ((Command = r.ReadByte()) != 0)
1453 throw new Exception(
"Obsolete file.");
1456 string CollectionName;
1462 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Database") >= 0 || SelectedParts.Length == 0;
1465 ShowStatus(TabID,
"Skipping database section.");
1467 ShowStatus(TabID,
"Restoring database section.");
1469 ShowStatus(TabID,
"Validating database section.");
1471 await Import.StartDatabase();
1473 while (!
string.IsNullOrEmpty(CollectionName = r.ReadString()))
1475 if (OnlySelectedCollections)
1477 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
1478 SelectedCollections.Length == 0);
1481 if (ImportCollection)
1483 await Import.StartCollection(CollectionName);
1484 CollectionFound(TabID, CollectionName);
1489 while ((b = r.ReadByte()) != 0)
1494 if (ImportCollection)
1495 await Import.StartIndex();
1497 while (!
string.IsNullOrEmpty(FieldName = r.ReadString()))
1499 Ascending = r.ReadBoolean();
1501 if (ImportCollection)
1502 await Import.ReportIndexField(FieldName, Ascending);
1505 if (ImportCollection)
1506 await Import.EndIndex();
1510 ObjectId = r.ReadString();
1511 TypeName = r.ReadString();
1513 if (ImportCollection)
1514 await Import.StartObject(ObjectId, TypeName);
1517 string PropertyName = r.ReadString();
1518 object PropertyValue;
1520 while (!
string.IsNullOrEmpty(PropertyName))
1522 PropertyValue = ReadValue(r, PropertyType);
1524 if (ImportCollection)
1525 await Import.ReportProperty(PropertyName, PropertyValue);
1528 PropertyName = r.ReadString();
1531 if (ImportCollection)
1532 await Import.EndObject();
1536 throw new Exception(
"Unsupported collection section: " + b.ToString());
1539 ShowReport(TabID, Import, ref LastReport, Overwrite);
1542 if (ImportCollection)
1543 await Import.EndCollection();
1546 await Import.EndDatabase();
1551 int MaxLen = 256 * 1024;
1552 byte[] Buffer =
new byte[MaxLen];
1554 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Files") >= 0 || SelectedParts.Length == 0;
1557 ShowStatus(TabID,
"Skipping files section.");
1559 ShowStatus(TabID,
"Restoring files section.");
1561 ShowStatus(TabID,
"Validating files section.");
1563 await Import.StartFiles();
1565 bool FirstFile =
true;
1567 while (!
string.IsNullOrEmpty(FileName = r.ReadString()))
1569 long Length = r.ReadInt64();
1571 if (Path.IsPathRooted(FileName))
1576 throw new Exception(
"Absolute path names not allowed: " + FileName);
1585 int Nr = r.Read(Buffer, 0, (
int)Math.Min(Length, MaxLen));
1587 await File.WriteAsync(Buffer, 0, Nr);
1596 ImportGatewayConfig(File);
1598 await Import.ExportFile(FileName, File);
1600 ShowReport(TabID, Import, ref LastReport, Overwrite);
1602 catch (Exception ex)
1604 ShowStatus(TabID,
"Unable to restore " + FileName +
": " + ex.Message);
1612 await Import.EndFiles();
1616 throw new Exception(
"Export file contains reported errors.");
1619 throw new Exception(
"Export file contains reported exceptions.");
1623 ImportPart = !OnlySelectedCollections || Array.IndexOf(SelectedParts,
"Ledger") >= 0 || SelectedParts.Length == 0;
1626 ShowStatus(TabID,
"Skipping ledger section.");
1628 ShowStatus(TabID,
"Restoring ledger section.");
1630 ShowStatus(TabID,
"Validating ledger section.");
1632 await Import.StartLedger();
1634 while (!
string.IsNullOrEmpty(CollectionName = r.ReadString()))
1636 if (OnlySelectedCollections)
1638 ImportCollection = ImportPart && (Array.IndexOf(SelectedCollections, CollectionName) >= 0 ||
1639 SelectedCollections.Length == 0);
1642 if (ImportCollection)
1644 await Import.StartCollection(CollectionName);
1645 CollectionFound(TabID, CollectionName);
1650 while ((b = r.ReadByte()) != 0)
1655 string BlockID = r.ReadString();
1656 if (ImportCollection)
1657 await Import.StartBlock(BlockID);
1661 ObjectId = r.ReadString();
1662 TypeName = r.ReadString();
1664 DateTimeKind Kind = (DateTimeKind)r.ReadByte();
1665 long Ticks = r.ReadInt64();
1666 DateTime DT =
new DateTime(Ticks, Kind);
1667 Ticks = r.ReadInt64();
1668 Ticks -= Ticks % 600000000;
1669 TimeSpan TS =
new TimeSpan(Ticks);
1670 DateTimeOffset EntryTimestamp =
new DateTimeOffset(DT, TS);
1672 if (ImportCollection)
1673 await Import.StartEntry(ObjectId, TypeName,
EntryType, EntryTimestamp);
1676 string PropertyName = r.ReadString();
1677 object PropertyValue;
1679 while (!
string.IsNullOrEmpty(PropertyName))
1681 PropertyValue = ReadValue(r, PropertyType);
1683 if (ImportCollection)
1684 await Import.ReportProperty(PropertyName, PropertyValue);
1687 PropertyName = r.ReadString();
1690 if (ImportCollection)
1691 await Import.EndEntry();
1695 if (ImportCollection)
1696 await Import.EndBlock();
1700 PropertyName = r.ReadString();
1702 PropertyValue = ReadValue(r, PropertyType);
1704 await Import.BlockMetaData(PropertyName, PropertyValue);
1708 throw new Exception(
"Unsupported collection section: " + b.ToString());
1711 ShowReport(TabID, Import, ref LastReport, Overwrite);
1714 if (ImportCollection)
1715 await Import.EndCollection();
1718 await Import.EndLedger();
1722 throw new Exception(
"Unsupported section: " +
Command.ToString());
1727 ShowReport(TabID, Import, Overwrite);
1731 private static void ImportGatewayConfig(Stream File)
1733 XmlDocument Doc =
new XmlDocument()
1735 PreserveWhitespace =
true
1740 XmlDocument Original =
new XmlDocument()
1742 PreserveWhitespace =
true
1744 Original.Load(OriginalFileName);
1746 if (!(Doc.DocumentElement is
null) && Doc.DocumentElement.LocalName ==
"GatewayConfiguration")
1748 List<KeyValuePair<string, string>> DefaultPages =
null;
1750 foreach (XmlNode N
in Doc.DocumentElement.ChildNodes)
1752 if (N is XmlElement E)
1754 switch (E.LocalName)
1756 case "ApplicationName":
1757 string s = E.InnerText;
1758 Gateway.ApplicationName = s;
1759 Original.DocumentElement[
"ApplicationName"].InnerText = s;
1766 if (DefaultPages is
null)
1767 DefaultPages =
new List<KeyValuePair<string, string>>();
1769 DefaultPages.Add(
new KeyValuePair<string, string>(Host, s));
1778 if (!(DefaultPages is
null))
1780 Gateway.SetDefaultPages(DefaultPages.ToArray());
1782 foreach (XmlNode N
in Original.DocumentElement.ChildNodes)
1784 if (N is XmlElement E && E.LocalName ==
"DefaultPage")
1788 E.InnerText = DefaultPage;
1794 Original.Save(OriginalFileName);
1797 private static object ReadValue(BinaryReader r,
byte PropertyType)
1799 switch (PropertyType)
1819 DateTimeKind Kind = (DateTimeKind)((
int)r.ReadByte());
1820 long Ticks = r.ReadInt64();
1821 return new DateTime(Ticks, Kind);
1824 Ticks = r.ReadInt64();
1825 return new TimeSpan(Ticks);
1828 int Count = r.ReadInt32();
1829 return r.ReadBytes(Count);
1832 byte[] Bin = r.ReadBytes(16);
1833 return new Guid(Bin);
1836 Kind = (DateTimeKind)((
int)r.ReadByte());
1837 Ticks = r.ReadInt64();
1838 DateTime DT =
new DateTime(Ticks, Kind);
1839 Ticks = r.ReadInt64();
1840 Ticks -= Ticks % 600000000;
1841 TimeSpan TS =
new TimeSpan(Ticks);
1842 return new DateTimeOffset(DT, TS);
1849 long NrElements = r.ReadInt64();
1851 List<object> List =
new List<object>();
1853 while (NrElements > 0)
1857 List.Add(ReadValue(r, PropertyType));
1860 return List.ToArray();
1863 string TypeName = r.ReadString();
1867 string PropertyName = r.ReadString();
1869 while (!
string.IsNullOrEmpty(PropertyName))
1871 Object[PropertyName] = ReadValue(r, PropertyType);
1874 PropertyName = r.ReadString();
1880 throw new Exception(
"Unsupported property type: " +
PropertyType.ToString());
1884 private static async Task RestoreCompressed(Stream BackupFile,
string TabID, ValidateBackupFile Import,
bool Overwrite,
1885 bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1887 using (GZipStream gz =
new GZipStream(BackupFile, CompressionMode.Decompress,
true))
1889 await RestoreBinary(gz, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
1893 private static async Task<(ICryptoTransform, CryptoStream)> RestoreEncrypted(Stream BackupFile, Stream KeyFile,
string TabID, ValidateBackupFile Import,
1894 bool Overwrite,
bool OnlySelectedCollections, Array SelectedCollections, Array SelectedParts)
1896 XmlDocument Doc =
new XmlDocument()
1898 PreserveWhitespace =
true
1907 throw new Exception(
"Invalid key file.");
1910 XmlElement KeyAes256 = Doc.DocumentElement;
1911 if (KeyAes256.LocalName !=
"KeyAes256" ||
1913 !KeyAes256.HasAttribute(
"key") ||
1914 !KeyAes256.HasAttribute(
"iv"))
1916 throw new Exception(
"Invalid key file.");
1919 byte[] Key = Convert.FromBase64String(KeyAes256.Attributes[
"key"].Value);
1920 byte[] IV = Convert.FromBase64String(KeyAes256.Attributes[
"iv"].Value);
1922 ICryptoTransform AesTransform = WebResources.StartExport.aes.CreateDecryptor(Key, IV);
1923 CryptoStream cs =
new CryptoStream(BackupFile, AesTransform, CryptoStreamMode.Read);
1925 await RestoreCompressed(cs, TabID, Import, Overwrite, OnlySelectedCollections, SelectedCollections, SelectedParts);
1927 return (AesTransform, cs);
1930 private static async Task DoAnalyze(
string TabID)
1932 ShowStatus(TabID,
"Analyzing database.");
1937 using (FileStream fs = File.Create(XmlPath))
1940 using (XmlWriter w = XmlWriter.Create(fs, Settings))
1948 XslCompiledTransform Xslt =
XSL.
LoadTransform(typeof(
Gateway).Namespace +
".Transforms.DbStatXmlToHtml.xslt");
1952 byte[] Bin = WebResources.StartAnalyze.utf8Bom.GetBytes(s);
1956 ShowStatus(TabID,
"Database analysis successfully completed.");
1979 return this.MakeCompleted(this.reloadConfiguration);
1988 return Task.FromResult(
true);
1994 public const string GATEWAY_RESTORE = nameof(GATEWAY_RESTORE);
1999 public const string GATEWAY_RESTORE_BAKFILE = nameof(GATEWAY_RESTORE_BAKFILE);
2004 public const string GATEWAY_RESTORE_KEYFILE = nameof(GATEWAY_RESTORE_KEYFILE);
2009 public const string GATEWAY_RESTORE_OVERWRITE = nameof(GATEWAY_RESTORE_OVERWRITE);
2014 public const string GATEWAY_RESTORE_COLLECTIONS = nameof(GATEWAY_RESTORE_COLLECTIONS);
2019 public const string GATEWAY_RESTORE_PARTS = nameof(GATEWAY_RESTORE_PARTS);
2027 if (!this.TryGetEnvironmentVariable(GATEWAY_RESTORE,
false, out
bool Restore))
2033 if (!this.TryGetEnvironmentVariable(GATEWAY_RESTORE_BAKFILE,
true, out
string BakFileName) ||
2034 !this.TryGetEnvironmentVariable(GATEWAY_RESTORE_OVERWRITE,
true, out
bool OverWrite))
2039 string KeyFileName = Environment.GetEnvironmentVariable(GATEWAY_RESTORE_KEYFILE);
2040 string CollectionsStr = Environment.GetEnvironmentVariable(GATEWAY_RESTORE_COLLECTIONS);
2041 string PartsStr = Environment.GetEnvironmentVariable(GATEWAY_RESTORE_PARTS);
2042 string[] Collections;
2045 if (
string.IsNullOrEmpty(CollectionsStr))
2046 Collections =
new string[0];
2048 Collections = CollectionsStr.Split(
',');
2050 if (
string.IsNullOrEmpty(PartsStr))
2051 Parts =
new string[0];
2053 Parts = PartsStr.Split(
',');
2055 FileStream BakFile =
null;
2056 FileStream KeyFile =
null;
2062 BakFile = File.OpenRead(BakFileName);
2064 catch (Exception ex)
2066 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_BAKFILE, BakFileName);
2070 if (!
string.IsNullOrEmpty(KeyFileName))
2074 KeyFile = File.OpenRead(KeyFileName);
2076 catch (Exception ex)
2078 this.LogEnvironmentError(ex.Message, GATEWAY_RESTORE_KEYFILE, KeyFileName);
2083 await this.Restore(BakFile, KeyFile,
string.Empty, BakFileName, OverWrite, Collections.Length > 0, Collections, Parts);
2085 catch (Exception ex)
2087 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.
Helps with common JSON-related tasks.
static string Encode(string s)
Encodes a string for inclusion in JSON.
Static class managing loading of resources stored as embedded resources or in content files.
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
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 string Resource
Resource to be redirected to, to perform the configuration.
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.
Variables Session
Contains session states, if the resource requires sessions, or null otherwise.
async Task< object > 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.
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.
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.