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;
2using System.Collections;
3using System.Collections.Generic;
4using System.IO;
5using System.Security.Cryptography;
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[this.folder.Length - 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.AddFirst(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 await this.eventQueue.AddLast(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 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 {
346 using (CryptoStream cs = new CryptoStream(fs, Aes, CryptoStreamMode.Write))
347 {
348 await Bucket.CopyTo(cs, Signature);
349 cs.FlushFinalBlock();
350 Bucket.Delete();
351 }
352 }
353 }
354
355 string LocalFileName = this.GetLocalFileName(FileName);
356
357 BlockReference Ref = new BlockReference(Bucket.Header, LocalFileName, Digest, Signature, Bytes);
358 await this.AddBlockReference(Ref);
359 }
360
361 DateTime Now = DateTime.Now;
362 if (Now >= NextCheck)
363 {
364 NextCheck = Now.AddHours(1);
365 await this.DeleteExpiredBlocks(Now);
366 }
367 }
368 catch (Exception ex)
369 {
370 Log.Exception(ex);
371 }
372 }
373 }
374 catch (Exception ex)
375 {
376 Log.Exception(ex);
377 }
378 finally
379 {
380 this.completed.TrySetResult(true);
381 }
382 }
383
390 private byte[] CalcCombinationDigest(byte[] ContentDigest, byte[] Signature)
391 {
392 int c = ContentDigest.Length;
393 int d = Signature.Length;
394 byte[] Bin = new byte[c + d];
395
396 Array.Copy(ContentDigest, 0, Bin, 0, c);
397 Array.Copy(Signature, 0, Bin, c, d);
398
399 using (MemoryStream ms = new MemoryStream())
400 {
401 ms.Write(ContentDigest, 0, ContentDigest.Length);
402 ms.Write(Signature, 0, Signature.Length);
403
404 ms.Position = 0;
405
406 return this.hashFunction(ms);
407 }
408 }
409
416 private string GetFullFileName(BlockHeader Header, byte[] Digest)
417 {
418 string FileName = Path.Combine(this.blockFolder,
419 Header.Created.Year.ToString("D4"),
420 Header.Created.Month.ToString("D2"),
421 Header.Created.Day.ToString("D2"));
422
423 if (!Directory.Exists(FileName))
424 Directory.CreateDirectory(FileName);
425
426 return Path.Combine(FileName, Hashes.BinaryToString(Digest)) + ".block";
427 }
428
434 private string GetLocalFileName(string FullFileName)
435 {
436 string LocalFileName;
437
438 if (FullFileName.StartsWith(this.blockFolder))
439 LocalFileName = FullFileName.Substring(this.blockFolder.Length);
440 else
441 LocalFileName = FullFileName;
442
443 return LocalFileName;
444 }
445
451 public async Task AddBlockFile(Stream File, BlockReference BlockReference)
452 {
453 File.Position = 0;
454 BlockReader Reader = await BlockReader.CreateAsync(File, this);
455
456 File.Position = 0;
457 byte[] Digest = this.hashFunction(File);
458
459 File.Position = 0;
460 byte[] Signature = this.signatureAlgorithm.Sign(File);
461
462 // TODO: Validate signature
463
464 Digest = this.CalcCombinationDigest(Digest, Signature);
465
466 string FileName = this.GetFullFileName(Reader.Header, Digest);
467
468 using (ICryptoTransform Aes = this.GetAes(FileName, true))
469 {
470 using (FileStream fs = System.IO.File.Create(FileName))
471 {
472 using (CryptoStream cs = new CryptoStream(fs, Aes, CryptoStreamMode.Write))
473 {
474 File.Position = 0;
475 File.CopyTo(cs);
476 cs.FlushFinalBlock();
477 }
478 }
479 }
480
481 FileName = this.GetLocalFileName(FileName);
482
483 if (BlockReference.FileName != FileName && !string.IsNullOrEmpty(FileName))
484 {
485 BlockReference.FileName = FileName;
487 }
488
489 await this.BlockAdded.Raise(this, new BlockReferenceEventArgs(BlockReference));
490 }
491
495 public event EventHandlerAsync<BlockReferenceEventArgs> BlockAdded = null;
496
497 private async Task AddBlockReference(BlockReference Block)
498 {
499 try
500 {
501 await Database.Insert(Block);
502 await this.BlockAdded.Raise(this, new BlockReferenceEventArgs(Block));
503 }
504 catch (Exception ex)
505 {
506 Log.Exception(ex);
507 }
508 }
509
515 public string GetFullFileName(string LocalFileName)
516 {
517 if (string.IsNullOrEmpty(LocalFileName))
518 return string.Empty;
519 else if (Path.IsPathRooted(LocalFileName))
520 return LocalFileName;
521 else
522 return Path.Combine(this.blockFolder, LocalFileName);
523 }
524
525 private async Task DeleteExpiredBlocks(DateTime Now)
526 {
527 try
528 {
529 Dictionary<string, bool> Directories = null;
530 string FileName;
531
532 foreach (BlockReference Ref in await Database.Find<BlockReference>(new FilterFieldLesserOrEqualTo("Expires", Now)))
533 {
534 FileName = this.GetFullFileName(Ref.FileName);
535
536 if (File.Exists(FileName))
537 {
538 try
539 {
540 File.Delete(FileName);
541
542 if (Directories is null)
543 Directories = new Dictionary<string, bool>();
544
545 string Folder = Path.GetDirectoryName(FileName);
546
547 Directories[Folder] = true;
548 }
549 catch (Exception ex)
550 {
551 Log.Error("Unable to delete block.",
552 new KeyValuePair<string, object>("FileName", FileName),
553 new KeyValuePair<string, object>("Message", ex.Message));
554
555 continue;
556 }
557 }
558
559 await this.DeleteBlockReference(Ref);
560 }
561
562 if (!(Directories is null))
563 {
564 foreach (string Key in Directories.Keys)
565 {
566 string FolderName = Key;
567
568 while (Directory.GetFiles(FolderName, "*.*", SearchOption.TopDirectoryOnly).Length == 0 &&
569 Directory.GetDirectories(FolderName, "*.*", SearchOption.TopDirectoryOnly).Length == 0)
570 {
571 try
572 {
573 Directory.Delete(FolderName, false);
574
575 int i = FolderName.LastIndexOf(Path.DirectorySeparatorChar);
576 if (i < 0)
577 break;
578 else
579 FolderName = FolderName.Substring(0, i);
580 }
581 catch (Exception ex)
582 {
583 Log.Error("Unable to delete folder.",
584 new KeyValuePair<string, object>("Folder", FolderName),
585 new KeyValuePair<string, object>("Message", ex.Message));
586 break;
587 }
588 }
589 }
590 }
591 }
592 catch (Exception ex)
593 {
594 Log.Exception(ex);
595 }
596 }
597
601 public event EventHandlerAsync<BlockReferenceEventArgs> BlockDeleted = null;
602
603 private async Task DeleteBlockReference(BlockReference Block)
604 {
605 try
606 {
607 await Database.Delete(Block);
608 await this.BlockDeleted.Raise(this, new BlockReferenceEventArgs(Block));
609 }
610 catch (Exception ex)
611 {
612 Log.Exception(ex);
613 }
614 }
615
616 #region IDisposable
617
621 public void Dispose()
622 {
623 if (!this.disposed)
624 {
625 this.disposed = true;
626 this.eventQueue.Dispose();
627 }
628 }
629
630 #endregion
631
632 #region ISerializerContext
633
638 public string Id => this.id;
639
643 public string DefaultCollectionName => this.defaultCollectionName;
644
649 {
650 get
651 {
652 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
653 }
654 }
655
659 public bool Debug => this.debug;
660
666 public bool NormalizedNames => false;
667
671 public string ExternalIdentity => this.externalIdentity;
672
679 public Task<ulong> GetFieldCode(string Collection, string FieldName)
680 {
681 throw new NotSupportedException("Field codes not used.");
682 }
683
691 public Task<string> GetFieldName(string Collection, ulong FieldCode)
692 {
693 throw new NotSupportedException("Field codes not used.");
694 }
695
701 public Task<IObjectSerializer> GetObjectSerializer(Type Type)
702 {
703 if (this.serializers is null)
704 throw new ObjectDisposedException("Service is closing down.");
705
706 return this.serializers.GetObjectSerializer(Type);
707 }
708
714 public Task<IObjectSerializer> GetObjectSerializerNoCreate(Type Type)
715 {
716 if (this.serializers is null)
717 throw new ObjectDisposedException("Service is closing down.");
718
719 return this.serializers.GetObjectSerializerNoCreate(Type);
720 }
721
727 public Task<ObjectSerializer> GetObjectSerializerEx(object Object)
728 {
729 return this.GetObjectSerializerEx(Object.GetType());
730 }
731
737 public async Task<ObjectSerializer> GetObjectSerializerEx(Type Type)
738 {
739 if (!(await this.GetObjectSerializer(Type) is ObjectSerializer Serializer))
740 throw new Exception("Objects of type " + Type.FullName + " must be embedded.");
741
742 return Serializer;
743 }
744
749 public Guid CreateGuid()
750 {
751 return Guid.NewGuid();
752 }
753
760 public Task<Guid> SaveNewObject(object Value, object State)
761 {
762 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
763 }
764
772 public Task<T> TryLoadObject<T>(Guid ObjectId, EmbeddedObjectSetter EmbeddedSetter)
773 where T : class
774 {
775 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
776 }
777
785 public Task<object> TryLoadObject(Type T, Guid ObjectId, EmbeddedObjectSetter EmbeddedSetter)
786 {
787 throw new NotSupportedException("Objects must be embedded. They cannot be referenced and separately stored.");
788 }
789
790 #endregion
791
796 public Task NewEntry(object Object)
797 {
798 this.Archive(EntryType.New, Object);
799 return Task.CompletedTask;
800 }
801
806 public Task UpdatedEntry(object Object)
807 {
808 this.Archive(EntryType.Update, Object);
809 return Task.CompletedTask;
810
811 // TODO: Update strategies
812 }
813
818 public Task DeletedEntry(object Object)
819 {
820 this.Archive(EntryType.Delete, Object);
821 return Task.CompletedTask;
822
823 // TODO: Delete strategies
824 }
825
830 public Task ClearedCollection(string Collection)
831 {
832 // TODO: Archive clear event.
833 // TODO: Clear strategies
834
835 return Task.CompletedTask;
836 }
837
838 private void Archive(EntryType EntryType, object Object)
839 {
840 if (this.disposed)
841 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
842
843 if (Object is null)
844 return;
845
846 if (!this.stopped)
847 {
848 // No assigned bucket and Object not null serializes object into bucket
849 // corresponding to object's collection and archiving time.
850
851 this.eventQueue.AddLast(new WorkItem()
852 {
853 Bucket = null,
854 Type = EntryType,
855 Object = Object
856 });
857 }
858 }
859
865 public async Task<ILedgerEnumerator<T>> GetEnumerator<T>()
866 {
867 ObjectSerializer Serializer = await this.GetObjectSerializerEx(typeof(T));
868 string CollectionName = await Serializer.CollectionName(null);
870 return await ObjectEnumerator<T>.Create(BlockEnumerator, this);
871 }
872
878 public async Task<ILedgerEnumerator<object>> GetEnumerator(string CollectionName)
879 {
882 }
883
888 public async Task<PaginatedEnumerator<BlockReference>> GetBlockEnumerator(bool Ascending)
889 {
890 return await Database.Enumerate<BlockReference>(
891 BlockPageSize, Ascending ? "Created" : "-Created");
892 }
893
901 public async Task<PaginatedEnumerator<BlockReference>> GetCollectionBlockEnumerator(string Collection, bool Ascending)
902 {
903 return await Database.Enumerate<BlockReference>(
904 BlockPageSize, new FilterFieldEqualTo("Collection", Collection),
905 Ascending ? "Created" : "-Created");
906 }
907
915 public async Task<PaginatedEnumerator<BlockReference>> GetCreatorBlockEnumerator(string Creator, bool Ascending)
916 {
917 return await Database.Enumerate<BlockReference>(
918 BlockPageSize, new FilterAnd(
919 new FilterFieldEqualTo("Creator", Creator),
920 new FilterFieldEqualTo("AccessDenied", false)),
921 Ascending ? "Created" : "-Created");
922 }
923
927 public async Task Start()
928 {
929 if (this.disposed)
930 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
931
932 this.stopped = false;
933 this.completed = new TaskCompletionSource<bool>();
934
935 string[] Files = Directory.GetFiles(this.bucketFolder, "*.bin", SearchOption.TopDirectoryOnly);
936 foreach (string FileName in Files)
937 {
938 try
939 {
940 string BucketName = Path.GetFileName(FileName);
941 BucketName = BucketName.Substring(0, BucketName.Length - 4);
942
943 int i = BucketName.LastIndexOf('.');
944 if (i < 0)
945 {
946 File.Delete(FileName);
947 continue;
948 }
949
950 string Suffix = BucketName.Substring(i + 1);
951 string CollectionName = BucketName.Substring(0, i);
952 DateTime Expires;
953
954 if (int.TryParse(Suffix, out int NrDays))
955 Expires = DateTime.UtcNow.AddDays(NrDays);
956 else
957 Expires = DateTime.MaxValue;
958
959 Bucket Bucket = await Bucket.Create(FileName, CollectionName, Expires, this);
960
961 if (Bucket.HasEntries)
962 this.buckets.Add(BucketName, Bucket);
963 else
964 Bucket.Delete();
965 }
966 catch (Exception ex)
967 {
968 Log.Exception(ex);
969 }
970 }
971
972 Task _ = Task.Run(() => this.StorageTask());
973 }
974
978 public async Task Stop()
979 {
980 if (this.disposed)
981 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
982
983 this.stopped = true;
984
985 this.eventQueue.Disposed += (Sender, e) =>
986 {
987 this.buckets?.Clear();
988 this.buckets?.Dispose();
989 this.buckets = null;
990
991 this.serializers?.Dispose();
992 this.serializers = null;
993
994 this.aes?.Dispose();
995 this.aes = null;
996 };
997
998 await this.eventQueue.Terminate();
999 await this.completed.Task;
1000 }
1001
1005 public Task Flush()
1006 {
1007 if (this.disposed)
1008 throw new ObjectDisposedException("The Neuro-Ledger has been disposed and does not accept more entries.");
1009
1010 this.buckets?.Clear();
1011 return Task.CompletedTask;
1012 }
1013
1014 internal ICryptoTransform GetAes(string FileName, bool Encryptor)
1015 {
1016 byte[] BinFileName = Encoding.UTF8.GetBytes(FileName);
1017 int c = BinFileName.Length;
1018 byte[] Concat = new byte[c + this.saltLength];
1019
1020 Array.Copy(BinFileName, 0, Concat, 0, c);
1021 Array.Copy(this.salt, 0, Concat, c, this.saltLength);
1022
1023 byte[] Key = Hashes.ComputeSHA256Hash(Concat);
1024 byte[] IV = Hashes.ComputeHMACSHA256Hash(Key, BinFileName);
1025
1026 Array.Resize(ref IV, 16);
1027
1028 if (Encryptor)
1029 return this.aes.CreateEncryptor(Key, IV);
1030 else
1031 return this.aes.CreateDecryptor(Key, IV);
1032 }
1033
1039 public static Task<BlockReference> FindReference(byte[] Digest)
1040 {
1041 return Database.FindFirstIgnoreRest<BlockReference>(new FilterFieldEqualTo("Digest", Digest));
1042 }
1043
1047 public async Task RepairRegistry()
1048 {
1049 int NrAdded = 0;
1050 int NrUpdated = 0;
1051 int NrDeleted = 0;
1052
1053 Log.Warning("Block Registry collection repaired during start-up. Scanning existing blocks in ledger to make sure block registry is up to date.");
1054
1055 await this.RepairRegistry(
1056 null,
1057 (Sender, e) =>
1058 {
1059 NrAdded++;
1060 return Task.CompletedTask;
1061 },
1062 (Sender, e) =>
1063 {
1064 NrUpdated++;
1065 return Task.CompletedTask;
1066 },
1067 (Sender, e) =>
1068 {
1069 NrDeleted++;
1070 return Task.CompletedTask;
1071 });
1072
1073 if (NrAdded == 0 && NrUpdated == 0 && NrDeleted == 0)
1074 {
1075 Log.Notice("Block Registry OK. Nothing to repair.",
1076 new KeyValuePair<string, object>("NrAdded", NrAdded),
1077 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1078 new KeyValuePair<string, object>("NrDeleted", NrDeleted));
1079 }
1080 else
1081 {
1082 Log.Warning("Block Registry repaired, based on blocks available in ledger.",
1083 new KeyValuePair<string, object>("NrAdded", NrAdded),
1084 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1085 new KeyValuePair<string, object>("NrDeleted", NrDeleted));
1086 }
1087 }
1088
1096 public async Task RepairRegistry(
1097 EventHandlerAsync<BlockReferenceEventArgs> NoChangeCallback,
1098 EventHandlerAsync<BlockReferenceEventArgs> AddedCallback,
1099 EventHandlerAsync<BlockReferenceEventArgs> UpdatedCallback,
1100 EventHandlerAsync<BlockReferenceEventArgs> DeletedCallback)
1101 {
1102 // First, scan blocks and recreate missing block reference objects.
1103
1104 IEnumerable<string> Files = Directory.EnumerateFiles(this.blockFolder, "*.block", SearchOption.AllDirectories);
1105 IEnumerator<string> e = Files.GetEnumerator();
1106
1107 while (e.MoveNext())
1108 {
1109 string BlockFile = e.Current;
1110
1111 try
1112 {
1113 BlockReader Reader = await BlockReader.CreateAsync(BlockFile, this);
1114 string LocalFileName;
1115
1116 if (BlockFile.StartsWith(this.blockFolder))
1117 LocalFileName = BlockFile.Substring(this.blockFolder.Length);
1118 else
1119 LocalFileName = BlockFile;
1120
1121 string s = Path.GetFileName(LocalFileName);
1122 s = s.Substring(0, s.Length - 6);
1123 byte[] Digest = Hashes.StringToBinary(s);
1124 if (Digest is null)
1125 continue;
1126
1127 BlockReference Ref = await FindReference(Digest);
1128 int i;
1129
1130 if (Ref is null)
1131 {
1132 Ref = new BlockReference(Reader.Header, LocalFileName, Digest, Reader.Signature, Reader.Bytes);
1133 await Database.Provider.Insert(Ref);
1134
1135 await AddedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1136 }
1137 else if ((i = this.Compare(Ref, Reader, LocalFileName, Digest)) != 0)
1138 {
1139 Ref.Creator = Reader.Header.Creator;
1140 Ref.Created = Reader.Header.Created;
1141 Ref.Updated = Reader.Header.Updated;
1142 Ref.Expires = Reader.Header.Expires;
1143 Ref.Status = Reader.Header.Status;
1144 Ref.Link = Reader.Header.Link;
1145 Ref.FileName = LocalFileName;
1146 Ref.Collection = Reader.CollectionName;
1147 Ref.Bytes = Reader.Bytes;
1148 Ref.Digest = (byte[])Digest.Clone();
1149 Ref.Signature = (byte[])Reader.Signature.Clone();
1150
1151 await Database.Provider.Update(Ref);
1152
1153 await UpdatedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1154 }
1155 else
1156 await NoChangeCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1157 }
1158 catch (Exception ex)
1159 {
1160 Log.Error("Unable to process block file.",
1161 new KeyValuePair<string, object>("FileName", BlockFile),
1162 new KeyValuePair<string, object>("Message", ex.Message));
1163 }
1164 }
1165
1166 // Second, scan block reference objects and remove obsolete reference objects.
1167
1168 string LastObjectId = null;
1169 const int Max = 1000;
1170 int Count;
1171
1172 do
1173 {
1174 IEnumerable<BlockReference> References;
1175
1176 if (LastObjectId is null)
1177 References = await Database.Find<BlockReference>(0, Max, "ObjectId");
1178 else
1179 References = await Database.Find<BlockReference>(0, Max, new FilterFieldGreaterThan("ObjectId", LastObjectId), "ObjectId");
1180
1181 Count = 0;
1182 foreach (BlockReference Ref in References)
1183 {
1184 Count++;
1185
1186 if (string.IsNullOrEmpty(Ref.FileName))
1187 {
1188 if (Ref.Creator == this.externalIdentity)
1189 {
1190 await Database.Provider.Delete(Ref);
1191
1192 await DeletedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1193 }
1194 else
1195 await NoChangeCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1196 }
1197 else
1198 {
1199 string FileName = this.GetFullFileName(Ref.FileName);
1200
1201 if (!File.Exists(FileName))
1202 {
1203 if (Ref.Creator == this.externalIdentity)
1204 {
1205 await Database.Provider.Delete(Ref);
1206
1207 await DeletedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1208 }
1209 else
1210 {
1211 Ref.FileName = string.Empty;
1212 await Database.Provider.Update(Ref);
1213 await UpdatedCallback.Raise(this, new BlockReferenceEventArgs(Ref));
1214 }
1215 }
1216 }
1217
1218 LastObjectId = Ref.ObjectId;
1219 }
1220 }
1221 while (Count >= Max);
1222 }
1223
1224 private int Compare(BlockReference Ref, BlockReader Reader, string LocalFileName, byte[] Digest)
1225 {
1226 if (Ref.Creator != Reader.Header.Creator)
1227 return 1;
1228
1229 if (!AreEqual(Ref.Created, Reader.Header.Created))
1230 return 2;
1231
1232 if (!AreEqual(Ref.Updated, Reader.Header.Updated))
1233 return 3;
1234
1235 if (!AreEqual(Ref.Expires, Reader.Header.Expires))
1236 return 4;
1237
1238 if (Ref.Status != Reader.Header.Status)
1239 return 5;
1240
1241 if (Ref.Link != Reader.Header.Link)
1242 return 6;
1243
1244 if (Ref.FileName != LocalFileName)
1245 return 7;
1246
1247 if (Ref.Collection != Reader.CollectionName)
1248 return 8;
1249
1250 if (Ref.Bytes != Reader.Bytes)
1251 return 9;
1252
1253 if (Convert.ToBase64String(Ref.Digest) != Convert.ToBase64String(Digest))
1254 return 10;
1255
1256 if (Convert.ToBase64String(Ref.Signature) != Convert.ToBase64String(Reader.Signature))
1257 return 11;
1258
1259 return 0;
1260 }
1261
1268 public static bool AreEqual(DateTime TP1, DateTime TP2)
1269 {
1270 return
1271 TP1.Millisecond == TP2.Millisecond &&
1272 TP1.Second == TP2.Second &&
1273 TP1.Minute == TP2.Minute &&
1274 TP1.Hour == TP2.Hour &&
1275 TP1.Day == TP2.Day &&
1276 TP1.Month == TP2.Month &&
1277 TP1.Year == TP2.Year &&
1278 TP1.Kind == TP2.Kind;
1279 }
1280
1285 public async Task RepairCollection(string CollectionName)
1286 {
1287 PaginatedEnumerator<BlockReference> Blocks = await this.GetCollectionBlockEnumerator(CollectionName, false);
1288 CachedStringDictionary Dictionary = null;
1289 List<ObjectState> ObjectsInBlock = new List<ObjectState>();
1290 DateTime Start = DateTime.Now;
1291 int Count = 0;
1292 bool Cleared = false;
1293 uint NrAdded = 0;
1294 uint NrUpdated = 0;
1295 uint NrDeleted = 0;
1296 uint NrErrors = 0;
1297
1298 try
1299 {
1300 await Database.StartBulk();
1301 try
1302 {
1303 while (await Blocks.MoveNextAsync())
1304 {
1305 BlockReference Ref = Blocks.Current;
1306 if (Ref.Status != BlockStatus.Valid || Ref.AccessDenied)
1307 continue;
1308
1309 if (Dictionary is null)
1310 {
1311 IPersistentDictionary PersistentDictionary = await Database.GetDictionary(CollectionName);
1312 await PersistentDictionary.ClearAsync();
1313
1314 Dictionary = new CachedStringDictionary(100000, PersistentDictionary);
1315
1316 Log.Warning(CollectionName + " collection repaired during start-up. Scanning existing blocks in ledger to make sure collection is up to date.");
1317 }
1318
1319 using (BlockEnumerator TempBlockEnumerator = new BlockEnumerator(Ref, this))
1320 {
1321 using (ObjectEnumerator<GenericObject> e = await ObjectEnumerator<GenericObject>.Create(TempBlockEnumerator, this))
1322 {
1323 while (await e.MoveNextAsync())
1324 ObjectsInBlock.Add(new ObjectState(e.CurrentEntry.Type, e.Current));
1325 }
1326
1327 ObjectsInBlock.Reverse();
1328
1329 Exception FirstException = null;
1330
1331 foreach (ObjectState ObjectState in ObjectsInBlock)
1332 {
1333 if (ObjectState.Type == EntryType.Clear)
1334 {
1335 Cleared = true;
1336 break;
1337 }
1338
1339 try
1340 {
1341 string Key = ObjectState.Object.ObjectId.ToString();
1342
1343 if (await Dictionary.ContainsKeyAsync(Key))
1344 continue; // Only latest is of importance.
1345
1346 await Dictionary.AddAsync(Key, ObjectState);
1347 }
1348 catch (Exception ex)
1349 {
1350 if (FirstException is null)
1351 FirstException = ex;
1352 break;
1353 }
1354
1355 if (++Count >= 100)
1356 {
1357 await Database.EndBulk();
1358 await Database.StartBulk();
1359 Count = 0;
1360 }
1361 }
1362
1363 if (!(FirstException is null))
1364 {
1365 Log.Error("Unable to enumerate objects in block properly when repairing collection:\r\n\r\n" +
1366 FirstException.Message, TempBlockEnumerator.Current.FileName, string.Empty, string.Empty,
1367 EventLevel.Major, string.Empty, string.Empty, Log.CleanStackTrace(FirstException.StackTrace),
1368 new KeyValuePair<string, object>("Collection", CollectionName));
1369 }
1370
1371 ObjectsInBlock.Clear();
1372 }
1373
1374 if (Cleared)
1375 break;
1376 }
1377
1378 if (Cleared)
1379 await Database.Provider.Clear(CollectionName);
1380 }
1381 finally
1382 {
1383 await Database.EndBulk();
1384 }
1385
1386 if (!(Dictionary is null))
1387 {
1388 Tuple<uint, uint, uint, uint> Counts = await this.Process(Dictionary, CollectionName);
1389
1390 NrAdded = Counts.Item1;
1391 NrUpdated = Counts.Item2;
1392 NrDeleted = Counts.Item3;
1393 NrErrors = Counts.Item4;
1394 }
1395 }
1396 finally
1397 {
1398 Blocks.Dispose();
1399
1400 if (!(Dictionary is null))
1401 {
1402 await Dictionary.ClearAsync();
1403 Dictionary.DeleteAndDispose();
1404 }
1405 }
1406
1407 TimeSpan Elapsed = DateTime.Now - Start;
1408
1409 if (NrAdded == 0 && NrUpdated == 0 && NrDeleted == 0 && NrErrors == 0)
1410 {
1411 Log.Notice("Collection OK. Nothing to repair.",
1412 new KeyValuePair<string, object>("CollectionName", CollectionName),
1413 new KeyValuePair<string, object>("NrAdded", NrAdded),
1414 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1415 new KeyValuePair<string, object>("NrDeleted", NrDeleted),
1416 new KeyValuePair<string, object>("NrErrors", NrErrors),
1417 new KeyValuePair<string, object>("Time", Elapsed.ToString()));
1418 }
1419 else
1420 {
1421 Log.Alert("Collection repaired, based on blocks available in ledger.",
1422 new KeyValuePair<string, object>("CollectionName", CollectionName),
1423 new KeyValuePair<string, object>("NrAdded", NrAdded),
1424 new KeyValuePair<string, object>("NrUpdated", NrUpdated),
1425 new KeyValuePair<string, object>("NrDeleted", NrDeleted),
1426 new KeyValuePair<string, object>("NrErrors", NrErrors),
1427 new KeyValuePair<string, object>("Time", Elapsed.ToString()));
1428 }
1429 }
1430
1438 public async Task<Tuple<uint, uint, uint, uint>> Process(CachedStringDictionary Records, string CollectionName)
1439 {
1440 uint NrAdded = 0;
1441 uint NrUpdated = 0;
1442 uint NrDeleted = 0;
1443 uint NrErrors = 0;
1444
1445 await Database.StartBulk();
1446 try
1447 {
1448 IEnumerator<KeyValuePair<string, object>> e = await Records.GetEnumeratorAsync();
1449 try
1450 {
1451 int Count = 0;
1452
1453 if (!(e is IAsyncEnumerator eAsync))
1454 eAsync = new PseudoAsyncEnumerator(e);
1455
1456 while (await eAsync.MoveNextAsync())
1457 {
1458 if (!(e.Current.Value is ObjectState ObjectState))
1459 continue;
1460
1461 try
1462 {
1463 switch (ObjectState.Type)
1464 {
1465 // Note: Use of Database.Provider avoids generating events that result in the creation of new blocks.
1466
1467 case EntryType.New:
1468 case EntryType.Update:
1470 if (Obj2 is null)
1471 {
1473 NrAdded++;
1474 }
1475 else
1476 {
1477 if (Obj2.Equals(ObjectState.Object))
1478 continue;
1479
1481 NrUpdated++;
1482 }
1483 break;
1484
1485 case EntryType.Delete:
1487 if (Obj2 is null)
1488 continue;
1489 else
1490 {
1491 await Database.Provider.Delete(Obj2);
1492 NrDeleted++;
1493 }
1494 break;
1495
1496 default:
1497 continue;
1498 }
1499
1500 if (++Count >= 100)
1501 {
1502 await Database.EndBulk();
1503 await Database.StartBulk();
1504 Count = 0;
1505 }
1506 }
1507 catch (Exception ex)
1508 {
1509 Log.Exception(ex);
1510 NrErrors++;
1511 }
1512 }
1513 }
1514 finally
1515 {
1516 if (e is IDisposable Disposable)
1517 Disposable.Dispose();
1518 }
1519 }
1520 finally
1521 {
1522 await Database.EndBulk();
1523 }
1524
1525 return new Tuple<uint, uint, uint, uint>(NrAdded, NrUpdated, NrDeleted, NrErrors);
1526 }
1527
1528 private class PseudoAsyncEnumerator : IAsyncEnumerator
1529 {
1530 private readonly IEnumerator e;
1531
1532 public PseudoAsyncEnumerator(IEnumerator e)
1533 {
1534 this.e = e;
1535 }
1536
1537 public object Current => this.e.Current;
1538 public bool MoveNext() => this.e.MoveNext();
1539 public Task<bool> MoveNextAsync() => Task.FromResult(this.e.MoveNext());
1540 public void Reset() => this.e.Reset();
1541 }
1542
1546 public string[] Collections
1547 {
1548 get
1549 {
1550 string[] Result;
1551
1552 lock (this.collections)
1553 {
1554 Result = new string[this.collections.Count];
1555 this.collections.Keys.CopyTo(Result, 0);
1556 }
1557
1558 return Result;
1559 }
1560 }
1561
1566 public Task<string[]> GetCollections()
1567 {
1568 return Task.FromResult<string[]>(this.Collections);
1569 }
1570
1578 public Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction)
1579 {
1580 return this.Export(Output, Restriction, null);
1581 }
1582
1591 public async Task<bool> Export(ILedgerExport Output, LedgerExportRestriction Restriction, ProfilerThread Thread)
1592 {
1593 Thread?.Start();
1594
1595 if (!await Output.StartLedger())
1596 return false;
1597
1598 bool Continue;
1599
1600 try
1601 {
1602 string[] Collections = Restriction?.CollectionNames ?? await Ledger.GetCollections();
1603
1604 foreach (string Collection in Collections)
1605 {
1606 Thread?.NewState(Collection);
1607 if (!await Output.StartCollection(Collection))
1608 return false;
1609 try
1610 {
1611 string[] BlockIds = Restriction?.BlockIds ?? new string[] { null };
1612
1613 foreach (string BlockId in BlockIds)
1614 {
1615 string[] Creators = Restriction?.Creators ?? new string[] { null };
1616
1617 foreach (string Creator in Creators)
1618 {
1619 List<Filter> Filters = new List<Filter>()
1620 {
1621 new FilterFieldEqualTo("Collection", Collection)
1622 };
1623
1624 if (!string.IsNullOrEmpty(BlockId))
1625 Filters.Add(new FilterFieldEqualTo("ObjectId", BlockId));
1626
1627 if (!string.IsNullOrEmpty(Creator))
1628 Filters.Add(new FilterFieldEqualTo("Creator", Creator));
1629
1630 if (Restriction.MinCreated.HasValue)
1631 {
1632 if (Restriction.MinCreatedIncluded)
1633 Filters.Add(new FilterFieldGreaterOrEqualTo("Created", Restriction.MinCreated.Value));
1634 else
1635 Filters.Add(new FilterFieldGreaterThan("Created", Restriction.MinCreated.Value));
1636 }
1637
1638 if (Restriction.MaxCreated.HasValue)
1639 {
1640 if (Restriction.MaxCreatedIncluded)
1641 Filters.Add(new FilterFieldLesserOrEqualTo("Created", Restriction.MaxCreated.Value));
1642 else
1643 Filters.Add(new FilterFieldLesserThan("Created", Restriction.MaxCreated.Value));
1644 }
1645
1646 Filter Filter;
1647
1648 if (Filters.Count > 1)
1649 Filter = new FilterAnd(Filters.ToArray());
1650 else
1651 Filter = Filters[0];
1652
1654 await Database.Enumerate<BlockReference>(BlockPageSize, Filter, "Created"))
1655 {
1656 while (await Blocks.MoveNextAsync())
1657 {
1658 BlockReference Ref = Blocks.Current;
1659 if (Ref.Status != BlockStatus.Valid || Ref.AccessDenied)
1660 continue;
1661
1662 await Output.StartBlock(Ref.ObjectId);
1663 try
1664 {
1665 if (!await Output.BlockMetaData("Bytes", Ref.Bytes))
1666 return false;
1667
1668 if (!await Output.BlockMetaData("Created", Ref.Created))
1669 return false;
1670
1671 if (!await Output.BlockMetaData("Creator", Ref.Creator))
1672 return false;
1673
1674 if (!await Output.BlockMetaData("Digest", Ref.Digest))
1675 return false;
1676
1677 if (!await Output.BlockMetaData("Expires", Ref.Expires))
1678 return false;
1679
1680 if (!await Output.BlockMetaData("FileName", Ref.FileName))
1681 return false;
1682
1683 if (!await Output.BlockMetaData("Signature", Ref.Signature))
1684 return false;
1685
1686 using (BlockEnumerator TempBlockEnumerator = new BlockEnumerator(Blocks.Current, this))
1687 {
1688 using (ObjectEnumerator<GenericObject> e = await ObjectEnumerator<GenericObject>.Create(TempBlockEnumerator, this))
1689 {
1690 GenericObject Obj;
1691
1692 while (await e.MoveNextAsync())
1693 {
1694 Obj = e.Current;
1695
1696 if (!await Output.StartEntry(Obj.ObjectId.ToString(), Obj.TypeName, e.CurrentEntry.Type, e.CurrentEntry.Timestamp))
1697 return false;
1698 try
1699 {
1700 foreach (KeyValuePair<string, object> P in Obj)
1701 {
1702 if (!await Output.ReportProperty(P.Key, P.Value))
1703 return false;
1704 }
1705 }
1706 catch (Exception ex)
1707 {
1708 Thread?.Exception(ex);
1709 if (!await this.ReportException(ex, Output))
1710 return false;
1711 }
1712 finally
1713 {
1714 Continue = await Output.EndEntry();
1715 }
1716
1717 if (!Continue)
1718 return false;
1719 }
1720 }
1721 }
1722 }
1723 finally
1724 {
1725 Continue = await Output.EndBlock();
1726 }
1727
1728 if (!Continue)
1729 return false;
1730 }
1731 }
1732 }
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.EndCollection();
1744 }
1745
1746 if (!Continue)
1747 return false;
1748 }
1749 }
1750 catch (Exception ex)
1751 {
1752 Thread?.Exception(ex);
1753 if (!await this.ReportException(ex, Output))
1754 return false;
1755 }
1756 finally
1757 {
1758 Continue = await Output.EndLedger();
1759
1760 Thread?.Idle();
1761 Thread?.Stop();
1762 }
1763
1764 return Continue;
1765 }
1766
1767 private async Task<bool> ReportException(Exception ex, ILedgerExport Output)
1768 {
1769 ex = Log.UnnestException(ex);
1770
1771 if (ex is AggregateException ex2)
1772 {
1773 foreach (Exception ex3 in ex2.InnerExceptions)
1774 {
1775 if (!await Output.ReportException(ex3))
1776 return false;
1777 }
1778
1779 return true;
1780 }
1781 else
1782 return await Output.ReportException(ex);
1783 }
1784
1791 {
1792 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
1793 throw new Exception("An interface for external events has already been registered.");
1794
1795 this.externalEvents = ExternalEvents;
1796 }
1797
1804 {
1805 if (!(this.externalEvents is null) && this.externalEvents != ExternalEvents)
1806 throw new Exception("The registered interface for external events differs from the one presented.");
1807
1808 this.externalEvents = null;
1809 }
1810
1815 {
1816 get
1817 {
1818 Assert.CallFromSource("Waher.Networking.XMPP.NeuroLedger.NeuroLedgerClient");
1819 return this.externalEvents;
1820 }
1821 }
1822
1823 }
1824}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:17
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
Definition: Log.cs:184
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 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:566
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 Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:818
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:450
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:1227
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IPersistentDictionary > GetDictionary(string Collection)
Gets a persistent dictionary containing objects in a collection.
Definition: Database.cs:1542
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:1494
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:57
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:1486
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:717
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
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:491
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:262
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:169
byte[] Signature
Signature of block.
Definition: BlockReader.cs:238
Event arguments for block reference events.
Represents the construction of a block file.
Definition: Bucket.cs:17
BlockHeader Header
Block Header
Definition: Bucket.cs:157
long Length
Length of bucket file.
Definition: Bucket.cs:137
void Delete()
Deletes the file, and disposes of the object.
Definition: Bucket.cs:182
static async Task< Bucket > Create(string FileName, string CollectionName, DateTime Expires, NeuroLedgerProvider Provider)
Represents the construction of a block file.
Definition: Bucket.cs:41
bool HasEntries
If entries has been written to the bucket.
Definition: Bucket.cs:152
async Task< long > WriteEntry(EntryType EntryType, byte[] Binary)
Writes an entry to the bucket.
Definition: Bucket.cs:203
byte[] Sign(ISignatureAlgorithm Algorithm)
Signs the contents of the file.
Definition: Bucket.cs:352
async Task CopyTo(Stream Destination, byte[] Signature)
Copies the content of the bucket to an output stream.
Definition: Bucket.cs:363
byte[] Hash(HashFunctionStream HashFunction)
Calculates a hash digest of the contents of the file.
Definition: Bucket.cs:341
Optimizes a persistent IPersistentDictionary using a cache.
async Task< IEnumerator< KeyValuePair< string, object > > > GetEnumeratorAsync()
TODO
DateTime Timestamp
Timestamp of entry.
Definition: Entry.cs:37
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 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< 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< 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.
Implements an in-memory cache.
Definition: Cache.cs:15
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:74
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:451
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:203
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:338
void Clear()
Clears the cache.
Definition: Cache.cs:484
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
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:15
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.
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:39
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static byte[] StringToBinary(string s)
Parses a hex string.
Definition: Hashes.cs:102
static string BinaryToString(byte[] Data)
Converts an array of bytes to a string with their hexadecimal representations (in lower case).
Definition: Hashes.cs:65
static byte[] ComputeHMACSHA256Hash(byte[] Key, byte[] Data)
Computes the HMAC-SHA-256 hash of a block of binary data.
Definition: Hashes.cs:585
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:348
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 > StartLedger()
Is called when export of ledger is started.
Task< bool > ReportException(Exception Exception)
Is called when an exception has occurred.
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.
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:28