Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NeuroLedgerProvider.cs
1using System;
4using System.IO;
6using System.Text;
7using System.Threading.Tasks;
8using Waher.Content;
9using Waher.Events;
18using Waher.Security;
20
22{
27 {
28 private const int BlockPageSize = 10;
29
30 private readonly byte[] salt;
31 private readonly string id;
32 private readonly string folder;
33 private readonly string bucketFolder;
34 private readonly string blockFolder;
35 private readonly string defaultCollectionName;
36 private readonly string externalIdentity;
37 private readonly int saltLength;
38 private readonly int maxBlockSize;
39 private readonly bool debug;
40 private readonly AsyncQueue<WorkItem> eventQueue = new AsyncQueue<WorkItem>();
41 private readonly ISignatureAlgorithm signatureAlgorithm;
42 private readonly HashFunctionStream hashFunction;
43 private readonly SortedDictionary<string, bool> collections = new SortedDictionary<string, bool>();
44 private TaskCompletionSource<bool> completed = new TaskCompletionSource<bool>();
45 private SerializerCollection serializers;
46 private Cache<string, Bucket> buckets;
47 private AesCryptoServiceProvider aes;
48 private ILedgerExternalEvents externalEvents;
49 private bool disposed = false;
50 private bool stopped = false;
51
65 public NeuroLedgerProvider(string Folder, TimeSpan CollectionTime, int MaxBlockSize, byte[] Salt,
68 {
69 this.id = Guid.NewGuid().ToString().Replace("-", string.Empty);
70 this.folder = Path.GetFullPath(Folder);
71 this.bucketFolder = Path.Combine(this.folder, "Buckets");
72 this.blockFolder = Path.Combine(this.folder, "Blocks");
73 this.salt = Salt;
74 this.saltLength = this.salt.Length;
75 this.defaultCollectionName = DefaultCollectionName;
76 this.maxBlockSize = MaxBlockSize;
77 this.externalIdentity = ExternalIdentity;
78 this.signatureAlgorithm = SignatureAlgorithm;
79 this.hashFunction = HashFunction;
80 this.debug = Debug;
81 this.serializers = new SerializerCollection(this, true);
82
83 this.aes = new AesCryptoServiceProvider()
84 {
85 BlockSize = 128,
86 KeySize = 256,
87 Mode = CipherMode.CBC,
88 Padding = PaddingMode.Zeros
89 };
90
91 if (!string.IsNullOrEmpty(this.folder) && this.folder[^1] != Path.DirectorySeparatorChar)
92 this.folder += Path.DirectorySeparatorChar;
93
94 if (!Directory.Exists(this.folder))
95 Directory.CreateDirectory(this.folder);
96
97 if (!Directory.Exists(this.bucketFolder))
98 Directory.CreateDirectory(this.bucketFolder);
99
100 if (!Directory.Exists(this.blockFolder))
101 Directory.CreateDirectory(this.blockFolder);
102
103 this.blockFolder += Path.DirectorySeparatorChar;
104
105 this.buckets = new Cache<string, Bucket>(int.MaxValue, CollectionTime, CollectionTime, true);
106 this.buckets.Removed += this.Buckets_Removed;
107
108 Task _ = this.LoadCollections();
109 }
110
111 private async Task LoadCollections()
112 {
113 string s = await RuntimeSettings.GetAsync("NL.Collections", string.Empty);
114
115 if (!string.IsNullOrEmpty(s))
116 {
117 lock (this.collections)
118 {
119 foreach (string Collection in s.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries))
120 this.collections[Collection] = true;
121 }
122 }
123 }
124
125 private Task Buckets_Removed(object Sender, CacheItemEventArgs<string, Bucket> e)
126 {
127 if (!this.disposed && !this.stopped)
128 {
129 // Bucket is set, and Object is null, signifies Bucket is completed and block
130 // is to be generated.
131
132 this.eventQueue.QueueFirst(new WorkItem()
133 {
134 Bucket = e.Value,
135 Type = EntryType.New,
136 Object = null
137 });
138 }
139
140 return Task.CompletedTask;
141 }
142
143 private class WorkItem
144 {
145 public Bucket Bucket;
146 public EntryType Type;
147 public object Object;
148 }
149
153 public string BucketFolder => this.bucketFolder;
154
158 public string BlockFolder => this.blockFolder;
159
163 public HashFunctionStream HashFunction => this.hashFunction;
164
168 public ISignatureAlgorithm SignatureAlgorithm => this.signatureAlgorithm;
169
170 private async Task StorageTask()
171 {
172 try
173 {
174 WorkItem Item;
175 Type LastType = null;
176 ObjectSerializer LastSerializer = null;
177 DateTime NextCheck = DateTime.Now.AddMinutes(1);
178
179 while (!((Item = await this.eventQueue.Wait()) is null))
180 {
181 try
182 {
183 Bucket Bucket = Item.Bucket;
184 if (Bucket is null)
185 {
186 // Write Entry
187
188 EntryType EntryType = Item.Type;
189 object Object = Item.Object;
190 Type T = Object.GetType();
191 ObjectSerializer Serializer;
192 string CollectionName;
193 string BucketName;
194 bool DynamicArchiveTime;
195 int? ArchivingTimeDays;
196
197 if (T == LastType)
198 Serializer = LastSerializer;
199 else
200 {
201 Serializer = await this.GetObjectSerializerEx(Object);
202 LastType = T;
203 LastSerializer = Serializer;
204 }
205
206 if (!Serializer.ArchiveObjects)
207 continue;
208
209 ArchivingTimeDays = Serializer.GetArchivingTimeDays(Object);
210 DynamicArchiveTime = Serializer.ArchiveTimeDynamic;
211 CollectionName = await Serializer.CollectionName(Object);
212 if (string.IsNullOrEmpty(CollectionName))
213 CollectionName = this.defaultCollectionName;
214
215 BucketName = CollectionName;
216
217 if (ArchivingTimeDays.HasValue && ArchivingTimeDays.Value != int.MaxValue)
218 BucketName += "." + ArchivingTimeDays.Value.ToString();
219 else
220 BucketName += ".x";
221
222 if (ArchivingTimeDays.HasValue && ArchivingTimeDays.Value <= 0)
223 continue;
224
225 BinarySerializer Output = new BinarySerializer(CollectionName, Encoding.UTF8);
226 byte[] Binary;
227
228 try
229 {
230 await Serializer.Serialize(Output, false, false, Object, null);
231 Binary = Output.GetSerialization();
232 }
233 catch (Exception ex)
234 {
235 Guid? ObjectId;
236
237 try
238 {
239 ObjectId = await Serializer.GetObjectId(Object, false, null);
240 }
241 catch (Exception)
242 {
243 ObjectId = null;
244 }
245
246 Binary = null;
247 Log.Alert("Unable to store object in ledger.",
248 new KeyValuePair<string, object>("Type", T.FullName),
249 new KeyValuePair<string, object>("Collection", CollectionName),
250 new KeyValuePair<string, object>("ObjectId", ObjectId.HasValue ? ObjectId.Value.ToString() : string.Empty),
251 new KeyValuePair<string, object>("Message", ex.Message),
252 new KeyValuePair<string, object>("StackTrace", ex.StackTrace),
253 new KeyValuePair<string, object>("JSON", JSON.Encode(Object, true)));
254 }
255
256 if (!(Binary is null))
257 {
258 if (!this.buckets.TryGetValue(BucketName, out Bucket))
259 {
260 DateTime Expires;
261
262 if (ArchivingTimeDays.HasValue && ArchivingTimeDays.Value < int.MaxValue)
263 {
264 try
265 {
266 Expires = DateTime.UtcNow.AddDays(ArchivingTimeDays.Value);
267 }
268 catch (ArgumentOutOfRangeException ex)
269 {
270 Expires = DateTime.MaxValue;
271
272 Log.Error("Invalid archiving time encountered.", T.FullName, "Neuro-Ledger",
273 new KeyValuePair<string, object>("Type", T.FullName),
274 new KeyValuePair<string, object>("ArchivingTimeDays", ArchivingTimeDays.Value),
275 new KeyValuePair<string, object>("Message", ex.Message));
276 }
277 }
278 else
279 Expires = DateTime.MaxValue;
280
281 try
282 {
283 Bucket = await Bucket.Create(Path.Combine(this.bucketFolder, BucketName + ".bin"), CollectionName, Expires, this);
284 }
285 catch (IOException)
286 {
287 // File locked. Place item last and try again.
288 // Note: This can happen when an item event is
289 // registered at the same time as a bucket
290 // collection time elapses and a block is generated.
291
292 this.eventQueue.QueueLast(Item);
293 await Task.Delay(10); // Small delay to avoid spam and 100% CPU while file is saved.
294
295 continue;
296 }
297
298 this.buckets.Add(BucketName, Bucket);
299
300 string[] Collections = null;
301
302 lock (this.collections)
303 {
304 if (!this.collections.ContainsKey(CollectionName))
305 {
306 this.collections[CollectionName] = true;
307
308 Collections = new string[this.collections.Count];
309 this.collections.Keys.CopyTo(Collections, 0);
310 }
311 }
312
313 if (!(Collections is null))
314 {
315 StringBuilder sb = new StringBuilder();
316
317 foreach (string Collection in Collections)
318 sb.AppendLine(Collection);
319
320 await RuntimeSettings.SetAsync("NL.Collections", sb.ToString());
321 }
322 }
323
324 if (await Bucket.WriteEntry(EntryType, Binary) > this.maxBlockSize)
325 this.buckets.Remove(BucketName); // Triggers removal event which will add bucket to queue to make it into a block.
326 }
327 }
328 else if (!Bucket.HasEntries)
329 await Bucket.Delete();
330 else
331 {
332 // Generate new block from contents in bucket
333
334 byte[] Digest = Bucket.Hash(this.hashFunction);
335 byte[] Signature = Bucket.Sign(this.signatureAlgorithm);
336 ulong Bytes = (ulong)Bucket.Length;
337
338 Digest = this.CalcCombinationDigest(Digest, Signature);
339
340 string FileName = this.GetFullFileName(Bucket.Header, Digest);
341
342 using (ICryptoTransform Aes = this.GetAes(FileName, true))
343 {
344 using FileStream fs = File.Create(FileName);
345 using CryptoStream cs = new CryptoStream(fs, Aes, CryptoStreamMode.Write);
346
347 await Bucket.CopyTo(cs, Signature);
348
349 cs.FlushFinalBlock();
350 await Bucket.Delete();
351 }
352
353 string LocalFileName = this.GetLocalFileName(FileName);
354
355 BlockReference Ref = new BlockReference(Bucket.Header, LocalFileName, Digest, Signature, Bytes);
356 await this.AddBlockReference(Ref);
357 }
358
359 DateTime Now = DateTime.Now;
360 if (Now >= NextCheck)
361 {
362 NextCheck = Now.AddHours(1);
363 await this.DeleteExpiredBlocks(Now);
364 }
365 }
366 catch (Exception ex)
367 {
368 Log.Exception(ex);
369 }
370 }
371 }
372 catch (Exception ex)
373 {
374 Log.Exception(ex);
375 }
376 finally
377 {
378 this.completed.TrySetResult(true);
379 }
380 }
381
388 private byte[] CalcCombinationDigest(byte[] ContentDigest, byte[] Signature)
389 {
390 int c = ContentDigest.Length;
391 int d = Signature.Length;
392 byte[] Bin = new byte[c + d];
393
394 Buffer.BlockCopy(ContentDigest, 0, Bin, 0, c);
395 Buffer.BlockCopy(Signature, 0, Bin, c, d);
396
397 using MemoryStream ms = new MemoryStream();
398
399 ms.Write(ContentDigest, 0, ContentDigest.Length);
400 ms.Write(Signature, 0, Signature.Length);
401
402 ms.Position = 0;
403
404 return this.hashFunction(ms);
405 }
406
413 private string GetFullFileName(BlockHeader Header, byte[] Digest)
414 {
415 string FileName = Path.Combine(this.blockFolder,
416 Header.Created.Year.ToString("D4"),
417 Header.Created.Month.ToString("D2"),
418 Header.Created.Day.ToString("D2"));
419
420 if (!Directory.Exists(FileName))
421 Directory.CreateDirectory(FileName);
422
423 return Path.Combine(FileName, Hashes.BinaryToString(Digest)) + ".block";
424 }
425
431 private string GetLocalFileName(string FullFileName)
432 {
433 string LocalFileName;
434
435 if (FullFileName.StartsWith(this.blockFolder))
436 LocalFileName = FullFileName[this.blockFolder.Length..];
437 else
438 LocalFileName = FullFileName;
439
440 return LocalFileName;
441 }
442
448 public async Task AddBlockFile(Stream File, BlockReference BlockReference)
449 {
450 File.Position = 0;
451 BlockReader Reader = await BlockReader.CreateAsync(File, this);
452
453 File.Position = 0;
454 byte[] Digest = this.hashFunction(File);
455
456 File.Position = 0;
457 byte[] Signature = this.signatureAlgorithm.Sign(File, false);
458
459 // TODO: Validate signature
460
461 Digest = this.CalcCombinationDigest(Digest, Signature);
462
463 string FileName = this.GetFullFileName(Reader.Header, Digest);
464
465 using (ICryptoTransform Aes = this.GetAes(FileName, true))
466 {
467 using FileStream fs = System.IO.File.Create(FileName);
468 using CryptoStream cs = new CryptoStream(fs, Aes, CryptoStreamMode.Write);
469
470 File.Position = 0;
471 File.CopyTo(cs);
472 cs.FlushFinalBlock();
473 }
474
475 FileName = this.GetLocalFileName(FileName);
476
477 if (BlockReference.FileName != FileName && !string.IsNullOrEmpty(FileName))
478 {
479 BlockReference.FileName = FileName;
481 }
482
483 await this.BlockAdded.Raise(this, new BlockReferenceEventArgs(BlockReference));
484 }
485
489 public event EventHandlerAsync<BlockReferenceEventArgs> BlockAdded = null;
490
491 private async Task AddBlockReference(BlockReference Block)
492 {
493 try
494 {
495 await Database.Insert(Block);
496 await this.BlockAdded.Raise(this, new BlockReferenceEventArgs(Block));
497 }
498 catch (Exception ex)
499 {
500 Log.Exception(ex);
501 }
502 }
503
509 public string GetFullFileName(string LocalFileName)
510 {
511 if (string.IsNullOrEmpty(LocalFileName))
512 return string.Empty;
513 else if (Path.IsPathRooted(LocalFileName))
514 return LocalFileName;
515 else
516 return Path.Combine(this.blockFolder, LocalFileName);
517 }
518
519 private async Task DeleteExpiredBlocks(DateTime Now)
520 {
521 try
522 {
523 Dictionary<string, bool> Directories = null;
524 string FileName;
525
526 foreach (BlockReference Ref in await Database.Find<BlockReference>(new FilterFieldLesserOrEqualTo("Expires", Now)))
527 {
528 FileName = this.GetFullFileName(Ref.FileName);
529
530 if (File.Exists(FileName))
531 {
532 try
533 {
534 File.Delete(FileName);
535
536 Directories ??= new Dictionary<string, bool>();
537
538 string Folder = Path.GetDirectoryName(FileName);
539
540 Directories[Folder] = true;
541 }
542 catch (Exception ex)
543 {
544 Log.Error("Unable to delete block.",
545 new KeyValuePair<string, object>("FileName", FileName),
546 new KeyValuePair<string, object>("Message", ex.Message));
547
548 continue;
549 }
550 }
551
552 await this.DeleteBlockReference(Ref);
553 }
554
555 if (!(Directories is null))
556 {
557 foreach (string Key in Directories.Keys)
558 {
559 string FolderName = Key;
560
561 while (Directory.GetFiles(FolderName, "*.*", SearchOption.TopDirectoryOnly).Length == 0 &&
562 Directory.GetDirectories(FolderName, "*.*", SearchOption.TopDirectoryOnly).Length == 0)
563 {
564 try
565 {
566 Directory.Delete(FolderName, false);
567
568 int i = FolderName.LastIndexOf(Path.DirectorySeparatorChar);
569 if (i < 0)
570 break;
571 else
572 FolderName = FolderName[..i];
573 }
574 catch (Exception ex)
575 {
576 Log.Error("Unable to delete folder.",
577 new KeyValuePair<string, object>("Folder", FolderName),
578 new KeyValuePair<string, object>("Message", ex.Message));
579 break;
580 }
581 }
582 }
583 }
584 }
585 catch (Exception ex)
586 {
587 Log.Exception(ex);
588 }
589 }
590
594 public event EventHandlerAsync<BlockReferenceEventArgs> BlockDeleted = null;
595
596 private async Task DeleteBlockReference(BlockReference Block)
597 {
598 try
599 {
600 await Database.Delete(Block);
601 await this.BlockDeleted.Raise(this, new BlockReferenceEventArgs(Block));
602 }
603 catch (Exception ex)
604 {
605 Log.Exception(ex);
606 }
607 }
608
609 #region IDisposable
610
614 public void Dispose()
615 {
616 if (!this.disposed)
617 {
618 this.disposed = true;
619 this.eventQueue.Dispose();
620 }
621 }
622
623 #endregion
624
625 #region ISerializerContext
626
631 public string Id => this.id;
632
636 public string DefaultCollectionName => this.defaultCollectionName;
637
642 {
643 get
644 {
645 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
646 }
647 }
648
652 public bool Debug => this.debug;
653
659 public bool NormalizedNames => false;
660
664 public string ExternalIdentity => this.externalIdentity;
665
672 public Task<ulong> GetFieldCode(string Collection, string FieldName)
673 {
674 throw new NotSupportedException("Field codes not used.");
675 }
676
684 public Task<string> GetFieldName(string Collection, ulong FieldCode)
685 {
686 throw new NotSupportedException("Field codes not used.");
687 }
688
694 public Task<IObjectSerializer> GetObjectSerializer(Type Type)
695 {
696 if (this.serializers is null)
697 throw new ObjectDisposedException("Service is closing down.");
698
699 return this.serializers.GetObjectSerializer(Type);
700 }
701
707 public Task<IObjectSerializer> GetObjectSerializerNoCreate(Type Type)
708 {
709 if (this.serializers is null)
710 throw new ObjectDisposedException("Service is closing down.");
711
712 return this.serializers.GetObjectSerializerNoCreate(Type);
713 }
714
720 public Task<ObjectSerializer> GetObjectSerializerEx(object Object)
721 {
722 return this.GetObjectSerializerEx(Object.GetType());
723 }
724
730 public async Task<ObjectSerializer> GetObjectSerializerEx(Type Type)
731 {
732 if (!(await this.GetObjectSerializer(Type) is ObjectSerializer Serializer))
733 throw new Exception("Objects of type " + Type.FullName + " must be embedded.");
734
735 return Serializer;
736 }
737
742 public Guid CreateGuid()
743 {
744 return Guid.NewGuid();
745 }
746
753 public Task<Guid> SaveNewObject(object Value, object State)
754 {
755 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
756 }
757
765 public Task<T> TryLoadObject<T>(Guid ObjectId, EmbeddedObjectSetter EmbeddedSetter)
766 where T : class
767 {
768 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
769 }
770
778 public Task<object> TryLoadObject(Type T, Guid ObjectId, EmbeddedObjectSetter EmbeddedSetter)
779 {
780 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
781 }
782
794 public Task<byte[]> Encrypt(byte[] Data, string Property, string Collection, Guid ObjectId,
795 int MinLength)
796 {
797 return Task.FromResult(new byte[MinLength]);
798 }
799
808 public Task<byte[]> Decrypt(byte[] Data, string Property, string Collection, Guid ObjectId)
809 {
810 return Task.FromResult(Data);
811 }
812
813 #endregion
814
819 public Task NewEntry(object Object)
820 {
821 this.Archive(EntryType.New, Object);
822 return Task.CompletedTask;
823 }
824
829 public Task UpdatedEntry(object Object)
830 {
831 this.Archive(EntryType.Update, Object);
832 return Task.CompletedTask;
833
834 // TODO: Update strategies
835 }
836
841 public Task DeletedEntry(object Object)
842 {
843 this.Archive(EntryType.Delete, Object);
844 return Task.CompletedTask;
845
846 // TODO: Delete strategies
847 }
848
853 public Task ClearedCollection(string Collection)
854 {
855 // TODO: Archive clear event.
856 // TODO: Clear strategies
857
858 return Task.CompletedTask;
859 }
860
861 private void Archive(EntryType EntryType, object Object)
862 {
863 if (this.disposed)
864 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
865
866 if (Object is null)
867 return;
868
869 if (!this.stopped)
870 {
871 // No assigned bucket and Object not null serializes object into bucket
872 // corresponding to object's collection and archiving time.
873
874 this.eventQueue.QueueLast(new WorkItem()
875 {
876 Bucket = null,
877 Type = EntryType,
878 Object = Object
879 });
880 }
881 }
882
888 public async Task<ILedgerEnumerator<T>> GetEnumerator<T>()
889 {
890 ObjectSerializer Serializer = await this.GetObjectSerializerEx(typeof(T));
891 string CollectionName = await Serializer.CollectionName(null);
893 return await ObjectEnumerator<T>.Create(BlockEnumerator, this);
894 }
895
901 public async Task<ILedgerEnumerator<object>> GetEnumerator(string CollectionName)
902 {
905 }
906
911 public async Task<PaginatedEnumerator<BlockReference>> GetBlockEnumerator(bool Ascending)
912 {
913 return await Database.Enumerate<BlockReference>(
914 BlockPageSize, Ascending ? "Created" : "-Created");
915 }
916
924 public async Task<PaginatedEnumerator<BlockReference>> GetCollectionBlockEnumerator(string Collection, bool Ascending)
925 {
926 return await Database.Enumerate<BlockReference>(
927 BlockPageSize, new FilterFieldEqualTo("Collection", Collection),
928 Ascending ? "Created" : "-Created");
929 }
930
938 public async Task<PaginatedEnumerator<BlockReference>> GetCreatorBlockEnumerator(string Creator, bool Ascending)
939 {
940 return await Database.Enumerate<BlockReference>(
941 BlockPageSize, new FilterAnd(
942 new FilterFieldEqualTo("Creator", Creator),
943 new FilterFieldEqualTo("AccessDenied", false)),
944 Ascending ? "Created" : "-Created");
945 }
946
950 public async Task Start()
951 {
952 if (this.disposed)
953 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
954
955 this.stopped = false;
956 this.completed = new TaskCompletionSource<bool>();
957
958 string[] Files = Directory.GetFiles(this.bucketFolder, "*.bin", SearchOption.TopDirectoryOnly);
959 foreach (string FileName in Files)
960 {
961 try
962 {
963 string BucketName = Path.GetFileName(FileName);
964 BucketName = BucketName[..^4];
965
966 int i = BucketName.LastIndexOf('.');
967 if (i < 0)
968 {
969 File.Delete(FileName);
970 continue;
971 }
972
973 string Suffix = BucketName[(i + 1)..];
974 string CollectionName = BucketName[..i];
975 int MaxDays = (int)(DateTime.MaxValue - DateTime.UtcNow).TotalDays;
976 DateTime Expires;
977
978 if (int.TryParse(Suffix, out int NrDays) && NrDays < MaxDays)
979 Expires = DateTime.UtcNow.AddDays(NrDays);
980 else
981 Expires = DateTime.MaxValue;
982
983 Bucket Bucket = await Bucket.Create(FileName, CollectionName, Expires, this);
984
985 if (Bucket.HasEntries)
986 this.buckets.Add(BucketName, Bucket);
987 else
988 await Bucket.Delete();
989 }
990 catch (Exception ex)
991 {
992 Log.Exception(ex);
993 }
994 }
995
996 Task _ = Task.Run(() => this.StorageTask());
997 }
998
1002 public async Task Stop()
1003 {
1004 if (this.disposed)
1005 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
1006
1007 this.stopped = true;
1008
1009 this.eventQueue.Disposed += async (Sender, e) =>
1010 {
1011 this.buckets?.Clear();
1012 this.buckets?.Dispose();
1013 this.buckets = null;
1014
1015 if (!(this.serializers is null))
1016 {
1017 await this.serializers.DisposeAsync();
1018 this.serializers = null;
1019 }
1020
1021 this.aes?.Dispose();
1022 this.aes = null;
1023 };
1024
1025 await this.eventQueue.Terminate();
1026 await this.completed.Task;
1027 }
1028
1032 public Task Flush()
1033 {
1034 if (this.disposed)
1035 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
1036
1037 this.buckets?.Clear();
1038 return Task.CompletedTask;
1039 }
1040
1041 internal ICryptoTransform GetAes(string FileName, bool Encryptor)
1042 {
1043 byte[] BinFileName = Encoding.UTF8.GetBytes(FileName);
1044 int c = BinFileName.Length;
1045 byte[] Concat = new byte[c + this.saltLength];
1046
1047 Buffer.BlockCopy(BinFileName, 0, Concat, 0, c);
1048 Buffer.BlockCopy(this.salt, 0, Concat, c, this.saltLength);
1049
1050 byte[] Key = Hashes.ComputeSHA256Hash(Concat);
1051 byte[] IV = Hashes.ComputeHMACSHA256Hash(Key, BinFileName);
1052
1053 Array.Resize(ref IV, 16);
1054
1055 if (Encryptor)
1056 return this.aes.CreateEncryptor(Key, IV);
1057 else
1058 return this.aes.CreateDecryptor(Key, IV);
1059 }
1060
1066 public static Task<BlockReference> FindReference(byte[] Digest)
1067 {
1068 return Database.FindFirstIgnoreRest<BlockReference>(new FilterFieldEqualTo("Digest", Digest));
1069 }
1070
1074 public async Task RepairRegistry()
1075 {
1076 int NrAdded = 0;
1077 int NrUpdated = 0;
1078 int NrDeleted = 0;
1079
1080 Log.Warning("Block Registry collection repaired during start-up. Scanning existing blocks in ledger to make sure block registry is up to date.");
1081
1082 await this.RepairRegistry(
1083 null,
1084 (Sender, e) =>
1085 {
1086 NrAdded++;
1087 return Task.CompletedTask;
1088 },
1089 (Sender, e) =>
1090 {
1091 NrUpdated++;
1092 return Task.CompletedTask;
1093 },
1094 (Sender, e) =>
1095 {
1096 NrDeleted++;
1097 return Task.CompletedTask;
1098 });
1099
1100 if (NrAdded == 0 && NrUpdated == 0 && NrDeleted == 0)
1101 {
1102 Log.Notice("Block Registry OK. Nothing to repair.",
1103 new KeyValuePair<string, object>("NrAdded", NrAdded),
1104 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1105 new KeyValuePair<string, object>("NrDeleted", NrDeleted));
1106 }
1107 else
1108 {
1109 Log.Warning("Block Registry repaired, based on blocks available in ledger.",
1110 new KeyValuePair<string, object>("NrAdded", NrAdded),
1111 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1112 new KeyValuePair<string, object>("NrDeleted", NrDeleted));
1113 }
1114 }
1115
1123 public async Task RepairRegistry(
1124 EventHandlerAsync<BlockReferenceEventArgs> NoChangeCallback,
1125 EventHandlerAsync<BlockReferenceEventArgs> AddedCallback,
1126 EventHandlerAsync<BlockReferenceEventArgs> UpdatedCallback,
1127 EventHandlerAsync<BlockReferenceEventArgs> DeletedCallback)
1128 {
1129 // First, scan blocks and recreate missing block reference objects.
1130
1131 IEnumerable<string> Files = Directory.EnumerateFiles(this.blockFolder, "*.block", SearchOption.AllDirectories);
1132
1133 foreach (string BlockFile in Files)
1134 {
1135 try
1136 {
1137 BlockReader Reader = await BlockReader.CreateAsync(BlockFile, this);
1138 string LocalFileName;
1139
1140 if (BlockFile.StartsWith(this.blockFolder))
1141 LocalFileName = BlockFile[this.blockFolder.Length..];
1142 else
1143 LocalFileName = BlockFile;
1144
1145 string s = Path.GetFileName(LocalFileName);
1146 s = s[..^6];
1147 byte[] Digest = Hashes.StringToBinary(s);
1148 if (Digest is null)
1149 continue;
1150
1151 BlockReference Ref = await FindReference(Digest);
1152 int i;
1153
1154 if (Ref is null)
1155 {
1156 Ref = new BlockReference(Reader.Header, LocalFileName, Digest, Reader.Signature, Reader.Bytes);
1157 await Database.Provider.Insert(Ref);
1158
1159 await AddedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1160 }
1161 else if ((i = this.Compare(Ref, Reader, LocalFileName, Digest)) != 0)
1162 {
1163 Ref.Creator = Reader.Header.Creator;
1164 Ref.Created = Reader.Header.Created;
1165 Ref.Updated = Reader.Header.Updated;
1166 Ref.Expires = Reader.Header.Expires;
1167 Ref.Status = Reader.Header.Status;
1168 Ref.Link = Reader.Header.Link;
1169 Ref.FileName = LocalFileName;
1170 Ref.Collection = Reader.CollectionName;
1171 Ref.Bytes = Reader.Bytes;
1172 Ref.Digest = (byte[])Digest.Clone();
1173 Ref.Signature = (byte[])Reader.Signature.Clone();
1174
1175 await Database.Provider.Update(Ref);
1176
1177 await UpdatedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1178 }
1179 else
1180 await NoChangeCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1181 }
1182 catch (Exception ex)
1183 {
1184 Log.Error("Unable to process block file.",
1185 new KeyValuePair<string, object>("FileName", BlockFile),
1186 new KeyValuePair<string, object>("Message", ex.Message));
1187 }
1188 }
1189
1190 // Second, scan block reference objects and remove obsolete reference objects.
1191
1192 string LastObjectId = null;
1193 const int Max = 1000;
1194 int Count;
1195
1196 do
1197 {
1198 IEnumerable<BlockReference> References;
1199
1200 if (LastObjectId is null)
1201 References = await Database.Find<BlockReference>(0, Max, "ObjectId");
1202 else
1203 References = await Database.Find<BlockReference>(0, Max, new FilterFieldGreaterThan("ObjectId", LastObjectId), "ObjectId");
1204
1205 Count = 0;
1206 foreach (BlockReference Ref in References)
1207 {
1208 Count++;
1209
1210 if (string.IsNullOrEmpty(Ref.FileName))
1211 {
1212 if (Ref.Creator == this.externalIdentity)
1213 {
1214 await Database.Provider.Delete(Ref);
1215
1216 await DeletedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1217 }
1218 else
1219 await NoChangeCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1220 }
1221 else
1222 {
1223 string FileName = this.GetFullFileName(Ref.FileName);
1224
1225 if (!File.Exists(FileName))
1226 {
1227 if (Ref.Creator == this.externalIdentity)
1228 {
1229 await Database.Provider.Delete(Ref);
1230
1231 await DeletedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1232 }
1233 else
1234 {
1235 Ref.FileName = string.Empty;
1236 await Database.Provider.Update(Ref);
1237 await UpdatedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1238 }
1239 }
1240 }
1241
1242 LastObjectId = Ref.ObjectId;
1243 }
1244 }
1245 while (Count >= Max);
1246 }
1247
1248 private int Compare(BlockReference Ref, BlockReader Reader, string LocalFileName, byte[] Digest)
1249 {
1250 if (Ref.Creator != Reader.Header.Creator)
1251 return 1;
1252
1253 if (!AreEqual(Ref.Created, Reader.Header.Created))
1254 return 2;
1255
1256 if (!AreEqual(Ref.Updated, Reader.Header.Updated))
1257 return 3;
1258
1259 if (!AreEqual(Ref.Expires, Reader.Header.Expires))
1260 return 4;
1261
1262 if (Ref.Status != Reader.Header.Status)
1263 return 5;
1264
1265 if (Ref.Link != Reader.Header.Link)
1266 return 6;
1267
1268 if (Ref.FileName != LocalFileName)
1269 return 7;
1270
1271 if (Ref.Collection != Reader.CollectionName)
1272 return 8;
1273
1274 if (Ref.Bytes != Reader.Bytes)
1275 return 9;
1276
1277 if (Convert.ToBase64String(Ref.Digest) != Convert.ToBase64String(Digest))
1278 return 10;
1279
1280 if (Convert.ToBase64String(Ref.Signature) != Convert.ToBase64String(Reader.Signature))
1281 return 11;
1282
1283 return 0;
1284 }
1285
1292 public static bool AreEqual(DateTime TP1, DateTime TP2)
1293 {
1294 return
1295 TP1.Millisecond == TP2.Millisecond &&
1296 TP1.Second == TP2.Second &&
1297 TP1.Minute == TP2.Minute &&
1298 TP1.Hour == TP2.Hour &&
1299 TP1.Day == TP2.Day &&
1300 TP1.Month == TP2.Month &&
1301 TP1.Year == TP2.Year &&
1302 TP1.Kind == TP2.Kind;
1303 }
1304
1309 public async Task RepairCollection(string CollectionName)
1310 {
1311 PaginatedEnumerator<BlockReference> Blocks = await this.GetCollectionBlockEnumerator(CollectionName, false);
1312 CachedStringDictionary Dictionary = null;
1313 List<ObjectState> ObjectsInBlock = new List<ObjectState>();
1314 DateTime Start = DateTime.Now;
1315 int Count = 0;
1316 bool Cleared = false;
1317 uint NrAdded = 0;
1318 uint NrUpdated = 0;
1319 uint NrDeleted = 0;
1320 uint NrErrors = 0;
1321
1322 try
1323 {
1324 bool Started = true;
1325
1326 await Database.StartBulk();
1327 try
1328 {
1329 while (await Blocks.MoveNextAsync())
1330 {
1331 BlockReference Ref = Blocks.Current;
1332 if (Ref.Status != BlockStatus.Valid || Ref.AccessDenied)
1333 continue;
1334
1335 if (Dictionary is null)
1336 {
1337 IPersistentDictionary PersistentDictionary = await Database.GetDictionary(CollectionName);
1338 await PersistentDictionary.ClearAsync();
1339
1340 Dictionary = new CachedStringDictionary(100000, PersistentDictionary);
1341
1342 Log.Warning(CollectionName + " collection repaired during start-up. Scanning existing blocks in ledger to make sure collection is up to date.");
1343 }
1344
1345 using (BlockEnumerator TempBlockEnumerator = new BlockEnumerator(Ref, this))
1346 {
1347 using (ObjectEnumerator<GenericObject> e = await ObjectEnumerator<GenericObject>.Create(TempBlockEnumerator, this))
1348 {
1349 while (await e.MoveNextAsync())
1350 ObjectsInBlock.Add(new ObjectState(e.CurrentEntry.Type, e.Current));
1351 }
1352
1353 ObjectsInBlock.Reverse();
1354
1355 Exception FirstException = null;
1356
1357 foreach (ObjectState ObjectState in ObjectsInBlock)
1358 {
1359 if (ObjectState.Type == EntryType.Clear)
1360 {
1361 Cleared = true;
1362 break;
1363 }
1364
1365 try
1366 {
1367 string Key = ObjectState.Object.ObjectId.ToString();
1368
1369 if (await Dictionary.ContainsKeyAsync(Key))
1370 continue; // Only latest is of importance.
1371
1372 await Dictionary.AddAsync(Key, ObjectState);
1373 }
1374 catch (Exception ex)
1375 {
1376 FirstException ??= ex;
1377 break;
1378 }
1379
1380 if (++Count >= 100)
1381 {
1382 await Database.EndBulk();
1383 Started = false;
1384
1385 await Database.StartBulk();
1386 Started = true;
1387 Count = 0;
1388 }
1389 }
1390
1391 if (!(FirstException is null))
1392 {
1393 Log.Error("Unable to enumerate objects in block properly when repairing collection:\r\n\r\n" +
1394 FirstException.Message, TempBlockEnumerator.Current.FileName, string.Empty, string.Empty,
1395 EventLevel.Major, string.Empty, string.Empty, Log.CleanStackTrace(FirstException.StackTrace),
1396 new KeyValuePair<string, object>("Collection", CollectionName));
1397 }
1398
1399 ObjectsInBlock.Clear();
1400 }
1401
1402 if (Cleared)
1403 break;
1404 }
1405
1406 if (Cleared)
1407 await Database.Provider.Clear(CollectionName);
1408 }
1409 finally
1410 {
1411 if (Started)
1412 await Database.EndBulk();
1413 }
1414
1415 if (!(Dictionary is null))
1416 {
1417 Tuple<uint, uint, uint, uint> Counts = await this.Process(Dictionary, CollectionName);
1418
1419 NrAdded = Counts.Item1;
1420 NrUpdated = Counts.Item2;
1421 NrDeleted = Counts.Item3;
1422 NrErrors = Counts.Item4;
1423 }
1424 }
1425 finally
1426 {
1427 await Blocks.DisposeAsync();
1428
1429 if (!(Dictionary is null))
1430 {
1431 await Dictionary.ClearAsync();
1432 Dictionary.DeleteAndDispose();
1433 }
1434 }
1435
1436 TimeSpan Elapsed = DateTime.Now - Start;
1437
1438 if (NrAdded == 0 && NrUpdated == 0 && NrDeleted == 0 && NrErrors == 0)
1439 {
1440 Log.Notice("Collection OK. Nothing to repair.",
1441 new KeyValuePair<string, object>("CollectionName", CollectionName),
1442 new KeyValuePair<string, object>("NrAdded", NrAdded),
1443 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1444 new KeyValuePair<string, object>("NrDeleted", NrDeleted),
1445 new KeyValuePair<string, object>("NrErrors", NrErrors),
1446 new KeyValuePair<string, object>("Time", Elapsed.ToString()));
1447 }
1448 else
1449 {
1450 Log.Alert("Collection repaired, based on blocks available in ledger.",
1451 new KeyValuePair<string, object>("CollectionName", CollectionName),
1452 new KeyValuePair<string, object>("NrAdded", NrAdded),
1453 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1454 new KeyValuePair<string, object>("NrDeleted", NrDeleted),
1455 new KeyValuePair<string, object>("NrErrors", NrErrors),
1456 new KeyValuePair<string, object>("Time", Elapsed.ToString()));
1457 }
1458 }
1459
1467 public async Task<Tuple<uint, uint, uint, uint>> Process(CachedStringDictionary Records, string CollectionName)
1468 {
1469 uint NrAdded = 0;
1470 uint NrUpdated = 0;
1471 uint NrDeleted = 0;
1472 uint NrErrors = 0;
1473
1474 await Database.StartBulk();
1475 try
1476 {
1477 IEnumerator<KeyValuePair<string, object>> e = await Records.GetEnumeratorAsync();
1478 try
1479 {
1480 int Count = 0;
1481
1482 if (!(e is IAsyncEnumerator eAsync))
1483 eAsync = new PseudoAsyncEnumerator(e);
1484
1485 while (await eAsync.MoveNextAsync())
1486 {
1487 if (!(e.Current.Value is ObjectState ObjectState))
1488 continue;
1489
1490 try
1491 {
1492 switch (ObjectState.Type)
1493 {
1494 // Note: Use of Database.Provider avoids generating events that result in the creation of new blocks.
1495
1496 case EntryType.New:
1497 case EntryType.Update:
1499 if (Obj2 is null)
1500 {
1502 NrAdded++;
1503 }
1504 else
1505 {
1506 if (Obj2.Equals(ObjectState.Object))
1507 continue;
1508
1510 NrUpdated++;
1511 }
1512 break;
1513
1514 case EntryType.Delete:
1516 if (Obj2 is null)
1517 continue;
1518 else
1519 {
1520 await Database.Provider.Delete(Obj2);
1521 NrDeleted++;
1522 }
1523 break;
1524
1525 default:
1526 continue;
1527 }
1528
1529 if (++Count >= 100)
1530 {
1531 await Database.EndBulk();
1532 await Database.StartBulk();
1533 Count = 0;
1534 }
1535 }
1536 catch (Exception ex)
1537 {
1538 Log.Exception(ex);
1539 NrErrors++;
1540 }
1541 }
1542 }
1543 finally
1544 {
1545 if (e is IDisposableAsync DisposableAsync)
1546 await DisposableAsync.DisposeAsync();
1547 else if (e is IDisposable Disposable)
1548 Disposable.Dispose();
1549 }
1550 }
1551 finally
1552 {
1553 await Database.EndBulk();
1554 }
1555
1556 return new Tuple<uint, uint, uint, uint>(NrAdded, NrUpdated, NrDeleted, NrErrors);
1557 }
1558
1559 private class PseudoAsyncEnumerator : IAsyncEnumerator
1560 {
1561 private readonly IEnumerator e;
1562
1563 public PseudoAsyncEnumerator(IEnumerator e)
1564 {
1565 this.e = e;
1566 }
1567
1568 public object Current => this.e.Current;
1569 public bool MoveNext() => this.e.MoveNext();
1570 public Task<bool> MoveNextAsync() => Task.FromResult(this.e.MoveNext());
1571 public void Reset() => this.e.Reset();
1572 }
1573
1577 public string[] Collections
1578 {
1579 get
1580 {
1581 string[] Result;
1582
1583 lock (this.collections)
1584 {
1585 Result = new string[this.collections.Count];
1586 this.collections.Keys.CopyTo(Result, 0);
1587 }
1588
1589 return Result;
1590 }
1591 }
1592
1597 public Task<string[]> GetCollections()
1598 {
1599 return Task.FromResult<string[]>(this.Collections);
1600 }
1601
1609 public Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction)
1610 {
1611 return this.Export(Output, Restriction, null);
1612 }
1613
1622 public async Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction, ProfilerThread Thread)
1623 {
1624 Thread?.Start();
1625
1626 if (!await Output.StartLedger(this))
1627 return false;
1628
1629 bool Continue;
1630
1631 try
1632 {
1633 string[] Collections = Restriction?.CollectionNames ?? await Ledger.GetCollections();
1634
1635 foreach (string Collection in Collections)
1636 {
1637 Thread?.NewState(Collection);
1638 if (!await Output.StartCollection(Collection))
1639 return false;
1640 try
1641 {
1642 string[] BlockIds = Restriction?.BlockIds ?? new string[] { null };
1643
1644 foreach (string BlockId in BlockIds)
1645 {
1646 string[] Creators = Restriction?.Creators ?? new string[] { null };
1647
1648 foreach (string Creator in Creators)
1649 {
1650 List<Filter> Filters = new List<Filter>()
1651 {
1652 new FilterFieldEqualTo("Collection", Collection)
1653 };
1654
1655 if (!string.IsNullOrEmpty(BlockId))
1656 Filters.Add(new FilterFieldEqualTo("ObjectId", BlockId));
1657
1658 if (!string.IsNullOrEmpty(Creator))
1659 Filters.Add(new FilterFieldEqualTo("Creator", Creator));
1660
1661 if (Restriction.MinCreated.HasValue)
1662 {
1663 if (Restriction.MinCreatedIncluded)
1664 Filters.Add(new FilterFieldGreaterOrEqualTo("Created", Restriction.MinCreated.Value));
1665 else
1666 Filters.Add(new FilterFieldGreaterThan("Created", Restriction.MinCreated.Value));
1667 }
1668
1669 if (Restriction.MaxCreated.HasValue)
1670 {
1671 if (Restriction.MaxCreatedIncluded)
1672 Filters.Add(new FilterFieldLesserOrEqualTo("Created", Restriction.MaxCreated.Value));
1673 else
1674 Filters.Add(new FilterFieldLesserThan("Created", Restriction.MaxCreated.Value));
1675 }
1676
1677 Filter Filter;
1678
1679 if (Filters.Count > 1)
1680 Filter = new FilterAnd(Filters.ToArray());
1681 else
1682 Filter = Filters[0];
1683
1684 using PaginatedEnumerator<BlockReference> Blocks =
1685 await Database.Enumerate<BlockReference>(BlockPageSize, Filter, "Created");
1686
1687 while (await Blocks.MoveNextAsync())
1688 {
1689 BlockReference Ref = Blocks.Current;
1690 if (Ref.Status != BlockStatus.Valid || Ref.AccessDenied)
1691 continue;
1692
1693 await Output.StartBlock(Ref.ObjectId);
1694 try
1695 {
1696 if (!await Output.BlockMetaData("Bytes", Ref.Bytes))
1697 return false;
1698
1699 if (!await Output.BlockMetaData("Created", Ref.Created))
1700 return false;
1701
1702 if (!await Output.BlockMetaData("Creator", Ref.Creator))
1703 return false;
1704
1705 if (!await Output.BlockMetaData("Digest", Ref.Digest))
1706 return false;
1707
1708 if (!await Output.BlockMetaData("Expires", Ref.Expires))
1709 return false;
1710
1711 if (!await Output.BlockMetaData("FileName", Ref.FileName))
1712 return false;
1713
1714 if (!await Output.BlockMetaData("Signature", Ref.Signature))
1715 return false;
1716
1717 using BlockEnumerator TempBlockEnumerator = new BlockEnumerator(Blocks.Current, this);
1718 using ObjectEnumerator<GenericObject> e = await ObjectEnumerator<GenericObject>.Create(TempBlockEnumerator, this);
1719 GenericObject Obj;
1720
1721 while (await e.MoveNextAsync())
1722 {
1723 Obj = e.Current;
1724
1725 if (!await Output.StartEntry(Obj.ObjectId.ToString(), Obj.TypeName, e.CurrentEntry.Type, e.CurrentEntry.Timestamp))
1726 return false;
1727 try
1728 {
1729 foreach (KeyValuePair<string, object> P in Obj)
1730 {
1731 if (!await Output.ReportProperty(P.Key, P.Value))
1732 return false;
1733 }
1734 }
1735 catch (Exception ex)
1736 {
1737 Thread?.Exception(ex);
1738 if (!await this.ReportException(ex, Output))
1739 return false;
1740 }
1741 finally
1742 {
1743 Continue = await Output.EndEntry();
1744 }
1745
1746 if (!Continue)
1747 return false;
1748 }
1749 }
1750 finally
1751 {
1752 Continue = await Output.EndBlock();
1753 }
1754
1755 if (!Continue)
1756 return false;
1757 }
1758 }
1759 }
1760 }
1761 catch (Exception ex)
1762 {
1763 Thread?.Exception(ex);
1764 if (!await this.ReportException(ex, Output))
1765 return false;
1766 }
1767 finally
1768 {
1769 Continue = await Output.EndCollection();
1770 }
1771
1772 if (!Continue)
1773 return false;
1774 }
1775 }
1776 catch (Exception ex)
1777 {
1778 Thread?.Exception(ex);
1779 if (!await this.ReportException(ex, Output))
1780 return false;
1781 }
1782 finally
1783 {
1784 Continue = await Output.EndLedger();
1785
1786 Thread?.Idle();
1787 Thread?.Stop();
1788 }
1789
1790 return Continue;
1791 }
1792
1793 private async Task<bool> ReportException(Exception ex, ILedgerExport Output)
1794 {
1795 ex = Log.UnnestException(ex);
1796
1797 if (ex is AggregateException ex2)
1798 {
1799 foreach (Exception ex3 in ex2.InnerExceptions)
1800 {
1801 if (!await Output.ReportException(ex3))
1802 return false;
1803 }
1804
1805 return true;
1806 }
1807 else
1808 return await Output.ReportException(ex);
1809 }
1810
1817 {
1818 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
1819 throw new Exception("An interface for external events has already been registered.");
1820
1821 this.externalEvents = ExternalEvents;
1822 }
1823
1830 {
1831 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
1832 throw new Exception("The registered interface for external events differs from the one presented.");
1833
1834 this.externalEvents = null;
1835 }
1836
1841 {
1842 get
1843 {
1844 neuroLedgerClientType ??= Types.GetType("Waher.Networking.XMPP.NeuroLedger.NeuroLedgerClient");
1845 Assert.CallFromSource(new ApproveType(neuroLedgerClientType));
1846 return this.externalEvents;
1847 }
1848 }
1849
1850 private static Type neuroLedgerClientType = null;
1851
1852 }
1853}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
Definition: Log.cs:194
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 Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:576
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:692
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:460
static void Alert(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an alert event.
Definition: Log.cs:1237
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IPersistentDictionary > GetDictionary(string Collection)
Gets a persistent dictionary containing objects in a collection.
Definition: Database.cs:2307
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:2259
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:59
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:2251
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
static async Task< PaginatedEnumerator< object > > Enumerate(string Collection, int PageSize, params string[] SortOrder)
Finds the first page of objects in a given collection.
Definition: Database.cs:482
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field greater or equal to a given value.
This filter selects objects that have a named field greater than a given value.
This filter selects objects that have a named field lesser or equal to a given value.
This filter selects objects that have a named field lesser than a given value.
Base class for all filter classes.
Definition: Filter.cs:15
Contains basic ledger export restrictions.
string[] Creators
Creators to export. If null, all relevant creators will be exported.
DateTime? MinCreated
Minimum value (if provided) of when a ledger block of information was created.
DateTime? MaxCreated
Maximum value (if provided) of when a ledger block of information was created.
string[] BlockIds
Blocks to export. If null, all relevant blocks will be exported.
bool MaxCreatedIncluded
If MaxCreated is included
string[] CollectionNames
Collections to export. If null, all collections will be exported.
bool MinCreatedIncluded
If MinCreated is included
Static interface for ledger persistence. In order to work, a ledger provider has to be assigned to it...
Definition: Ledger.cs:14
static Task< string[]> GetCollections()
Gets an array of available collections.
Definition: Ledger.cs:225
byte[] Link
Link to updated block (in case Status shows the block has been updated).
Definition: BlockHeader.cs:120
string Creator
Creator of the block.
Definition: BlockHeader.cs:55
BlockStatus Status
Claimed status of block.
Definition: BlockHeader.cs:108
DateTime Created
When the block was created.
Definition: BlockHeader.cs:75
DateTime Expires
When the block expires.
Definition: BlockHeader.cs:97
DateTime Updated
When the block was updated (in case Status shows the block has been updated or deleted).
Definition: BlockHeader.cs:87
static async Task< BlockReader > CreateAsync(string FileName, NeuroLedgerProvider Provider)
Creates a block reader.
Definition: BlockReader.cs:79
ulong Bytes
Size of block, in bytes
Definition: BlockReader.cs:166
byte[] Signature
Signature of block.
Definition: BlockReader.cs:235
Event arguments for block reference events.
Represents the construction of a block file.
Definition: Bucket.cs:18
BlockHeader Header
Block Header
Definition: Bucket.cs:164
long Length
Length of bucket file.
Definition: Bucket.cs:144
static async Task< Bucket > Create(string FileName, string CollectionName, DateTime Expires, NeuroLedgerProvider Provider)
Represents the construction of a block file.
Definition: Bucket.cs:42
bool HasEntries
If entries has been written to the bucket.
Definition: Bucket.cs:159
async Task< long > WriteEntry(EntryType EntryType, byte[] Binary)
Writes an entry to the bucket.
Definition: Bucket.cs:238
byte[] Sign(ISignatureAlgorithm Algorithm)
Signs the contents of the file.
Definition: Bucket.cs:391
async Task CopyTo(Stream Destination, byte[] Signature)
Copies the content of the bucket to an output stream.
Definition: Bucket.cs:402
byte[] Hash(HashFunctionStream HashFunction)
Calculates a hash digest of the contents of the file.
Definition: Bucket.cs:380
async Task Delete()
Deletes the file, and disposes of the object.
Definition: Bucket.cs:211
Optimizes a persistent IPersistentDictionary using a cache.
async Task< IEnumerator< KeyValuePair< string, object > > > GetEnumeratorAsync()
TODO
EntryType Type
Entry Type
Definition: Entry.cs:32
async Task< ObjectSerializer > GetObjectSerializerEx(Type Type)
Gets the object serializer corresponding to a specific object.
async Task< bool > Export(ILedgerExport Output, LedgerExportRestriction Restriction, ProfilerThread Thread)
Performs an export of the entire ledger.
async Task RepairRegistry(EventHandlerAsync< BlockReferenceEventArgs > NoChangeCallback, EventHandlerAsync< BlockReferenceEventArgs > AddedCallback, EventHandlerAsync< BlockReferenceEventArgs > UpdatedCallback, EventHandlerAsync< BlockReferenceEventArgs > DeletedCallback)
Make sure block reference objects match existing blocks.
HashFunctionStream HashFunction
Hash function used for calculating block digests.
Task< IObjectSerializer > GetObjectSerializer(Type Type)
Gets the object serializer corresponding to a specific type.
string[] Collections
Array of collections archived in the ledger.
async Task< ILedgerEnumerator< object > > GetEnumerator(string CollectionName)
Gets an eumerator for objects in a collection.
Task< ulong > GetFieldCode(string Collection, string FieldName)
Gets the code for a specific field in a collection.
EventHandlerAsync< BlockReferenceEventArgs > BlockAdded
Event raised when a new block is added.
string Id
An ID of the serialization context. It's unique, and constant during the life-time of the application...
Task UpdatedEntry(object Object)
Updates an entry in the ledger.
Task< object > TryLoadObject(Type T, Guid ObjectId, EmbeddedObjectSetter EmbeddedSetter)
Tries to load an object given its Object ID ObjectId and its base type T .
Task< string[]> GetCollections()
Gets an array of available collections.
Task NewEntry(object Object)
Adds an entry to the ledger.
async Task AddBlockFile(Stream File, BlockReference BlockReference)
Adds a block file to the ledger.
async Task< PaginatedEnumerator< BlockReference > > GetCollectionBlockEnumerator(string Collection, bool Ascending)
Gets a block enumerator for blocks pertaining to a given collection. Enumerates blocks in order of cr...
static bool AreEqual(DateTime TP1, DateTime TP2)
Compares two timestamps, to the millisecond (but not tick) level.
bool Debug
If the provider is run in debug mode.
NeuroLedgerProvider(string Folder, TimeSpan CollectionTime, int MaxBlockSize, byte[] Salt, string DefaultCollectionName, string ExternalIdentity, ISignatureAlgorithm SignatureAlgorithm, HashFunctionStream HashFunction, bool Debug)
Neuro-Ledger provider.
string GetFullFileName(string LocalFileName)
Gets the full file name of a file hosted by the Neuro-Ledger, given its local file name.
async Task< ILedgerEnumerator< T > > GetEnumerator< T >()
Gets an eumerator for objects of type T .
ILedgerExternalEvents ExternalEvents
Interface for reporting external events.
async Task RepairCollection(string CollectionName)
Repairs a database collection based on contents in the corresponding ledger.
Task< ObjectSerializer > GetObjectSerializerEx(object Object)
Gets the object serializer corresponding to a specific object.
bool NormalizedNames
If normalized names are to be used or not. Normalized names reduces the number of bytes required to s...
ISignatureAlgorithm SignatureAlgorithm
Signature Algorithm used for calculating block signatures.
Task< byte[]> Decrypt(byte[] Data, string Property, string Collection, Guid ObjectId)
Decrypts field data.
Task ClearedCollection(string Collection)
Clears a collection in the ledger.
EventHandlerAsync< BlockReferenceEventArgs > BlockDeleted
Event raised when a block has been deleted.
int TimeoutMilliseconds
Timeout, in milliseconds, for asynchronous operations.
static Task< BlockReference > FindReference(byte[] Digest)
Finds a BlockReference object related to a block, given its digest.
async Task Start()
Called when processing starts.
async Task Stop()
Called when processing ends.
Task< byte[]> Encrypt(byte[] Data, string Property, string Collection, Guid ObjectId, int MinLength)
Encrypts field data.
Task< bool > Export(ILedgerExport Output, LedgerExportRestriction Restriction)
Performs an export of the entire ledger.
async Task< PaginatedEnumerator< BlockReference > > GetCreatorBlockEnumerator(string Creator, bool Ascending)
Gets a block enumerator for blocks pertaining to a given collection. Enumerates blocks in order of cr...
async Task< PaginatedEnumerator< BlockReference > > GetBlockEnumerator(bool Ascending)
Gets a block enumerator. Enumerates blocks in order of creation.
Task< IObjectSerializer > GetObjectSerializerNoCreate(Type Type)
Gets the object serializer corresponding to a specific type, if one exists.
Task< T > TryLoadObject< T >(Guid ObjectId, EmbeddedObjectSetter EmbeddedSetter)
Tries to load an object given its Object ID ObjectId and its base type T .
Task< string > GetFieldName(string Collection, ulong FieldCode)
Gets the name of a field in a collection, given its code.
async Task< Tuple< uint, uint, uint, uint > > Process(CachedStringDictionary Records, string CollectionName)
Processes an ordered set of records containing ObjectState objects in a cached string dictionary (for...
Task DeletedEntry(object Object)
Deletes an entry in the ledger.
void Unregister(ILedgerExternalEvents ExternalEvents)
Unregisters a recipient of external events.
void Register(ILedgerExternalEvents ExternalEvents)
Registers a recipient of external events.
Task< Guid > SaveNewObject(object Value, object State)
Saves an unsaved object, and returns a new GUID identifying the saved object.
async Task RepairRegistry()
Make sure block reference objects match existing blocks.
Enumeratres through objects available in a series of blocks.
T Current
Gets the element in the collection at the current position of the enumerator.
async Task< bool > MoveNextAsync()
Advances the enumerator to the next element of the collection.
static async Task< ObjectEnumerator< T > > Create(IAsyncEnumerator< BlockReference > BlockEnumerator, NeuroLedgerProvider Provider)
Creates an object enumerator from a block enumerator.
Represents an object state.
Definition: ObjectState.cs:11
Contains a reference to a block in the ledger.
bool AccessDenied
If access to the block was denied.
async Task DisposeAsync()
IDisposableAsync.DisposeAsync
async Task< bool > MoveNextAsync()
Moves to next item.
Manages binary serialization of data.
byte[] GetSerialization()
Gets the binary serialization.
Generic object. Contains a sequence of properties.
Serializes a class, taking into account attributes defined in Attributes.
virtual async Task< Guid > GetObjectId(object Value, bool InsertIfNotFound, object State)
Gets the Object ID for a given object.
bool ArchiveObjects
If objects of this type can be archived.
bool ArchiveTimeDynamic
If each object contains the information for how long time it can be archived.
virtual async Task Serialize(ISerializer Writer, bool WriteTypeCode, bool Embedded, object Value, object State)
Serializes a value.
virtual int GetArchivingTimeDays(object Object)
Number of days to archive objects of this type. If equal to int.MaxValue, no limit is defined.
virtual Task< string > CollectionName(object Object)
Name of collection objects of this type is to be stored in, if available. If not available,...
Task< IObjectSerializer > GetObjectSerializerNoCreate(Type Type)
Gets the object serializer corresponding to a specific type, if one exists.
async Task< IObjectSerializer > GetObjectSerializer(Type Type)
Gets the object serializer corresponding to a specific type.
async Task DisposeAsync()
IDisposableAsync.DisposeAsync
Implements an in-memory cache.
Definition: Cache.cs:17
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:616
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:446
void Clear()
Clears the cache.
Definition: Cache.cs:679
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static Type GetType(string FullName)
Gets a type, given its full name.
Definition: Types.cs:42
Class that keeps track of events and timing for one thread.
void Exception(System.Exception Exception)
Exception occurred
void NewState(string State)
Thread changes state.
Asynchronous First-in-First-out (FIFO) Queue, for use when transporting items of type T between task...
Definition: AsyncQueue.cs:16
Represents an asynchronous operation to be performed.
Definition: WorkItem.cs:10
Task< bool > Wait()
Waits for the item to be processed.
Definition: WorkItem.cs:40
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Checks for an approved type in the call stack.
Definition: ApproveType.cs:10
Static class containing methods that can be used to make sure calls are made from appropriate locatio...
Definition: Assert.cs:15
static void CallFromSource(params string[] Sources)
Makes sure the call is made from one of the listed sources.
Definition: Assert.cs:54
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] StringToBinary(string s)
Parses a hex string.
Definition: Hashes.cs:100
static string BinaryToString(byte[] Data)
Converts an array of bytes to a string with their hexadecimal representations (in lower case).
Definition: Hashes.cs:63
static byte[] ComputeHMACSHA256Hash(byte[] Key, byte[] Data)
Computes the HMAC-SHA-256 hash of a block of binary data.
Definition: Hashes.cs:735
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:469
Interface for asynchronously disposable objects.
Interface for asynchronous enumerators.
Task Update(object Object)
Updates an object in the database.
Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Task Delete(object Object)
Deletes an object in the database.
Task Clear(string CollectionName)
Clears a collection of all objects.
Task Insert(object Object)
Inserts an object into the database.
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.
Persistent dictionary that can contain more entries than possible in the internal memory.
Task ClearAsync()
Clears the dictionary.
Task< bool > EndCollection()
Is called when a collection is finished.
Task< bool > ReportProperty(string PropertyName, object PropertyValue)
Is called when a property is reported.
Task< bool > StartCollection(string CollectionName)
Is called when a collection is started.
Task< bool > EndLedger()
Is called when export of ledger is finished.
Task< bool > ReportException(Exception Exception)
Is called when an exception has occurred.
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 > EndBlock()
Is called when a block in a collection is finished.
Task< bool > StartEntry(string ObjectId, string TypeName, EntryType EntryType, DateTimeOffset EntryTimestamp)
Is called when an entry is started.
Task< bool > StartBlock(string BlockID)
Is called when a block in a collection is started.
Task< bool > EndEntry()
Is called when an entry is finished.
Interface for digital signature algorithms.
Definition: ImplTypes.g.cs:58
EventLevel
Event level.
Definition: EventLevel.cs:7
BlockStatus
Status of the block.
Definition: BlockHeader.cs:12
delegate void EmbeddedObjectSetter(object EmbeddedObject)
Delegate for embedded object value setter methods. Is used when loading embedded objects.
EntryType
Ledger entry type.
Definition: ILedgerEntry.cs:9
delegate byte[] HashFunctionStream(Stream Data)
Delegate to hash function.
HashFunction
Hash method enumeration.
Definition: Hashes.cs:26