Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmlFileLedger.cs
1using System;
4using System.IO;
5using System.Text;
6using System.Threading;
7using System.Threading.Tasks;
8using System.Xml;
9using Waher.Content;
11using Waher.Events;
15
17{
22 {
26 public const string Namespace = "http://waher.se/Schema/Export.xsd";
27
28 private readonly XmlWriterSettings settings;
29 private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
30 private StreamWriter file;
31 private DateTime lastEvent = DateTime.MinValue;
32 private XmlWriter output;
33 private TextWriter textOutput;
34 private ILedgerExternalEvents externalEvents;
35 private readonly FileNameTimeSequence fileSequence;
36 private readonly string transform = null;
37 private readonly int deleteAfterDays;
38 private bool running;
39
40 #region Construction
41
56 public XmlFileLedger(string FileName)
57 : this(FileName, string.Empty, 7)
58 {
59 }
60
77 public XmlFileLedger(string FileName, int DeleteAfterDays)
78 : this(FileName, string.Empty, DeleteAfterDays)
79 {
80 }
81
97 public XmlFileLedger(string FileName, string Transform)
98 : this(FileName, Transform, 7)
99 {
100 }
101
119 public XmlFileLedger(string FileName, string Transform, int DeleteAfterDays)
120 {
121 this.file = null;
122 this.output = null;
123 this.fileSequence = new FileNameTimeSequence(FileName, true);
124 this.transform = Transform;
125 this.deleteAfterDays = DeleteAfterDays;
126
127 this.settings = new XmlWriterSettings()
128 {
129 CloseOutput = true,
130 ConformanceLevel = ConformanceLevel.Document,
131 Encoding = Encoding.UTF8,
132 Indent = true,
133 IndentChars = "\t",
134 NewLineChars = "\r\n",
135 NewLineHandling = NewLineHandling.Replace,
136 NewLineOnAttributes = false,
137 OmitXmlDeclaration = false,
138 WriteEndDocumentOnClose = true,
139 Async = true
140 };
141
142 string FolderName = Path.GetDirectoryName(FileName);
143
144 if (!string.IsNullOrEmpty(FolderName) && !Directory.Exists(FolderName))
145 {
146 Log.Informational("Creating folder.", FolderName);
147 Directory.CreateDirectory(FolderName);
148 }
149 }
150
155 public XmlFileLedger(TextWriter Output)
156 {
157 this.textOutput = Output;
158 this.file = null;
159 this.fileSequence = null;
160 this.transform = null;
161 this.deleteAfterDays = 0;
162
163 this.settings = new XmlWriterSettings()
164 {
165 CloseOutput = true,
166 ConformanceLevel = ConformanceLevel.Document,
167 Encoding = Encoding.UTF8,
168 Indent = true,
169 IndentChars = "\t",
170 NewLineChars = "\r\n",
171 NewLineHandling = NewLineHandling.Replace,
172 NewLineOnAttributes = false,
173 OmitXmlDeclaration = false,
174 WriteEndDocumentOnClose = true,
175 Async = true
176 };
177
178 this.output = XmlWriter.Create(this.textOutput, this.settings);
179 this.output.WriteStartDocument();
180 this.output.WriteStartElement("LedgerExport", Namespace);
181 this.output.Flush();
182 }
183
184 #endregion
185
186 #region Persistance
187
191 public string FileName => this.fileSequence.FileNamePattern;
192
196 public string Transform => this.transform;
197
201 public DateTime LastEvent => this.lastEvent;
202
206 private async Task BeforeWrite()
207 {
208 if (this.fileSequence is null)
209 return;
210
211 if (!this.fileSequence.TryGetNewFileName(out string s))
212 return;
213
214 try
215 {
216 if (!(this.output is null))
217 {
218 await this.output.WriteEndElementAsync();
219 await this.output.WriteEndDocumentAsync();
220 await this.output.FlushAsync();
221 }
222
223 this.file?.Dispose();
224 }
225 catch (Exception)
226 {
227 try
228 {
229 await this.DisposeOutput();
230 }
231 catch (Exception)
232 {
233 // Ignore
234 }
235 }
236
237 this.file = null;
238 this.output = null;
239
240 try
241 {
242 this.file = File.CreateText(s);
243 this.output = XmlWriter.Create(this.file, this.settings);
244 }
245 catch (Exception ex)
246 {
247 Log.Exception(ex);
248 this.output = null;
249 return;
250 }
251
252 await this.output.WriteStartDocumentAsync();
253
254 if (!string.IsNullOrEmpty(this.transform))
255 {
256 if (File.Exists(this.transform))
257 {
258 try
259 {
260 byte[] XsltBin = await Runtime.IO.Files.ReadAllBytesAsync(this.transform);
261
262 await this.output.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"data:text/xsl;base64," +
263 Convert.ToBase64String(XsltBin) + "\"");
264 }
265 catch (Exception)
266 {
267 await this.output.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"" + this.transform + "\"");
268 }
269 }
270 else
271 await this.output.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"" + this.transform + "\"");
272 }
273
274 await this.output.WriteStartElementAsync(string.Empty, "LedgerExport", Namespace);
275 await this.output.FlushAsync();
276
277 if (this.deleteAfterDays > 0 && this.deleteAfterDays < int.MaxValue)
278 {
279 string FolderName = Path.GetDirectoryName(s);
280
281 if (!string.IsNullOrEmpty(FolderName))
282 Files.DeleteOldFiles(FolderName, TimeSpan.FromDays(this.deleteAfterDays), SearchOption.AllDirectories, true);
283 }
284 }
285
289 private async Task DisposeOutput()
290 {
291 if (!(this.output is null))
292 {
293 await this.output.FlushAsync();
294 this.output.Dispose();
295 this.output = null;
296 }
297
298 this.file?.Dispose();
299 this.file = null;
300
301 this.textOutput?.Flush();
302 this.textOutput = null;
303 }
304
305 #endregion
306
307 #region ILedgerProvider
308
312 public Task Start()
313 {
314 this.running = true;
315 return Task.CompletedTask;
316 }
317
321 public async Task Stop()
322 {
323 if (this.running)
324 {
325 this.running = false;
326
327 if (!(this.output is null))
328 {
329 await this.output.WriteEndElementAsync();
330 await this.output.WriteEndDocumentAsync();
331 }
332 }
333
334 await this.Flush();
335
336 this.output?.Dispose();
337 this.output = null;
338 }
339
343 public async Task Flush()
344 {
345 if (!(this.output is null))
346 await this.output.FlushAsync();
347
348 if (!(this.file is null))
349 await this.file.FlushAsync();
350
351 if (!(this.textOutput is null))
352 await this.textOutput.FlushAsync();
353 }
354
359 public Task NewEntry(object Object)
360 {
361 return this.OutputEntry("New", Object);
362 }
363
368 public Task UpdatedEntry(object Object)
369 {
370 return this.OutputEntry("Update", Object);
371 }
372
377 public Task DeletedEntry(object Object)
378 {
379 return this.OutputEntry("Delete", Object);
380 }
381
382 private async Task OutputEntry(string Method, object Object)
383 {
384 if (!this.running)
385 return;
386
387 GenericObject Obj = await Database.Generalize(Object);
388 DateTime Timestamp = DateTime.UtcNow;
389 this.lastEvent = Timestamp;
390
391 await this.semaphore.WaitAsync();
392 try
393 {
394 try
395 {
396 await this.BeforeWrite();
397
398 if (!(this.output is null))
399 {
400 await this.output.WriteStartElementAsync(string.Empty, Method, Namespace);
401 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(Timestamp));
402 await this.output.WriteAttributeStringAsync(string.Empty, "collection", string.Empty, Obj.CollectionName);
403 await this.output.WriteAttributeStringAsync(string.Empty, "type", string.Empty, Obj.TypeName);
404 await this.output.WriteAttributeStringAsync(string.Empty, "id", string.Empty, Obj.ObjectId.ToString());
405
406 foreach (KeyValuePair<string, object> P in Obj.Properties)
407 await ReportProperty(this.output, P.Key, P.Value, Namespace);
408
409 await this.output.WriteEndElementAsync();
410 await this.output.FlushAsync();
411 }
412 }
413 catch (Exception)
414 {
415 try
416 {
417 await this.DisposeOutput();
418 }
419 catch (Exception)
420 {
421 // Ignore
422 }
423 }
424 }
425 finally
426 {
427 this.semaphore.Release();
428 }
429 }
430
438 public static async Task ReportProperty(XmlWriter Output, string PropertyName, object PropertyValue, string Namespace)
439 {
440 if (PropertyValue is null)
441 {
442 await Output.WriteStartElementAsync(string.Empty, "Null", Namespace);
443 if (!(PropertyName is null))
444 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
445 await Output.WriteEndElementAsync();
446 }
447 else if (PropertyValue is Enum)
448 {
449 await Output.WriteStartElementAsync(string.Empty, "En", Namespace);
450 if (!(PropertyName is null))
451 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
452 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
453 await Output.WriteEndElementAsync();
454 }
455 else
456 {
457 switch (Type.GetTypeCode(PropertyValue.GetType()))
458 {
459 case TypeCode.Boolean:
460 await Output.WriteStartElementAsync(string.Empty, "Bl", Namespace);
461 if (!(PropertyName is null))
462 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
463 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((bool)PropertyValue));
464 await Output.WriteEndElementAsync();
465 break;
466
467 case TypeCode.Byte:
468 await Output.WriteStartElementAsync(string.Empty, "B", Namespace);
469 if (!(PropertyName is null))
470 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
471 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
472 await Output.WriteEndElementAsync();
473 break;
474
475 case TypeCode.Char:
476 await Output.WriteStartElementAsync(string.Empty, "Ch", Namespace);
477 if (!(PropertyName is null))
478 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
479 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
480 await Output.WriteEndElementAsync();
481 break;
482
483 case TypeCode.DateTime:
484 await Output.WriteStartElementAsync(string.Empty, "DT", Namespace);
485 if (!(PropertyName is null))
486 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
487 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, XML.Encode((DateTime)PropertyValue));
488 await Output.WriteEndElementAsync();
489 break;
490
491 case TypeCode.Decimal:
492 await Output.WriteStartElementAsync(string.Empty, "Dc", Namespace);
493 if (!(PropertyName is null))
494 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
495 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((decimal)PropertyValue));
496 await Output.WriteEndElementAsync();
497 break;
498
499 case TypeCode.Double:
500 await Output.WriteStartElementAsync(string.Empty, "Db", Namespace);
501 if (!(PropertyName is null))
502 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
503 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((double)PropertyValue));
504 await Output.WriteEndElementAsync();
505 break;
506
507 case TypeCode.Int16:
508 await Output.WriteStartElementAsync(string.Empty, "I2", Namespace);
509 if (!(PropertyName is null))
510 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
511 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
512 await Output.WriteEndElementAsync();
513 break;
514
515 case TypeCode.Int32:
516 await Output.WriteStartElementAsync(string.Empty, "I4", Namespace);
517 if (!(PropertyName is null))
518 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
519 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
520 await Output.WriteEndElementAsync();
521 break;
522
523 case TypeCode.Int64:
524 await Output.WriteStartElementAsync(string.Empty, "I8", Namespace);
525 if (!(PropertyName is null))
526 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
527 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
528 await Output.WriteEndElementAsync();
529 break;
530
531 case TypeCode.SByte:
532 await Output.WriteStartElementAsync(string.Empty, "I1", Namespace);
533 if (!(PropertyName is null))
534 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
535 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
536 await Output.WriteEndElementAsync();
537 break;
538
539 case TypeCode.Single:
540 await Output.WriteStartElementAsync(string.Empty, "Fl", Namespace);
541 if (!(PropertyName is null))
542 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
543 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((float)PropertyValue));
544 await Output.WriteEndElementAsync();
545 break;
546
547 case TypeCode.String:
548 string s = PropertyValue.ToString();
549 try
550 {
551 XmlConvert.VerifyXmlChars(s);
552 await Output.WriteStartElementAsync(string.Empty, "S", Namespace);
553 if (!(PropertyName is null))
554 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
555 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
556 await Output.WriteEndElementAsync();
557 }
558 catch (XmlException)
559 {
560 byte[] Bin = System.Text.Encoding.UTF8.GetBytes(s);
561 s = Convert.ToBase64String(Bin);
562 await Output.WriteStartElementAsync(string.Empty, "S64", Namespace);
563 if (!(PropertyName is null))
564 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
565 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
566 await Output.WriteEndElementAsync();
567 }
568 break;
569
570 case TypeCode.UInt16:
571 await Output.WriteStartElementAsync(string.Empty, "U2", Namespace);
572 if (!(PropertyName is null))
573 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
574 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
575 await Output.WriteEndElementAsync();
576 break;
577
578 case TypeCode.UInt32:
579 await Output.WriteStartElementAsync(string.Empty, "U4", Namespace);
580 if (!(PropertyName is null))
581 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
582 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
583 await Output.WriteEndElementAsync();
584 break;
585
586 case TypeCode.UInt64:
587 await Output.WriteStartElementAsync(string.Empty, "U8", Namespace);
588 if (!(PropertyName is null))
589 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
590 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
591 await Output.WriteEndElementAsync();
592 break;
593
594 case (TypeCode)2: // DBNull:
595 case TypeCode.Empty:
596 await Output.WriteStartElementAsync(string.Empty, "Null", Namespace);
597 if (!(PropertyName is null))
598 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
599 await Output.WriteEndElementAsync();
600 break;
601
602 case TypeCode.Object:
603 if (PropertyValue is TimeSpan)
604 {
605 await Output.WriteStartElementAsync(string.Empty, "TS", Namespace);
606 if (!(PropertyName is null))
607 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
608 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
609 await Output.WriteEndElementAsync();
610 }
611 else if (PropertyValue is DateTimeOffset DTO)
612 {
613 await Output.WriteStartElementAsync(string.Empty, "DTO", Namespace);
614 if (!(PropertyName is null))
615 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
616 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, XML.Encode(DTO));
617 await Output.WriteEndElementAsync();
618 }
619 else if (PropertyValue is CaseInsensitiveString Cis)
620 {
621 s = Cis.Value;
622 try
623 {
624 XmlConvert.VerifyXmlChars(s);
625 await Output.WriteStartElementAsync(string.Empty, "CIS", Namespace);
626 if (!(PropertyName is null))
627 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
628 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
629 await Output.WriteEndElementAsync();
630 }
631 catch (XmlException)
632 {
633 byte[] Bin = System.Text.Encoding.UTF8.GetBytes(s);
634 s = Convert.ToBase64String(Bin);
635 await Output.WriteStartElementAsync(string.Empty, "CIS64", Namespace);
636 if (!(PropertyName is null))
637 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
638 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
639 await Output.WriteEndElementAsync();
640 }
641 }
642 else if (PropertyValue is byte[] Bin)
643 {
644 await Output.WriteStartElementAsync(string.Empty, "Bin", Namespace);
645 if (!(PropertyName is null))
646 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
647
648 byte[] Buf = null;
649 int c = Bin.Length;
650 int i = 0;
651 int d;
652 int j;
653
654 while (i < c)
655 {
656 d = c - i;
657 if (d > 49152)
658 j = 49152;
659 else
660 j = (int)d;
661
662 if (Buf is null)
663 {
664 if (i == 0 && j == c)
665 Buf = Bin;
666 else
667 Buf = new byte[j];
668 }
669
670 if (Buf != Bin)
671 Buffer.BlockCopy(Bin, i, Buf, 0, j);
672
673 await Output.WriteElementStringAsync(string.Empty, "Chunk", Namespace, Convert.ToBase64String(Buf, 0, j));
674 i += j;
675 }
676
677 await Output.WriteEndElementAsync();
678 }
679 else if (PropertyValue is Guid)
680 {
681 await Output.WriteStartElementAsync(string.Empty, "ID", Namespace);
682 if (!(PropertyName is null))
683 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
684 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
685 await Output.WriteEndElementAsync();
686 }
687 else if (PropertyValue is Array A)
688 {
689 await Output.WriteStartElementAsync(string.Empty, "Array", Namespace);
690 if (!(PropertyName is null))
691 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
692 await Output.WriteAttributeStringAsync(string.Empty, "elementType", string.Empty, PropertyValue.GetType().GetElementType().FullName);
693
694 foreach (object Obj in A)
695 await ReportProperty(Output, null, Obj, Namespace);
696
697 await Output.WriteEndElementAsync();
698 }
699 else if (PropertyValue is GenericObject Obj)
700 {
701 await Output.WriteStartElementAsync(string.Empty, "Obj", Namespace);
702 if (!(PropertyName is null))
703 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
704 await Output.WriteAttributeStringAsync(string.Empty, "type", string.Empty, Obj.TypeName);
705
706 foreach (KeyValuePair<string, object> P in Obj)
707 await ReportProperty(Output, P.Key, P.Value, Namespace);
708
709 await Output.WriteEndElementAsync();
710 }
711 else
712 throw new Exception("Unhandled property value type: " + PropertyValue.GetType().FullName);
713 break;
714
715 default:
716 throw new Exception("Unhandled property value type: " + PropertyValue.GetType().FullName);
717 }
718 }
719 }
720
725 public async Task ClearedCollection(string Collection)
726 {
727 if (!this.running)
728 return;
729
730 DateTime Timestamp = DateTime.UtcNow;
731
732 await this.semaphore.WaitAsync();
733 try
734 {
735 try
736 {
737 await this.BeforeWrite();
738
739 if (!(this.output is null))
740 {
741 await this.output.WriteStartElementAsync(string.Empty, "Clear", Namespace);
742 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(Timestamp));
743 await this.output.WriteAttributeStringAsync(string.Empty, "collection", string.Empty, Collection);
744 await this.output.WriteEndElementAsync();
745 await this.output.FlushAsync();
746 }
747 }
748 catch (Exception)
749 {
750 try
751 {
752 await this.DisposeOutput();
753 }
754 catch (Exception)
755 {
756 // Ignore
757 }
758 }
759 }
760 finally
761 {
762 this.semaphore.Release();
763 }
764 }
765
771 public void Register(ILedgerExternalEvents ExternalEvents)
772 {
773 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
774 throw new Exception("An interface for external events has already been registered.");
775
776 this.externalEvents = ExternalEvents;
777 }
778
784 public void Unregister(ILedgerExternalEvents ExternalEvents)
785 {
786 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
787 throw new Exception("The registered interface for external events differs from the one presented.");
788
789 this.externalEvents = null;
790 }
791
797 public Task<ILedgerEnumerator<T>> GetEnumerator<T>()
798 {
799 return Task.FromResult<ILedgerEnumerator<T>>(new NoEntries<T>());
800 }
801
807 public Task<ILedgerEnumerator<object>> GetEnumerator(string CollectionName)
808 {
809 return Task.FromResult<ILedgerEnumerator<object>>(new NoEntries<object>());
810 }
811
812 private class NoEntries<T> : ILedgerEnumerator<T>
813 {
814 public ILedgerEntry<T> Current => null;
815 object IEnumerator.Current => null;
816 public void Dispose() { }
817 public bool MoveNext() => false;
818 public Task<bool> MoveNextAsync() => Task.FromResult(false);
819 public void Reset() { }
820 }
821
826 public Task<string[]> GetCollections()
827 {
828 return Task.FromResult(Array.Empty<string>());
829 }
830
838 public Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction)
839 {
840 return Task.FromResult(true);
841 }
842
851 public Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction, ProfilerThread Thread)
852 {
853 return Task.FromResult(true);
854 }
855
856 #endregion
857
858 #region ILedgerExport
859
860 private readonly LinkedList<KeyValuePair<string, object>> blockMetaData = new LinkedList<KeyValuePair<string, object>>();
861 private string writtenCollection = null;
862 private string writtenBlockId = null;
863 private string currentCollection = null;
864 private string currentBlockId = null;
865 private string currentEntryObjectId = null;
866 private string currentEntryObjectType = null;
867 private EntryType currentEntryType = EntryType.New;
868 private DateTimeOffset currentEntryTimestamp = DateTimeOffset.MinValue;
869 private Dictionary<string, object> currentEntryProperties = null;
870
876 public async Task<bool> StartLedger(ILedgerProvider Provider)
877 {
878 this.currentCollection = null;
879 await this.Start();
880 return true;
881 }
882
887 public async Task<bool> EndLedger()
888 {
889 await this.Stop();
890 return true;
891 }
892
898 public Task<bool> StartCollection(string CollectionName)
899 {
900 this.currentCollection = CollectionName;
901 return Task.FromResult(true);
902 }
903
908 public async Task<bool> EndCollection()
909 {
910 if (!(this.writtenCollection is null))
911 {
912 await this.output.WriteEndElementAsync();
913 this.writtenCollection = null;
914 }
915
916 this.currentCollection = null;
917 return true;
918 }
919
925 public Task<bool> StartBlock(string BlockID)
926 {
927 this.currentBlockId = BlockID;
928 return Task.FromResult(true);
929 }
930
937 public Task<bool> BlockMetaData(string Key, object Value)
938 {
939 this.blockMetaData.AddLast(new KeyValuePair<string, object>(Key, Value));
940 return Task.FromResult(true);
941 }
942
947 public async Task<bool> EndBlock()
948 {
949 if (!(this.writtenBlockId is null))
950 {
951 await this.output.WriteEndElementAsync();
952 this.writtenBlockId = null;
953 this.blockMetaData.Clear();
954 }
955
956 this.currentBlockId = null;
957 return true;
958 }
959
969 public Task<bool> StartEntry(string ObjectId, string TypeName, EntryType EntryType, DateTimeOffset EntryTimestamp)
970 {
971 this.currentEntryObjectId = ObjectId;
972 this.currentEntryObjectType = TypeName;
973 this.currentEntryType = EntryType;
974 this.currentEntryTimestamp = EntryTimestamp;
975
976 if (this.currentEntryProperties is null)
977 this.currentEntryProperties = new Dictionary<string, object>();
978 else
979 this.currentEntryProperties.Clear();
980
981 return Task.FromResult(true);
982 }
983
988 public async Task<bool> EndEntry()
989 {
990 if (!this.running)
991 return false;
992
993 await this.semaphore.WaitAsync();
994 try
995 {
996 try
997 {
998 await this.BeforeWrite();
999
1000 if (!(this.output is null))
1001 {
1002 await this.WritePendingInfoLocked();
1003
1004 await this.output.WriteStartElementAsync(string.Empty, this.currentEntryType.ToString(), Namespace);
1005 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(this.currentEntryTimestamp));
1006 await this.output.WriteAttributeStringAsync(string.Empty, "type", string.Empty, this.currentEntryObjectType);
1007 await this.output.WriteAttributeStringAsync(string.Empty, "id", string.Empty, this.currentEntryObjectId);
1008
1009 foreach (KeyValuePair<string, object> P in this.currentEntryProperties)
1010 await ReportProperty(this.output, P.Key, P.Value, Namespace);
1011
1012 await this.output.WriteEndElementAsync();
1013 await this.output.FlushAsync();
1014 }
1015 }
1016 catch (Exception)
1017 {
1018 try
1019 {
1020 await this.DisposeOutput();
1021 }
1022 catch (Exception)
1023 {
1024 // Ignore
1025 }
1026 }
1027 }
1028 finally
1029 {
1030 this.semaphore.Release();
1031 }
1032
1033 return true;
1034 }
1035
1036 private async Task WritePendingInfoLocked()
1037 {
1038 if (!(this.currentBlockId is null) &&
1039 !(this.writtenBlockId is null) &&
1040 this.currentBlockId != this.writtenBlockId)
1041 {
1042 await this.output.WriteEndElementAsync();
1043 this.writtenBlockId = null;
1044 this.blockMetaData.Clear();
1045 }
1046
1047 if (!(this.currentCollection is null) &&
1048 !(this.writtenCollection is null) &&
1049 this.currentCollection != this.writtenCollection)
1050 {
1051 await this.output.WriteEndElementAsync();
1052 this.writtenCollection = null;
1053 }
1054
1055 if (this.writtenCollection is null)
1056 {
1057 await this.output.WriteStartElementAsync(string.Empty, "Collection", Namespace);
1058 await this.output.WriteAttributeStringAsync(string.Empty, "name", string.Empty, this.currentCollection);
1059 this.writtenCollection = this.currentCollection;
1060 }
1061
1062 if (this.writtenBlockId is null)
1063 {
1064 await this.output.WriteStartElementAsync(string.Empty, "Block", Namespace);
1065 await this.output.WriteAttributeStringAsync(string.Empty, "id", string.Empty, this.currentBlockId);
1066 this.writtenBlockId = this.currentBlockId;
1067
1068 if (!(this.blockMetaData.First is null))
1069 {
1070 foreach (KeyValuePair<string, object> P in this.blockMetaData)
1071 await ReportProperty(this.output, P.Key, P.Value, Namespace);
1072 }
1073 }
1074 }
1075
1080 public async Task<bool> CollectionCleared(DateTimeOffset EntryTimestamp)
1081 {
1082 if (!this.running)
1083 return false;
1084
1085 await this.semaphore.WaitAsync();
1086 try
1087 {
1088 try
1089 {
1090 await this.BeforeWrite();
1091
1092 if (!(this.output is null))
1093 {
1094 await this.WritePendingInfoLocked();
1095
1096 await this.output.WriteStartElementAsync(string.Empty, "Clear", Namespace);
1097 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(EntryTimestamp));
1098 await this.output.WriteEndElementAsync();
1099 await this.output.FlushAsync();
1100 }
1101 }
1102 catch (Exception)
1103 {
1104 try
1105 {
1106 await this.DisposeOutput();
1107 }
1108 catch (Exception)
1109 {
1110 // Ignore
1111 }
1112 }
1113 }
1114 finally
1115 {
1116 this.semaphore.Release();
1117 }
1118
1119 return true;
1120 }
1121
1128 public Task<bool> ReportProperty(string PropertyName, object PropertyValue)
1129 {
1130 this.currentEntryProperties[PropertyName] = PropertyValue;
1131 return Task.FromResult(true);
1132 }
1133
1139 public async Task<bool> ReportError(string Message)
1140 {
1141 if (!this.running)
1142 return false;
1143
1144 await this.semaphore.WaitAsync();
1145 try
1146 {
1147 try
1148 {
1149 await this.BeforeWrite();
1150
1151 if (!(this.output is null))
1152 {
1153 await this.WritePendingInfoLocked();
1154
1155 await this.output.WriteCommentAsync(Message);
1156 await this.output.FlushAsync();
1157 }
1158 }
1159 catch (Exception)
1160 {
1161 try
1162 {
1163 await this.DisposeOutput();
1164 }
1165 catch (Exception)
1166 {
1167 // Ignore
1168 }
1169 }
1170 }
1171 finally
1172 {
1173 this.semaphore.Release();
1174 }
1175
1176 return true;
1177 }
1178
1184 public Task<bool> ReportException(Exception Exception)
1185 {
1186 return this.ReportError(Exception.Message);
1187 }
1188
1189 #endregion
1190
1191 }
1192}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:596
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
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
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
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< GenericObject > Generalize(object Object)
Creates a generalized representation of an object.
Definition: Database.cs:2473
Contains basic ledger export restrictions.
Generic object. Contains a sequence of properties.
IEnumerable< KeyValuePair< string, object > > Properties
Current set of properties.
Simple ledger that records anything that happens in the database to XML files in the program data fol...
async Task< bool > CollectionCleared(DateTimeOffset EntryTimestamp)
Is called when the collection has been cleared.
XmlFileLedger(string FileName, string Transform, int DeleteAfterDays)
Simple ledger that records anything that happens in the database to XML files in the program data fol...
Task< bool > Export(ILedgerExport Output, LedgerExportRestriction Restriction)
Performs an export of the entire ledger.
Task< string[]> GetCollections()
Gets an array of available collections.
async Task Stop()
Called when processing ends.
Task< ILedgerEnumerator< T > > GetEnumerator< T >()
Gets an eumerator for objects of type T .
Task< bool > Export(ILedgerExport Output, LedgerExportRestriction Restriction, ProfilerThread Thread)
Performs an export of the entire ledger.
async Task ClearedCollection(string Collection)
Clears a collection in the ledger.
void Unregister(ILedgerExternalEvents ExternalEvents)
Unregisters a recipient of external events.
XmlFileLedger(TextWriter Output)
Simple ledger that records anything that happens in the database to a text output stream.
async Task< bool > EndBlock()
Is called when a block in a collection is finished.
Task DeletedEntry(object Object)
Deletes an entry in the ledger.
void Register(ILedgerExternalEvents ExternalEvents)
Registers a recipient of external events.
XmlFileLedger(string FileName, string Transform)
Simple ledger that records anything that happens in the database to XML files in the program data fol...
Task< bool > StartEntry(string ObjectId, string TypeName, EntryType EntryType, DateTimeOffset EntryTimestamp)
Is called when an entry is started.
Task< bool > StartCollection(string CollectionName)
Is called when a collection is started.
Task< ILedgerEnumerator< object > > GetEnumerator(string CollectionName)
Gets an eumerator for objects in a collection.
async Task Flush()
Persists any pending changes.
Task< bool > ReportException(Exception Exception)
Is called when an exception has occurred.
Task< bool > StartBlock(string BlockID)
Is called when a block in a collection is started.
async Task< bool > EndEntry()
Is called when an entry is finished.
DateTime LastEvent
Timestamp of Last event
XmlFileLedger(string FileName, int DeleteAfterDays)
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
Task Start()
Called when processing starts.
XmlFileLedger(string FileName)
Simple ledger that records anything that happens in the database to XML files in the program data fol...
static async Task ReportProperty(XmlWriter Output, string PropertyName, object PropertyValue, string Namespace)
Serializes a property to XML.
async Task< bool > EndLedger()
Is called when export of ledger is finished.
Task NewEntry(object Object)
Adds an entry to the ledger.
async Task< bool > StartLedger(ILedgerProvider Provider)
Is called when export of ledger is started.
Task< bool > BlockMetaData(string Key, object Value)
Reports block meta-data.
Task< bool > ReportProperty(string PropertyName, object PropertyValue)
Is called when a property is reported.
async Task< bool > EndCollection()
Is called when a collection is finished.
Task UpdatedEntry(object Object)
Updates an entry in the ledger.
async Task< bool > ReportError(string Message)
Is called when an error is reported.
Class that generates a sequence of file names, based on the system time.
Contains static methods
Definition: Files.cs:14
static void DeleteOldFiles(string FolderName, TimeSpan MaxAge)
Deletes old files.
Definition: Files.cs:140
Class that keeps track of events and timing for one thread.
Interface for ledger entries.
Definition: ILedgerEntry.cs:36
Enumerator of ledger entries
Interface for proxy for reporting changes to the ledger from external sources.
Interface for ledger providers that can be plugged into the static Ledger class.
EntryType
Ledger entry type.
Definition: ILedgerEntry.cs:9