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;
2using System.Collections;
3using System.Collections.Generic;
4using System.IO;
5using System.Text;
6using System.Threading;
7using System.Threading.Tasks;
8using System.Xml;
9using Waher.Content;
11using Waher.Events;
14using Waher.Script;
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 string fileName;
36 private string lastFileName = null;
37 private readonly string transform = null;
38 private readonly int deleteAfterDays;
39 private bool running;
40
41 #region Construction
42
57 public XmlFileLedger(string FileName)
58 : this(FileName, string.Empty, 7)
59 {
60 }
61
78 public XmlFileLedger(string FileName, int DeleteAfterDays)
79 : this(FileName, string.Empty, DeleteAfterDays)
80 {
81 }
82
98 public XmlFileLedger(string FileName, string Transform)
99 : this(FileName, Transform, 7)
100 {
101 }
102
120 public XmlFileLedger(string FileName, string Transform, int DeleteAfterDays)
121 {
122 this.file = null;
123 this.output = null;
124 this.fileName = FileName;
125 this.transform = Transform;
126 this.deleteAfterDays = DeleteAfterDays;
127
128 this.settings = new XmlWriterSettings()
129 {
130 CloseOutput = true,
131 ConformanceLevel = ConformanceLevel.Document,
132 Encoding = Encoding.UTF8,
133 Indent = true,
134 IndentChars = "\t",
135 NewLineChars = "\r\n",
136 NewLineHandling = NewLineHandling.Replace,
137 NewLineOnAttributes = false,
138 OmitXmlDeclaration = false,
139 WriteEndDocumentOnClose = true,
140 Async = true
141 };
142
143 string FolderName = Path.GetDirectoryName(FileName);
144
145 if (!string.IsNullOrEmpty(FolderName) && !Directory.Exists(FolderName))
146 {
147 Log.Informational("Creating folder.", FolderName);
148 Directory.CreateDirectory(FolderName);
149 }
150 }
151
156 public XmlFileLedger(TextWriter Output)
157 {
158 this.textOutput = Output;
159 this.file = null;
160 this.fileName = null;
161 this.transform = null;
162 this.deleteAfterDays = 0;
163
164 this.settings = new XmlWriterSettings()
165 {
166 CloseOutput = true,
167 ConformanceLevel = ConformanceLevel.Document,
168 Encoding = Encoding.UTF8,
169 Indent = true,
170 IndentChars = "\t",
171 NewLineChars = "\r\n",
172 NewLineHandling = NewLineHandling.Replace,
173 NewLineOnAttributes = false,
174 OmitXmlDeclaration = false,
175 WriteEndDocumentOnClose = true,
176 Async = true
177 };
178
179 this.output = XmlWriter.Create(this.textOutput, this.settings);
180 this.output.WriteStartDocument();
181 this.output.WriteStartElement("LedgerExport", Namespace);
182 this.output.Flush();
183 }
184
185 #endregion
186
187 #region Persistance
188
192 public string FileName => this.fileName;
193
197 public string Transform => this.transform;
198
202 public DateTime LastEvent => this.lastEvent;
203
210 public static string GetFileName(string TemplateFileName, DateTime TP)
211 {
212 return TemplateFileName.
213 Replace("%YEAR%", TP.Year.ToString("D4")).
214 Replace("%MONTH%", TP.Month.ToString("D2")).
215 Replace("%DAY%", TP.Day.ToString("D2")).
216 Replace("%HOUR%", TP.Hour.ToString("D2")).
217 Replace("%MINUTE%", TP.Minute.ToString("D2")).
218 Replace("%SECOND%", TP.Second.ToString("D2"));
219 }
220
225 public static void MakeUnique(ref string FileName)
226 {
227 if (File.Exists(FileName))
228 {
229 int i = FileName.LastIndexOf('.');
230 int j = 2;
231
232 if (i < 0)
233 i = FileName.Length;
234
235 string s;
236
237 do
238 {
239 s = FileName.Insert(i, " (" + (j++).ToString() + ")");
240 }
241 while (File.Exists(s));
242
243 FileName = s;
244 }
245 }
246
250 private async Task BeforeWrite()
251 {
252 if (this.fileName is null)
253 return;
254
255 DateTime TP = DateTime.Now;
256 string s = GetFileName(this.fileName, TP);
257 this.lastEvent = TP;
258
259 if (!(this.lastFileName is null) && this.lastFileName == s && !(this.file is null) && this.file.BaseStream.CanWrite)
260 return;
261
262 try
263 {
264 if (!(this.output is null))
265 {
266 await this.output.WriteEndElementAsync();
267 await this.output.WriteEndDocumentAsync();
268 await this.output.FlushAsync();
269 }
270
271 this.file?.Dispose();
272 }
273 catch (Exception)
274 {
275 try
276 {
277 await this.DisposeOutput();
278 }
279 catch (Exception)
280 {
281 // Ignore
282 }
283 }
284
285 this.file = null;
286 this.output = null;
287
288 string s2 = s;
289 MakeUnique(ref s2);
290
291 try
292 {
293 this.file = File.CreateText(s2);
294 this.lastFileName = s;
295 this.output = XmlWriter.Create(this.file, this.settings);
296 }
297 catch (Exception ex)
298 {
299 Log.Exception(ex);
300 this.output = null;
301 return;
302 }
303
304 await this.output.WriteStartDocumentAsync();
305
306 if (!string.IsNullOrEmpty(this.transform))
307 {
308 if (File.Exists(this.transform))
309 {
310 try
311 {
312 byte[] XsltBin = await Resources.ReadAllBytesAsync(this.transform);
313
314 await this.output.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"data:text/xsl;base64," +
315 Convert.ToBase64String(XsltBin) + "\"");
316 }
317 catch (Exception)
318 {
319 await this.output.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"" + this.transform + "\"");
320 }
321 }
322 else
323 await this.output.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"" + this.transform + "\"");
324 }
325
326 await this.output.WriteStartElementAsync(string.Empty, "LedgerExport", Namespace);
327 await this.output.FlushAsync();
328
329 if (this.deleteAfterDays > 0 && this.deleteAfterDays < int.MaxValue)
330 {
331 string FolderName = Path.GetDirectoryName(s);
332
333 if (!string.IsNullOrEmpty(FolderName))
334 {
335 string[] Files = Directory.GetFiles(FolderName, "*.*");
336
337 foreach (string FileName in Files)
338 {
339 if ((DateTime.Now - File.GetLastWriteTime(FileName)).TotalDays >= this.deleteAfterDays)
340 {
341 try
342 {
343 File.Delete(FileName);
344 }
345 catch (IOException ex)
346 {
347 Log.Error("Unable to delete file: " + ex.Message, FileName);
348 }
349 catch (Exception ex)
350 {
352 }
353 }
354 }
355 }
356 }
357 }
358
362 private async Task DisposeOutput()
363 {
364 if (!(this.output is null))
365 {
366 await this.output.FlushAsync();
367 this.output.Dispose();
368 this.output = null;
369 }
370
371 this.file?.Dispose();
372 this.file = null;
373
374 this.textOutput?.Flush();
375 this.textOutput = null;
376 }
377
378 #endregion
379
380 #region ILedgerProvider
381
385 public Task Start()
386 {
387 this.running = true;
388 return Task.CompletedTask;
389 }
390
394 public async Task Stop()
395 {
396 if (this.running)
397 {
398 this.running = false;
399
400 if (!(this.output is null))
401 {
402 await this.output.WriteEndElementAsync();
403 await this.output.WriteEndDocumentAsync();
404 }
405 }
406
407 await this.Flush();
408 }
409
413 public async Task Flush()
414 {
415 if (!(this.output is null))
416 await this.output.FlushAsync();
417
418 if (!(this.file is null))
419 await this.file.FlushAsync();
420
421 if (!(this.textOutput is null))
422 await this.textOutput.FlushAsync();
423 }
424
429 public Task NewEntry(object Object)
430 {
431 return this.OutputEntry("New", Object);
432 }
433
438 public Task UpdatedEntry(object Object)
439 {
440 return this.OutputEntry("Update", Object);
441 }
442
447 public Task DeletedEntry(object Object)
448 {
449 return this.OutputEntry("Delete", Object);
450 }
451
452 private async Task OutputEntry(string Method, object Object)
453 {
454 if (!this.running)
455 return;
456
457 GenericObject Obj = await Database.Generalize(Object);
458 DateTime Timestamp = DateTime.UtcNow;
459
460 await this.semaphore.WaitAsync();
461 try
462 {
463 try
464 {
465 await this.BeforeWrite();
466
467 if (!(this.output is null))
468 {
469 await this.output.WriteStartElementAsync(string.Empty, Method, Namespace);
470 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(Timestamp));
471 await this.output.WriteAttributeStringAsync(string.Empty, "collection", string.Empty, Obj.CollectionName);
472 await this.output.WriteAttributeStringAsync(string.Empty, "type", string.Empty, Obj.TypeName);
473 await this.output.WriteAttributeStringAsync(string.Empty, "id", string.Empty, Obj.ObjectId.ToString());
474
475 foreach (KeyValuePair<string, object> P in Obj.Properties)
476 await ReportProperty(this.output, P.Key, P.Value, Namespace);
477
478 await this.output.WriteEndElementAsync();
479 await this.output.FlushAsync();
480 }
481 }
482 catch (Exception)
483 {
484 try
485 {
486 await this.DisposeOutput();
487 }
488 catch (Exception)
489 {
490 // Ignore
491 }
492 }
493 }
494 finally
495 {
496 this.semaphore.Release();
497 }
498 }
499
507 public static async Task ReportProperty(XmlWriter Output, string PropertyName, object PropertyValue, string Namespace)
508 {
509 if (PropertyValue is null)
510 {
511 await Output.WriteStartElementAsync(string.Empty, "Null", Namespace);
512 if (!(PropertyName is null))
513 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
514 await Output.WriteEndElementAsync();
515 }
516 else if (PropertyValue is Enum)
517 {
518 await Output.WriteStartElementAsync(string.Empty, "En", Namespace);
519 if (!(PropertyName is null))
520 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
521 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
522 await Output.WriteEndElementAsync();
523 }
524 else
525 {
526 switch (Type.GetTypeCode(PropertyValue.GetType()))
527 {
528 case TypeCode.Boolean:
529 await Output.WriteStartElementAsync(string.Empty, "Bl", Namespace);
530 if (!(PropertyName is null))
531 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
532 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((bool)PropertyValue));
533 await Output.WriteEndElementAsync();
534 break;
535
536 case TypeCode.Byte:
537 await Output.WriteStartElementAsync(string.Empty, "B", Namespace);
538 if (!(PropertyName is null))
539 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
540 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
541 await Output.WriteEndElementAsync();
542 break;
543
544 case TypeCode.Char:
545 await Output.WriteStartElementAsync(string.Empty, "Ch", Namespace);
546 if (!(PropertyName is null))
547 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
548 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
549 await Output.WriteEndElementAsync();
550 break;
551
552 case TypeCode.DateTime:
553 await Output.WriteStartElementAsync(string.Empty, "DT", Namespace);
554 if (!(PropertyName is null))
555 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
556 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, XML.Encode((DateTime)PropertyValue));
557 await Output.WriteEndElementAsync();
558 break;
559
560 case TypeCode.Decimal:
561 await Output.WriteStartElementAsync(string.Empty, "Dc", Namespace);
562 if (!(PropertyName is null))
563 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
564 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((decimal)PropertyValue));
565 await Output.WriteEndElementAsync();
566 break;
567
568 case TypeCode.Double:
569 await Output.WriteStartElementAsync(string.Empty, "Db", Namespace);
570 if (!(PropertyName is null))
571 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
572 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((double)PropertyValue));
573 await Output.WriteEndElementAsync();
574 break;
575
576 case TypeCode.Int16:
577 await Output.WriteStartElementAsync(string.Empty, "I2", Namespace);
578 if (!(PropertyName is null))
579 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
580 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
581 await Output.WriteEndElementAsync();
582 break;
583
584 case TypeCode.Int32:
585 await Output.WriteStartElementAsync(string.Empty, "I4", Namespace);
586 if (!(PropertyName is null))
587 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
588 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
589 await Output.WriteEndElementAsync();
590 break;
591
592 case TypeCode.Int64:
593 await Output.WriteStartElementAsync(string.Empty, "I8", Namespace);
594 if (!(PropertyName is null))
595 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
596 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
597 await Output.WriteEndElementAsync();
598 break;
599
600 case TypeCode.SByte:
601 await Output.WriteStartElementAsync(string.Empty, "I1", Namespace);
602 if (!(PropertyName is null))
603 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
604 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
605 await Output.WriteEndElementAsync();
606 break;
607
608 case TypeCode.Single:
609 await Output.WriteStartElementAsync(string.Empty, "Fl", Namespace);
610 if (!(PropertyName is null))
611 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
612 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, CommonTypes.Encode((float)PropertyValue));
613 await Output.WriteEndElementAsync();
614 break;
615
616 case TypeCode.String:
617 string s = PropertyValue.ToString();
618 try
619 {
620 XmlConvert.VerifyXmlChars(s);
621 await Output.WriteStartElementAsync(string.Empty, "S", Namespace);
622 if (!(PropertyName is null))
623 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
624 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
625 await Output.WriteEndElementAsync();
626 }
627 catch (XmlException)
628 {
629 byte[] Bin = System.Text.Encoding.UTF8.GetBytes(s);
630 s = Convert.ToBase64String(Bin);
631 await Output.WriteStartElementAsync(string.Empty, "S64", Namespace);
632 if (!(PropertyName is null))
633 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
634 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
635 await Output.WriteEndElementAsync();
636 }
637 break;
638
639 case TypeCode.UInt16:
640 await Output.WriteStartElementAsync(string.Empty, "U2", Namespace);
641 if (!(PropertyName is null))
642 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
643 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
644 await Output.WriteEndElementAsync();
645 break;
646
647 case TypeCode.UInt32:
648 await Output.WriteStartElementAsync(string.Empty, "U4", Namespace);
649 if (!(PropertyName is null))
650 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
651 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
652 await Output.WriteEndElementAsync();
653 break;
654
655 case TypeCode.UInt64:
656 await Output.WriteStartElementAsync(string.Empty, "U8", Namespace);
657 if (!(PropertyName is null))
658 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
659 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
660 await Output.WriteEndElementAsync();
661 break;
662
663 case (TypeCode)2: // DBNull:
664 case TypeCode.Empty:
665 await Output.WriteStartElementAsync(string.Empty, "Null", Namespace);
666 if (!(PropertyName is null))
667 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
668 await Output.WriteEndElementAsync();
669 break;
670
671 case TypeCode.Object:
672 if (PropertyValue is TimeSpan)
673 {
674 await Output.WriteStartElementAsync(string.Empty, "TS", Namespace);
675 if (!(PropertyName is null))
676 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
677 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
678 await Output.WriteEndElementAsync();
679 }
680 else if (PropertyValue is DateTimeOffset DTO)
681 {
682 await Output.WriteStartElementAsync(string.Empty, "DTO", Namespace);
683 if (!(PropertyName is null))
684 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
685 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, XML.Encode(DTO));
686 await Output.WriteEndElementAsync();
687 }
688 else if (PropertyValue is CaseInsensitiveString Cis)
689 {
690 s = Cis.Value;
691 try
692 {
693 XmlConvert.VerifyXmlChars(s);
694 await Output.WriteStartElementAsync(string.Empty, "CIS", Namespace);
695 if (!(PropertyName is null))
696 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
697 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
698 await Output.WriteEndElementAsync();
699 }
700 catch (XmlException)
701 {
702 byte[] Bin = System.Text.Encoding.UTF8.GetBytes(s);
703 s = Convert.ToBase64String(Bin);
704 await Output.WriteStartElementAsync(string.Empty, "CIS64", Namespace);
705 if (!(PropertyName is null))
706 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
707 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, s);
708 await Output.WriteEndElementAsync();
709 }
710 }
711 else if (PropertyValue is byte[] Bin)
712 {
713 await Output.WriteStartElementAsync(string.Empty, "Bin", Namespace);
714 if (!(PropertyName is null))
715 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
716
717 byte[] Buf = null;
718 int c = Bin.Length;
719 int i = 0;
720 int d;
721 int j;
722
723 while (i < c)
724 {
725 d = c - i;
726 if (d > 49152)
727 j = 49152;
728 else
729 j = (int)d;
730
731 if (Buf is null)
732 {
733 if (i == 0 && j == c)
734 Buf = Bin;
735 else
736 Buf = new byte[j];
737 }
738
739 if (Buf != Bin)
740 Array.Copy(Bin, i, Buf, 0, j);
741
742 await Output.WriteElementStringAsync(string.Empty, "Chunk", Namespace, Convert.ToBase64String(Buf, 0, j));
743 i += j;
744 }
745
746 await Output.WriteEndElementAsync();
747 }
748 else if (PropertyValue is Guid)
749 {
750 await Output.WriteStartElementAsync(string.Empty, "ID", Namespace);
751 if (!(PropertyName is null))
752 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
753 await Output.WriteAttributeStringAsync(string.Empty, "v", string.Empty, PropertyValue.ToString());
754 await Output.WriteEndElementAsync();
755 }
756 else if (PropertyValue is Array A)
757 {
758 await Output.WriteStartElementAsync(string.Empty, "Array", Namespace);
759 if (!(PropertyName is null))
760 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
761 await Output.WriteAttributeStringAsync(string.Empty, "elementType", string.Empty, PropertyValue.GetType().GetElementType().FullName);
762
763 foreach (object Obj in A)
764 await ReportProperty(Output, null, Obj, Namespace);
765
766 await Output.WriteEndElementAsync();
767 }
768 else if (PropertyValue is GenericObject Obj)
769 {
770 await Output.WriteStartElementAsync(string.Empty, "Obj", Namespace);
771 if (!(PropertyName is null))
772 await Output.WriteAttributeStringAsync(string.Empty, "n", string.Empty, PropertyName);
773 await Output.WriteAttributeStringAsync(string.Empty, "type", string.Empty, Obj.TypeName);
774
775 foreach (KeyValuePair<string, object> P in Obj)
776 await ReportProperty(Output, P.Key, P.Value, Namespace);
777
778 await Output.WriteEndElementAsync();
779 }
780 else
781 throw new Exception("Unhandled property value type: " + PropertyValue.GetType().FullName);
782 break;
783
784 default:
785 throw new Exception("Unhandled property value type: " + PropertyValue.GetType().FullName);
786 }
787 }
788 }
789
794 public async Task ClearedCollection(string Collection)
795 {
796 if (!this.running)
797 return;
798
799 DateTime Timestamp = DateTime.UtcNow;
800
801 await this.semaphore.WaitAsync();
802 try
803 {
804 try
805 {
806 await this.BeforeWrite();
807
808 if (!(this.output is null))
809 {
810 await this.output.WriteStartElementAsync(string.Empty, "Clear", Namespace);
811 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(Timestamp));
812 await this.output.WriteAttributeStringAsync(string.Empty, "collection", string.Empty, Collection);
813 await this.output.WriteEndElementAsync();
814 await this.output.FlushAsync();
815 }
816 }
817 catch (Exception)
818 {
819 try
820 {
821 await this.DisposeOutput();
822 }
823 catch (Exception)
824 {
825 // Ignore
826 }
827 }
828 }
829 finally
830 {
831 this.semaphore.Release();
832 }
833 }
834
840 public void Register(ILedgerExternalEvents ExternalEvents)
841 {
842 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
843 throw new Exception("An interface for external events has already been registered.");
844
845 this.externalEvents = ExternalEvents;
846 }
847
853 public void Unregister(ILedgerExternalEvents ExternalEvents)
854 {
855 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
856 throw new Exception("The registered interface for external events differs from the one presented.");
857
858 this.externalEvents = null;
859 }
860
866 public Task<ILedgerEnumerator<T>> GetEnumerator<T>()
867 {
868 return Task.FromResult<ILedgerEnumerator<T>>(new NoEntries<T>());
869 }
870
876 public Task<ILedgerEnumerator<object>> GetEnumerator(string CollectionName)
877 {
878 return Task.FromResult<ILedgerEnumerator<object>>(new NoEntries<object>());
879 }
880
881 private class NoEntries<T> : ILedgerEnumerator<T>
882 {
883 public ILedgerEntry<T> Current => null;
884 object IEnumerator.Current => null;
885 public void Dispose() { }
886 public bool MoveNext() => false;
887 public Task<bool> MoveNextAsync() => Task.FromResult(false);
888 public void Reset() { }
889 }
890
895 public Task<string[]> GetCollections()
896 {
897 return Task.FromResult(new string[0]);
898 }
899
907 public Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction)
908 {
909 return Task.FromResult(true);
910 }
911
920 public Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction, ProfilerThread Thread)
921 {
922 return Task.FromResult(true);
923 }
924
925 #endregion
926
927 #region ILedgerExport
928
929 private readonly LinkedList<KeyValuePair<string, object>> blockMetaData = new LinkedList<KeyValuePair<string, object>>();
930 private string writtenCollection = null;
931 private string writtenBlockId = null;
932 private string currentCollection = null;
933 private string currentBlockId = null;
934 private string currentEntryObjectId = null;
935 private string currentEntryObjectType = null;
936 private EntryType currentEntryType = EntryType.New;
937 private DateTimeOffset currentEntryTimestamp = DateTimeOffset.MinValue;
938 private Dictionary<string, object> currentEntryProperties = null;
939
944 public async Task<bool> StartLedger()
945 {
946 this.currentCollection = null;
947 await this.Start();
948 return true;
949 }
950
955 public async Task<bool> EndLedger()
956 {
957 await this.Stop();
958 return true;
959 }
960
966 public Task<bool> StartCollection(string CollectionName)
967 {
968 this.currentCollection = CollectionName;
969 return Task.FromResult(true);
970 }
971
976 public async Task<bool> EndCollection()
977 {
978 if (!(this.writtenCollection is null))
979 {
980 await this.output.WriteEndElementAsync();
981 this.writtenCollection = null;
982 }
983
984 this.currentCollection = null;
985 return true;
986 }
987
993 public Task<bool> StartBlock(string BlockID)
994 {
995 this.currentBlockId = BlockID;
996 return Task.FromResult(true);
997 }
998
1005 public Task<bool> BlockMetaData(string Key, object Value)
1006 {
1007 this.blockMetaData.AddLast(new KeyValuePair<string, object>(Key, Value));
1008 return Task.FromResult(true);
1009 }
1010
1015 public async Task<bool> EndBlock()
1016 {
1017 if (!(this.writtenBlockId is null))
1018 {
1019 await this.output.WriteEndElementAsync();
1020 this.writtenBlockId = null;
1021 this.blockMetaData.Clear();
1022 }
1023
1024 this.currentBlockId = null;
1025 return true;
1026 }
1027
1037 public Task<bool> StartEntry(string ObjectId, string TypeName, EntryType EntryType, DateTimeOffset EntryTimestamp)
1038 {
1039 this.currentEntryObjectId = ObjectId;
1040 this.currentEntryObjectType = TypeName;
1041 this.currentEntryType = EntryType;
1042 this.currentEntryTimestamp = EntryTimestamp;
1043
1044 if (this.currentEntryProperties is null)
1045 this.currentEntryProperties = new Dictionary<string, object>();
1046 else
1047 this.currentEntryProperties.Clear();
1048
1049 return Task.FromResult(true);
1050 }
1051
1056 public async Task<bool> EndEntry()
1057 {
1058 if (!this.running)
1059 return false;
1060
1061 await this.semaphore.WaitAsync();
1062 try
1063 {
1064 try
1065 {
1066 await this.BeforeWrite();
1067
1068 if (!(this.output is null))
1069 {
1070 await this.WritePendingInfoLocked();
1071
1072 await this.output.WriteStartElementAsync(string.Empty, this.currentEntryType.ToString(), Namespace);
1073 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(this.currentEntryTimestamp));
1074 await this.output.WriteAttributeStringAsync(string.Empty, "type", string.Empty, this.currentEntryObjectType);
1075 await this.output.WriteAttributeStringAsync(string.Empty, "id", string.Empty, this.currentEntryObjectId);
1076
1077 foreach (KeyValuePair<string, object> P in this.currentEntryProperties)
1078 await ReportProperty(this.output, P.Key, P.Value, Namespace);
1079
1080 await this.output.WriteEndElementAsync();
1081 await this.output.FlushAsync();
1082 }
1083 }
1084 catch (Exception)
1085 {
1086 try
1087 {
1088 await this.DisposeOutput();
1089 }
1090 catch (Exception)
1091 {
1092 // Ignore
1093 }
1094 }
1095 }
1096 finally
1097 {
1098 this.semaphore.Release();
1099 }
1100
1101 return true;
1102 }
1103
1104 private async Task WritePendingInfoLocked()
1105 {
1106 if (!(this.currentBlockId is null) &&
1107 !(this.writtenBlockId is null) &&
1108 this.currentBlockId != this.writtenBlockId)
1109 {
1110 await this.output.WriteEndElementAsync();
1111 this.writtenBlockId = null;
1112 this.blockMetaData.Clear();
1113 }
1114
1115 if (!(this.currentCollection is null) &&
1116 !(this.writtenCollection is null) &&
1117 this.currentCollection != this.writtenCollection)
1118 {
1119 await this.output.WriteEndElementAsync();
1120 this.writtenCollection = null;
1121 }
1122
1123 if (this.writtenCollection is null)
1124 {
1125 await this.output.WriteStartElementAsync(string.Empty, "Collection", Namespace);
1126 await this.output.WriteAttributeStringAsync(string.Empty, "name", string.Empty, this.currentCollection);
1127 this.writtenCollection = this.currentCollection;
1128 }
1129
1130 if (this.writtenBlockId is null)
1131 {
1132 await this.output.WriteStartElementAsync(string.Empty, "Block", Namespace);
1133 await this.output.WriteAttributeStringAsync(string.Empty, "id", string.Empty, this.currentBlockId);
1134 this.writtenBlockId = this.currentBlockId;
1135
1136 if (!(this.blockMetaData.First is null))
1137 {
1138 foreach (KeyValuePair<string, object> P in this.blockMetaData)
1139 await ReportProperty(this.output, P.Key, P.Value, Namespace);
1140 }
1141 }
1142 }
1143
1148 public async Task<bool> CollectionCleared(DateTimeOffset EntryTimestamp)
1149 {
1150 if (!this.running)
1151 return false;
1152
1153 await this.semaphore.WaitAsync();
1154 try
1155 {
1156 try
1157 {
1158 await this.BeforeWrite();
1159
1160 if (!(this.output is null))
1161 {
1162 await this.WritePendingInfoLocked();
1163
1164 await this.output.WriteStartElementAsync(string.Empty, "Clear", Namespace);
1165 await this.output.WriteAttributeStringAsync(string.Empty, "timestamp", string.Empty, XML.Encode(EntryTimestamp));
1166 await this.output.WriteEndElementAsync();
1167 await this.output.FlushAsync();
1168 }
1169 }
1170 catch (Exception)
1171 {
1172 try
1173 {
1174 await this.DisposeOutput();
1175 }
1176 catch (Exception)
1177 {
1178 // Ignore
1179 }
1180 }
1181 }
1182 finally
1183 {
1184 this.semaphore.Release();
1185 }
1186
1187 return true;
1188 }
1189
1196 public Task<bool> ReportProperty(string PropertyName, object PropertyValue)
1197 {
1198 this.currentEntryProperties[PropertyName] = PropertyValue;
1199 return Task.FromResult(true);
1200 }
1201
1207 public async Task<bool> ReportError(string Message)
1208 {
1209 if (!this.running)
1210 return false;
1211
1212 await this.semaphore.WaitAsync();
1213 try
1214 {
1215 try
1216 {
1217 await this.BeforeWrite();
1218
1219 if (!(this.output is null))
1220 {
1221 await this.WritePendingInfoLocked();
1222
1223 await this.output.WriteCommentAsync(Message);
1224 await this.output.FlushAsync();
1225 }
1226 }
1227 catch (Exception)
1228 {
1229 try
1230 {
1231 await this.DisposeOutput();
1232 }
1233 catch (Exception)
1234 {
1235 // Ignore
1236 }
1237 }
1238 }
1239 finally
1240 {
1241 this.semaphore.Release();
1242 }
1243
1244 return true;
1245 }
1246
1252 public Task<bool> ReportException(Exception Exception)
1253 {
1254 return this.ReportError(Exception.Message);
1255 }
1256
1257 #endregion
1258
1259 }
1260}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:594
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static async Task< byte[]> ReadAllBytesAsync(string FileName)
Reads a binary file asynchronously.
Definition: Resources.cs:183
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
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:1647
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:682
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:334
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:19
static Task< GenericObject > Generalize(object Object)
Creates a generalized representation of an object.
Definition: Database.cs:1678
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.
static void MakeUnique(ref string FileName)
Makes a file name unique.
DateTime LastEvent
Timestamp of Last event
static string GetFileName(string TemplateFileName, DateTime TP)
Gets the name of a file, given a file name template.
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()
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 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.
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
EntryType
Ledger entry type.
Definition: ILedgerEntry.cs:9