Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NeuroLedgerClient.cs
1using System;
3using System.IO;
4using System.Reflection;
5using System.Text;
6using System.Threading.Tasks;
7using System.Xml;
8using Waher.Content;
12using Waher.Events;
32
34{
39 {
43 public const string NeuroLedgerNamespace = "http://waher.se/NL";
44
48 public const int DefaultTimeoutMs = 2 * 60 * 1000;
49
50 private static readonly BlockAdded blockAddedRef = new BlockAdded();
51 private Cache<string, PeerStatus> peerStatus;
52 private readonly Dictionary<string, NodeStatus> nodeStatus = new Dictionary<string, NodeStatus>();
53 private readonly Dictionary<string, bool> loading = new Dictionary<string, bool>();
54 private readonly BlockResource blockResource;
55 private readonly HTTP.HttpResource blockListResource;
56 private readonly HTTP.HttpResource multiGetResource;
57 private readonly HTTP.HttpServer webServer;
58 private readonly EndpointSecurity e2eEncryption;
59 private readonly bool internalScheduler;
60 private readonly ILedgerExternalEvents externalEvents;
61 private NeuroLedgerProvider provider;
62 private PepClient pepClient = null;
63 private Scheduler scheduler;
64
72 public NeuroLedgerClient(XmppClient Client, EndpointSecurity E2eEncryption, HTTP.HttpServer WebServer, NeuroLedgerProvider Provider)
73 : base(Client)
74 {
75 this.provider = Provider;
76 this.webServer = WebServer;
77 this.e2eEncryption = E2eEncryption;
78 this.externalEvents = Provider.ExternalEvents;
79
80 this.peerStatus = new Cache<string, PeerStatus>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromDays(1), true);
81
82 if (Types.TryGetModuleParameter("Scheduler", out Scheduler Scheduler))
83 {
84 this.scheduler = Scheduler;
85 this.internalScheduler = false;
86 }
87 else
88 {
89 this.scheduler = new Scheduler();
90 this.internalScheduler = true;
91 }
92
93 this.webServer.Register(this.blockResource = new BlockResource("/NL/B", this.provider, this.client, this, this.webServer, "User"));
94 this.blockListResource = this.webServer.Register(new BlockListResource("/NL/L", this.client, this.webServer, "User"));
95 this.multiGetResource = this.webServer.Register(new MultiGetResource("/NL/MG", this.client, this.webServer, "User"));
96
97 Client.RegisterIqGetHandler("getBlockReferences", NeuroLedgerNamespace, this.GetBlockReferences, true);
98
99 foreach (IXmppExtension Extension in Client.Extensions)
100 {
101 if (Extension is PepClient PepClient)
102 {
103 this.pepClient = PepClient;
104
105 this.pepClient.RegisterHandler(typeof(BlockAdded), this.BlockAddedReceived);
106 this.pepClient.RegisterHandler(typeof(BlockDeleted), this.BlockDeletedReceived);
107 break;
108 }
109 }
110
111 Provider.BlockAdded += this.BlockAdded;
112 Provider.BlockDeleted += this.BlockDeleted;
113
114 this.client.OnStateChanged += this.Client_OnStateChanged;
115 this.client.OnPresence += this.Client_OnPresence;
116
117 if (this.client.State == XmppState.Connected)
118 {
119 Task _ = this.ResendPresences();
120 }
121 }
122
126 public NeuroLedgerProvider Provider => this.provider;
127
128 private async Task ResendPresences()
129 {
130 foreach (RosterItem Item in this.client.Roster)
131 {
132 foreach (PresenceEventArgs e in Item.Resources)
133 await this.Client_OnPresence(this.client, e);
134 }
135 }
136
137 private async Task Client_OnPresence(object Sender, PresenceEventArgs e)
138 {
139 PeerStatus PeerStatus = await this.GetPeerStatus(e);
140 if (PeerStatus is null)
141 return;
142
143 Task _ = Task.Run(() => this.SynchronizePeer(PeerStatus, false, null));
144 }
145
153 public async Task<string> SynchronizePeer(PeerStatus PeerStatus, bool StartFromBeginning, CallbackAsync<SynchronizationStatus> Callback)
154 {
155 try
156 {
158 return "Peer not a Neuro-Ledger node.";
159
161
162 lock (this.nodeStatus)
163 {
164 if (!this.nodeStatus.TryGetValue(PeerStatus.BareJid, out NodeStatus))
165 NodeStatus = null;
166 }
167
168 if (NodeStatus is null)
169 {
170 NodeStatus = await Database.FindFirstDeleteRest<NodeStatus>(new FilterFieldEqualTo("BareJid", PeerStatus.BareJid));
171 if (NodeStatus is null)
172 {
173 NodeStatus = new NodeStatus()
174 {
175 BareJid = PeerStatus.BareJid
176 };
177
179 }
180 }
181
182 NodeStatus ToDelete = null;
183
184 lock (this.nodeStatus)
185 {
186 if (this.nodeStatus.TryGetValue(PeerStatus.BareJid, out NodeStatus NodeStatus2))
187 {
188 if (NodeStatus2 != NodeStatus)
189 {
190 ToDelete = NodeStatus;
191 NodeStatus = NodeStatus2;
192 }
193 }
194 else
195 this.nodeStatus[PeerStatus.BareJid] = NodeStatus;
196 }
197
198 if (!(ToDelete is null))
199 await Database.Delete(ToDelete);
200
201 if (StartFromBeginning && !string.IsNullOrEmpty(NodeStatus.LastBlockId))
202 {
203 NodeStatus.LastBlockId = string.Empty;
205 }
206
207 return await this.SynchBlocksAsync(PeerStatus, NodeStatus, Callback, Callback is null, false);
208 }
209 catch (Exception ex)
210 {
211 return ex.Message;
212 }
213 }
214
220 public async Task<PeerStatus> GetPeerStatus(PresenceEventArgs e)
221 {
222 if (!e.IsOnline || e.FromBareJID == e.Client.BareJID || e.FromBareJID == e.From || this.peerStatus is null)
223 return null;
224
226 return null;
227
228 if (!this.peerStatus.TryGetValue(e.From, out PeerStatus PeerStatus))
229 {
230 PeerStatus = new PeerStatus()
231 {
232 FullJid = e.From,
233 BareJid = e.FromBareJID
234 };
235
236 this.peerStatus[e.From] = PeerStatus;
237 }
238
242 {
243 return null;
244 }
245
249 {
250 PeerStatus.RequestingCapabilitiesFunction = e.EntityCapabilityHashFunction;
251 PeerStatus.RequestingCapabilitiesNode = e.EntityCapabilityNode;
252 PeerStatus.RequestingCapabilitiesVersion = e.EntityCapabilityVersion;
253
254 try
255 {
256 string Node = e.EntityCapabilityNode + "#" + e.EntityCapabilityVersion;
258
259 string[] Features = new string[e2.Features.Count];
260 e2.Features.Keys.CopyTo(Features, 0);
261
262 PeerStatus.CapabilitiesFunction = e.EntityCapabilityHashFunction;
263 PeerStatus.CapabilitiesNode = e.EntityCapabilityNode;
264 PeerStatus.CapabilitiesVersion = e.EntityCapabilityVersion;
265 PeerStatus.Features = Features;
266 PeerStatus.IsNeuroLedger = e2.Features.ContainsKey(NeuroLedgerNamespace);
267 }
268 finally
269 {
270 PeerStatus.RequestingCapabilitiesFunction = string.Empty;
271 PeerStatus.RequestingCapabilitiesNode = string.Empty;
272 PeerStatus.RequestingCapabilitiesVersion = string.Empty;
273 }
274 }
275
276 return PeerStatus;
277 }
278
284 {
285 lock (PeerStatus)
286 {
288 {
289 PeerStatus.IsSynchronizing = false;
291 }
292 }
293 }
294
295 private async Task<string> SynchBlocksAsync(PeerStatus PeerStatus, NodeStatus NodeStatus, CallbackAsync<SynchronizationStatus> Callback,
296 bool IgnoreIfRunning, bool Repopulate)
297 {
298 int SynchCounter;
299
300 lock (PeerStatus)
301 {
303 {
304 if (IgnoreIfRunning)
305 return string.Empty;
306 }
307
308 PeerStatus.IsSynchronizing = true;
309 SynchCounter = ++PeerStatus.SynchCounter;
310 }
311
313 DateTime LastReported = DateTime.Now;
314 int MaxCount = 1000;
315 bool More;
316
317 try
318 {
319 List<BlockReference> Blocks = new List<BlockReference>();
320 Dictionary<string, bool> Collections = new Dictionary<string, bool>();
321 string LastId = NodeStatus.LastBlockId;
322 List<KeyValuePair<BlockReference, string>> Bulk = new List<KeyValuePair<BlockReference, string>>();
323 ulong BulkSize = 0;
324 int Count;
325
326 do
327 {
328 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
329
330 XmlElement Result = await this.GetBlockList(NodeStatus.BareJid, LastId, MaxCount);
331
332 if (Result is null || Result.LocalName != "blockReferences" || Result.NamespaceURI != NeuroLedgerNamespace)
333 break;
334
335 Count = 0;
336 foreach (XmlNode N in Result.ChildNodes)
337 {
338 if (PeerStatus.SynchCounter != SynchCounter || this.ClientDisposed)
339 return null;
340
341 if (N is XmlElement E2 && E2.LocalName == "ref" && E2.NamespaceURI == NeuroLedgerNamespace)
342 {
343 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
344
345 string Id = XML.Attribute(E2, "id");
346 BlockAdded Event = (BlockAdded)blockAddedRef.Parse(E2);
347
348 BlockReference Ref = await this.GetReference(Event, Status, !Repopulate);
349 if (!(Ref is null))
350 {
351 Collections[Ref.Collection] = true;
352
353 Bulk.Add(new KeyValuePair<BlockReference, string>(Ref, Event.Url));
354 BulkSize += Ref.Bytes;
355
356 if (Bulk.Count >= 100 || BulkSize >= 16 * 1024 * 1024)
357 {
358 await this.BulkLoad(PeerStatus, Bulk.ToArray(), LastReported, Status, Callback, false, Blocks);
359 Bulk.Clear();
360 BulkSize = 0;
361 }
362 }
363
364 Count++;
365 LastId = Id;
366 }
367 }
368
369 More = Count >= MaxCount;
370 }
371 while (More && !this.ClientDisposed);
372
373 if (Bulk.Count > 0)
374 await this.BulkLoad(PeerStatus, Bulk.ToArray(), LastReported, Status, Callback, false, Blocks);
375
376 if (Blocks.Count > 0)
377 {
378 Blocks.Reverse();
379
380 List<BlockReference> Subset = new List<BlockReference>();
381 List<ObjectState> ObjectsInBlock = new List<ObjectState>();
382 bool Cleared;
383
384 foreach (string Collection in Collections.Keys)
385 {
386 foreach (BlockReference Ref in Blocks)
387 {
388 if (Ref.Collection != Collection)
389 continue;
390
391 if (Ref.Status != BlockStatus.Valid || Ref.AccessDenied)
392 continue;
393
394 Subset.Add(Ref);
395 }
396
397 if (Subset.Count == 0)
398 continue;
399
400 IPersistentDictionary PersistentDictionary = await Database.GetDictionary(Collection);
401 await PersistentDictionary.ClearAsync();
402
403 CachedStringDictionary ObjectEvents = new CachedStringDictionary(100000, PersistentDictionary);
404 try
405 {
406 await Database.StartBulk();
407 try
408 {
409 Cleared = false;
410 Count = 0;
411
412 foreach (BlockReference Ref in Subset)
413 {
414 using (BlockEnumerator TempBlockEnumerator = new BlockEnumerator(Ref, this.provider))
415 {
416 using (ObjectEnumerator<GenericObject> e = await ObjectEnumerator<GenericObject>.Create(TempBlockEnumerator, this.provider))
417 {
418 while (await e.MoveNextAsync())
419 ObjectsInBlock.Add(new ObjectState(e.CurrentEntry.Type, e.Current));
420 }
421
422 ObjectsInBlock.Reverse();
423
424 foreach (ObjectState ObjectState in ObjectsInBlock)
425 {
426 Status.Events++;
427 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
428
429 if (ObjectState.Type == EntryType.Clear)
430 {
431 Cleared = true;
432 break;
433 }
434
435 try
436 {
437 string Key = ObjectState.Object.ObjectId.ToString();
438
439 if (await ObjectEvents.ContainsKeyAsync(Key))
440 continue; // Only latest is of importance.
441
442 await ObjectEvents.AddAsync(Key, ObjectState);
443 }
444 catch (Exception ex)
445 {
446 Log.Error("Unable to enumerate objects in block properly when repairing collection:\r\n\r\n" +
447 ex.Message, TempBlockEnumerator.Current.FileName, string.Empty, string.Empty,
448 EventLevel.Major, string.Empty, string.Empty, Log.CleanStackTrace(ex.StackTrace),
449 new KeyValuePair<string, object>("Collection", Collection));
450
451 break;
452 }
453
454 if (++Count >= 100)
455 {
456 await Database.EndBulk();
457 await Database.StartBulk();
458 Count = 0;
459 }
460 }
461
462 ObjectsInBlock.Clear();
463 }
464
465 if (Cleared)
466 break;
467 }
468
469 if (Cleared)
470 {
471 await Database.Provider.Clear(Collection);
472 this.externalEvents?.RaiseCollectionCleared(Collection);
473 }
474 }
475 finally
476 {
477 await Database.EndBulk();
478 }
479
480 Tuple<uint, uint, uint, uint> Counts = await this.provider.Process(ObjectEvents, Collection);
481
482 Status.ObjectsAdded += Counts.Item1;
483 Status.ObjectsUpdated += Counts.Item2;
484 Status.ObjectsDeleted += Counts.Item3;
485 Status.ObjectErrors += Counts.Item4;
486
487 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
488 }
489 finally
490 {
491 await ObjectEvents.ClearAsync();
492 ObjectEvents.DeleteAndDispose();
493
494 Subset.Clear();
495 }
496 }
497
498 NodeStatus.LastBlockId = LastId;
500 }
501
502 foreach (BlockReference Ref in await Database.Find<BlockReference>(new FilterAnd(
503 new FilterFieldEqualTo("FileName", string.Empty),
504 new FilterFieldEqualTo("Creator", NodeStatus.BareJid),
505 new FilterFieldEqualTo("AccessDenied", false))))
506 {
507 if (Ref.Sources is null)
508 continue;
509
510 foreach (string Source in Ref.Sources)
511 {
512 if (PeerStatus.SynchCounter != SynchCounter || this.ClientDisposed)
513 return null;
514
515 try
516 {
517 Uri Uri = new Uri(Source);
518 if (Uri.Authority == NodeStatus.BareJid)
519 {
520 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
521 await this.RetrieveBlock(Ref, Source, Status, true);
522 break;
523 }
524 }
525 catch (Exception ex)
526 {
527 Log.Exception(ex);
528 Status.IncBlockError(NodeStatus.BareJid, ex.Message + " (1)");
529 }
530 }
531 }
532
533 foreach (BlockReference Ref in await Database.Find<BlockReference>(new FilterAnd(
534 new FilterFieldEqualTo("Creator", NodeStatus.BareJid),
535 new FilterFieldEqualTo("AccessDenied", false),
536 new FilterFieldEqualTo("Unpacked", false))))
537 {
538 if (Ref.Sources is null)
539 continue;
540
541 foreach (string Source in Ref.Sources)
542 {
543 if (PeerStatus.SynchCounter != SynchCounter || this.ClientDisposed)
544 return null;
545
546 try
547 {
548 Uri Uri = new Uri(Source);
549 if (Uri.Authority == NodeStatus.BareJid)
550 {
551 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
552 await this.RetrieveBlock(Ref, Source, Status, true);
553 break;
554 }
555 }
556 catch (Exception ex)
557 {
558 Log.Exception(ex);
559 Status.IncBlockError(NodeStatus.BareJid, ex.Message + " (2)");
560 }
561 }
562 }
563 }
564 catch (Exception ex)
565 {
566 Log.Exception(ex);
567 Status.IncBlockError(NodeStatus.BareJid, ex.Message + " (3)");
568 }
569 finally
570 {
571 if (SynchCounter == PeerStatus.SynchCounter)
572 {
573 lock (PeerStatus)
574 {
575 PeerStatus.IsSynchronizing = false;
576 }
577
578 Status.Done = true;
579 await this.UpdateStatus(DateTime.MinValue, Status, Callback);
580 }
581 }
582
583 return string.Empty;
584 }
585
586 private async Task BulkLoad(PeerStatus PeerStatus, KeyValuePair<BlockReference, string>[] Resources,
587 DateTime LastReported, SynchronizationStatus Status, CallbackAsync<SynchronizationStatus> Callback,
588 bool Unpack, List<BlockReference> Blocks)
589 {
590 string Msg;
591 int Processed = 0;
592
593 try
594 {
595 StringBuilder Csv = new StringBuilder();
596
597 foreach (KeyValuePair<BlockReference, string> Item in Resources)
598 {
599 this.client.Information("Retrieving block (" + Item.Key.Collection + "): " + Item.Value);
600
601 Csv.Append(Item.Value);
602 Csv.AppendLine(",application/octet-stream");
603 }
604
605 Uri Uri = new Uri("httpx://" + PeerStatus.BareJid + "/NL/MG");
607 Encoding.UTF8.GetBytes(Csv.ToString()), "text/csv; charset=utf-8",
608 new KeyValuePair<string, string>("Accept", "multipart/mixed"));
609
610 Response.AssertOk();
613
615 {
616 int i = 0;
617 int c = Resources.Length;
618
619 foreach (EmbeddedContent Content in Result.Content)
620 {
621 if (i >= c)
622 break;
623
624 LastReported = await this.UpdateStatus(LastReported, Status, Callback);
625 Processed++;
626
627 KeyValuePair<BlockReference, string> P = Resources[i++];
628 BlockReference Ref = P.Key;
629 string Url = P.Value;
630
631 if (!int.TryParse(Content.Description, out int StatusCode))
632 {
633 Status?.IncBlockError(Url, "Invalid content description: " + Content.Description);
634 continue;
635 }
636
637 if (StatusCode == 200)
638 {
639 byte[] Block = Content.TransferDecoded;
640 this.client.Information("Block retrieved (" + Ref.Collection + "): " + Url);
641
642 if (this.provider is null)
643 {
644 Log.Warning("Bulk loading of blocks aborted. Neuro-Ledger closed.");
645 return;
646 }
647
648 using (MemoryStream ms = new MemoryStream(Block))
649 {
650 await this.provider.AddBlockFile(ms, P.Key);
651 }
652
653 this.client.Information("Block added (" + Ref.Collection + "): " + Url);
654
655 if (Ref.AccessDenied)
656 {
657 Ref.AccessDenied = false;
658 await Database.Update(Ref);
659 }
660
661 if (!(Status is null))
662 {
663 Status.NrLoaded++;
664 Status.LoadedBytes += Ref.Bytes;
665 }
666
667 if (Unpack)
668 await this.UnpackObjects(Ref, Status);
669
670 Blocks.Add(Ref);
671 }
672 else if (StatusCode == HTTP.ForbiddenException.Code ||
673 StatusCode == HTTP.FailedDependencyException.Code)
674 {
675 this.client.Information("Access to block denied (" + Ref.Collection + "): " + Url + ". Cause: " + Content.Decoded?.ToString());
676
677 if (!Ref.AccessDenied)
678 {
679 Ref.AccessDenied = true;
680 await Database.Update(Ref);
681 }
682
683 if (!(Status is null))
684 Status.NrDenied++;
685 }
686 else if (StatusCode == HTTP.NotFoundException.Code)
687 {
688 this.client.Warning(Msg = "Block not found (" + Ref.Collection + "): " + Url + ". Cause: " + Content.Decoded?.ToString());
689
690 if (Ref.Creator == this.provider.ExternalIdentity &&
691 Content.Decoded is string ErrorMsg &&
693 {
694 await Database.Delete(Ref);
695
696 if (!(Status is null))
697 Status.NrBlocks--;
698 }
699 else
700 Status?.IncBlockError(Url, Msg);
701 }
702 else if (!this.ClientDisposed && !this.client.Disposed)
703 {
704 this.client.Warning(Msg = "Unable to retrieve block. Trying again later. Error reported: " + StatusCode.ToString() + ", " + Content.Decoded?.ToString());
705 this.scheduler?.Add(DateTime.Now.AddHours(1), this.RetryProcessing, new object[] { Ref, Url });
706
707 Status?.IncBlockError(Url, Msg);
708 }
709 }
710 }
711 }
712 catch (Exception ex)
713 {
714 this.client.Error(Msg = "Unable to retrieve blocks. Error reported: " + ex.Message);
715 Status?.IncBlockError(PeerStatus.BareJid, Msg, (uint)(Resources.Length - Processed));
716 }
717 }
718
719 private async Task<XmlElement> GetBlockList(string Jid, string LastBlockId, int MaxCount)
720 {
721 StringBuilder Url = new StringBuilder();
722
723 Url.Append("httpx://");
724 Url.Append(Jid);
725 Url.Append("/NL/L?Max=");
726 Url.Append(MaxCount.ToString());
727
728 if (!string.IsNullOrEmpty(LastBlockId))
729 {
730 Url.Append("&Last=");
731 Url.Append(LastBlockId);
732 }
733
734 using ContentStreamResponse Content = await InternetContent.GetTempStreamAsync(new Uri(Url.ToString()), DefaultTimeoutMs,
735 new KeyValuePair<string, string>("Accept", XmlCodec.DefaultContentType));
736 Content.AssertOk();
737
738 XmlDocument Doc = new XmlDocument();
739
740 Content.Encoded.Position = 0;
741 Doc.Load(Content.Encoded);
742
743 return Doc.DocumentElement;
744 }
745
746 private async Task<DateTime> UpdateStatus(DateTime LastReported, SynchronizationStatus Status, CallbackAsync<SynchronizationStatus> Callback)
747 {
748 DateTime TP = DateTime.Now;
749 if ((TP - LastReported).TotalSeconds > 1)
750 {
751 LastReported = TP;
752 if (!(Callback is null))
753 {
754 try
755 {
756 await Callback(Status);
757 }
758 catch (Exception ex)
759 {
760 Status?.IncBlockError(string.Empty, ex.Message);
761 }
762 }
763 }
764
765 return LastReported;
766 }
767
768 private async Task GetBlockReferences(object Sender, IqEventArgs e)
769 {
770 int Max = XML.Attribute(e.Query, "max", 50);
771
772 if (Max <= 0)
773 {
774 await e.IqError(new BadRequestException("Maximum number of records must be positive.", e.IQ));
775 return;
776 }
777
778 RosterItem Item = (Sender as XmppClient)?.GetRosterItem(e.FromBareJid);
779 if (Item is null || (Item.State != SubscriptionState.Both && Item.State != SubscriptionState.From))
780 {
781 await e.IqError(new ForbiddenException("You must have an active presence subscription to the node to request information.", e.IQ));
782 return;
783 }
784
785 if (Max > 50)
786 Max = 50;
787
788 IEnumerable<BlockReference> Blocks;
789 FilterCustom<BlockReference> CollectionFilter = new FilterCustom<BlockReference>((Ref) => Item.IsInGroup(Ref.Collection));
790 string Last = XML.Attribute(e.Query, "last");
791 int i;
792
793 if (string.IsNullOrEmpty(Last))
794 Blocks = await Database.Find<BlockReference>(0, Max + 1, CollectionFilter, "ObjectId");
795 else
796 {
797 Blocks = await Database.Find<BlockReference>(0, Max + 1,
798 new FilterAnd(new FilterFieldGreaterThan("ObjectId", Last), CollectionFilter), "ObjectId");
799 }
800
801 i = 0;
802 foreach (BlockReference Ref in Blocks)
803 i++;
804
805 StringBuilder Xml = new StringBuilder();
806
807 Xml.Append("<blockReferences xmlns=\"");
808 Xml.Append(NeuroLedgerNamespace);
809 Xml.Append('"');
810
811 if (i > Max)
812 Xml.Append(" more=\"true\"");
813
814 if (i > 0)
815 {
816 Xml.Append('>');
817
818 foreach (BlockReference Ref in Blocks)
819 {
820 if (--Max < 0)
821 break;
822
823 Xml.Append("<ref id='");
824 Xml.Append(Ref.ObjectId);
825
826 Xml.Append("' d='");
827 Xml.Append(Convert.ToBase64String(Ref.Digest));
828 Xml.Append("' s='");
829 Xml.Append(Convert.ToBase64String(Ref.Signature));
830
831 if (!(Ref.Link is null))
832 {
833 Xml.Append("' l='");
834 Xml.Append(Convert.ToBase64String(Ref.Link));
835 }
836
837 Xml.Append("' cn='");
838 Xml.Append(XML.Encode(Ref.Collection));
839 Xml.Append("' cr='");
840 Xml.Append(XML.Encode(Ref.Creator));
841 Xml.Append("' ct='");
842 Xml.Append(XML.Encode(Ref.Created));
843
844 if (Ref.Updated != DateTime.MinValue)
845 {
846 Xml.Append("' u='");
847 Xml.Append(XML.Encode(Ref.Updated));
848 }
849
850 if (Ref.Expires != DateTime.MaxValue)
851 {
852 Xml.Append("' x='");
853 Xml.Append(XML.Encode(Ref.Expires));
854 }
855
856 if (Ref.Status != BlockStatus.Valid)
857 {
858 Xml.Append("' t='");
859 Xml.Append(Ref.Status.ToString());
860 }
861
862 Xml.Append("' r='");
863 Xml.Append("httpx://");
864 Xml.Append(this.client.BareJID);
865 Xml.Append("/NL/B/");
866 Xml.Append(Base64Url.Encode(Ref.Digest));
867 Xml.Append("' b='");
868 Xml.Append(Ref.Bytes.ToString());
869 Xml.Append("'/>");
870 }
871
872 Xml.Append("</blockReferences>");
873 }
874 else
875 Xml.Append("/>");
876
877 await e.IqResult(Xml.ToString());
878 }
879
880 private Task Client_OnStateChanged(object Sender, XmppState NewState)
881 {
882 if (NewState == XmppState.Connected)
883 Task.Run(() => this.CheckOutgoingEvents());
884
885 return Task.CompletedTask;
886 }
887
888 private async Task CheckOutgoingEvents()
889 {
890 try
891 {
892 IEnumerable<OutgoingEvent> Events;
893
894 try
895 {
896 Events = await Database.Find<OutgoingEvent>();
897 }
898 catch (Exception ex)
899 {
900 Log.Exception(ex);
901 await Database.Clear("OutgoingPEP");
902 return;
903 }
904
905 foreach (OutgoingEvent Event in Events)
906 {
907 if (this.pepClient is null)
908 break;
909
910 await this.pepClient.PublishAsync(Event.Event);
911 await Database.Delete(Event);
912 }
913 }
914 catch (Exception ex)
915 {
916 Log.Exception(ex);
917 }
918 }
919
923 public override string[] Extensions => new string[] { };
924
928 public override void Dispose()
929 {
930 if (!(this.provider is null))
931 {
932 this.webServer.Unregister(this.blockResource);
933 this.webServer.Unregister(this.blockListResource);
934 this.webServer.Unregister(this.multiGetResource);
935
936 this.provider.BlockAdded -= this.BlockAdded;
937 this.provider.BlockDeleted -= this.BlockDeleted;
938
939 this.client.OnStateChanged -= this.Client_OnStateChanged;
940 this.client.OnPresence -= this.Client_OnPresence;
941 this.client.UnregisterIqGetHandler("getBlockReferences", NeuroLedgerNamespace, this.GetBlockReferences, true);
942
943 this.pepClient.UnregisterHandler(typeof(BlockAdded), this.BlockAddedReceived);
944 this.pepClient.UnregisterHandler(typeof(BlockDeleted), this.BlockDeletedReceived);
945
946 this.provider.BlockAdded -= this.BlockAdded;
947 this.provider.BlockDeleted -= this.BlockDeleted;
948
949 if (this.internalScheduler)
950 this.scheduler?.Dispose();
951
952 this.scheduler = null;
953
954 this.peerStatus?.Dispose();
955 this.peerStatus = null;
956
957 this.provider = null;
958 this.pepClient = null;
959 }
960
961 base.Dispose();
962 }
963
964 private async Task Queue(NeuroLedgerPepEvent Event)
965 {
966 try
967 {
968 await Database.Insert(new OutgoingEvent()
969 {
970 Event = Event
971 });
972 }
973 catch (Exception ex)
974 {
975 Log.Exception(ex);
976 }
977 }
978
979 private Task BlockAdded(object Sender, BlockReferenceEventArgs e)
980 {
982 Task T;
983
984 this.Fill(Event, e);
985
986 if (this.pepClient.Client.State == XmppState.Connected)
987 {
988 this.pepClient.Publish(Event, (sender, e2) =>
989 {
990 if (!e2.Ok)
991 T = this.Queue(Event);
992
993 return Task.CompletedTask;
994
995 }, null);
996 }
997 else
998 T = this.Queue(Event);
999
1000 return Task.CompletedTask;
1001 }
1002
1003 private void Fill(BlockEvent Event, BlockReferenceEventArgs e)
1004 {
1005 BlockReference Block = e.Block;
1006
1007 Event.Collection = Block.Collection;
1008 Event.Created = Block.Created;
1009 Event.Creator = Block.Creator;
1010 Event.Digest = Block.Digest;
1011 Event.Expires = Block.Expires;
1012 Event.Link = Block.Link;
1013 Event.Signature = Block.Signature;
1014 Event.Status = Block.Status;
1015 Event.Updated = Block.Updated;
1016 Event.Bytes = Block.Bytes;
1017 Event.Url = "httpx://" + this.client.BareJID + "/NL/B/" + Base64Url.Encode(Block.Digest);
1018 }
1019
1020 private Task BlockDeleted(object Sender, BlockReferenceEventArgs e)
1021 {
1023 Task T;
1024
1025 this.Fill(Event, e);
1026
1027 if (this.pepClient.Client.State == XmppState.Connected)
1028 {
1029 this.pepClient.Publish(Event, (sender, e2) =>
1030 {
1031 if (!e2.Ok)
1032 T = this.Queue(Event);
1033
1034 return Task.CompletedTask;
1035
1036 }, null);
1037 }
1038 else
1039 T = this.Queue(Event);
1040
1041 return Task.CompletedTask;
1042 }
1043
1044 private async Task BlockAddedReceived(object Sender, PersonalEventNotificationEventArgs e)
1045 {
1047 {
1048 lock (this.processingQueue)
1049 {
1050 if (this.processing)
1051 {
1052 this.processingQueue.AddLast(Event);
1053 return;
1054 }
1055
1056 this.processing = true;
1057 }
1058
1059 do
1060 {
1061 try
1062 {
1063 await this.Process(Event, null);
1064 }
1065 catch (ForbiddenException)
1066 {
1067 // Node not authorized to access blocks from the corresponding collection. Ignore error.
1068 }
1069 catch (Exception ex)
1070 {
1071 Log.Exception(ex);
1072 }
1073
1074 lock (this.processingQueue)
1075 {
1076 if (this.processingQueue.First is null)
1077 {
1078 Event = null;
1079 this.processing = false;
1080 }
1081 else
1082 {
1083 Event = this.processingQueue.First.Value;
1084 this.processingQueue.RemoveFirst();
1085 }
1086 }
1087 }
1088 while (!(Event is null));
1089 }
1090 }
1091
1092 private readonly LinkedList<BlockAdded> processingQueue = new LinkedList<BlockAdded>();
1093 private bool processing = false;
1094
1095 private async Task Process(BlockAdded Event, SynchronizationStatus Status)
1096 {
1097 BlockReference Ref = await this.GetReference(Event, Status, true);
1098 if (!(Ref is null))
1099 await this.RetrieveBlock(Ref, Event.Url, Status, true);
1100 }
1101
1102 private async Task<BlockReference> GetReference(BlockAdded Event, SynchronizationStatus Status, bool NullIfNotChanged)
1103 {
1104 if (!(Status is null))
1105 {
1106 Status.NrBlocks++;
1107 Status.TotalBytes += Event.Bytes;
1108
1109 if (Event.Created < Status.First)
1110 Status.First = Event.Created;
1111
1112 if (Event.Created > Status.Last)
1113 Status.Last = Event.Created;
1114 }
1115
1117
1118 if (Ref is null)
1119 {
1120 Ref = new BlockReference()
1121 {
1122 Collection = Event.Collection,
1123 Created = Event.Created,
1124 Creator = Event.Creator,
1125 Digest = Event.Digest,
1126 Expires = Event.Expires,
1127 Link = Event.Link,
1128 Signature = Event.Signature,
1129 Status = Event.Status,
1130 Updated = Event.Updated,
1131 Bytes = Event.Bytes,
1132 Sources = new string[] { Event.Url },
1133 AccessDenied = false,
1134 Unpacked = false
1135 };
1136
1137 await Database.Insert(Ref);
1138
1139 if (!(Status is null))
1140 Status.NrNew++;
1141
1142 return Ref;
1143 }
1144 else
1145 {
1146 bool Updated = false;
1147 bool Changed = false;
1148
1149 if (Ref.Creator != this.provider?.ExternalIdentity &&
1150 (Ref.Collection != Event.Collection ||
1151 !NeuroLedgerProvider.AreEqual(Ref.Created, Event.Created) ||
1152 !NeuroLedgerProvider.AreEqual(Ref.Updated, Event.Updated) ||
1153 !NeuroLedgerProvider.AreEqual(Ref.Expires, Event.Expires) ||
1154 Ref.Creator != Event.Creator ||
1155 !Compare(Ref.Digest, Event.Digest) ||
1156 !Compare(Ref.Link, Event.Link) ||
1157 !Compare(Ref.Signature, Event.Signature) ||
1158 Ref.Status != Event.Status ||
1159 Ref.Bytes != Event.Bytes ||
1160 Ref.AccessDenied))
1161 {
1162 Ref.Collection = Event.Collection;
1163 Ref.Created = Event.Created;
1164 Ref.Creator = Event.Creator;
1165 Ref.Expires = Event.Expires;
1166 Ref.Digest = Event.Digest;
1167 Ref.Link = Event.Link;
1168 Ref.Signature = Event.Signature;
1169 Ref.Status = Event.Status;
1170 Ref.Updated = Event.Updated;
1171 Ref.Bytes = Event.Bytes;
1172 Ref.AccessDenied = false;
1173
1174 Changed = true;
1175 }
1176
1177 if (Ref.Sources is null || Array.IndexOf(Ref.Sources, Event.Url) < 0)
1178 {
1179 int c = Ref.Sources?.Length ?? 0;
1180
1181 string[] s = new string[c + 1];
1182 if (c > 0)
1183 Array.Copy(Ref.Sources, 0, s, 0, c);
1184
1185 s[c] = Event.Url;
1186 Ref.Sources = s;
1187 Updated = true;
1188 }
1189
1190 if (Updated || Changed)
1191 {
1192 await Database.Update(Ref);
1193
1194 if (!(Status is null))
1195 Status.NrUpdated++;
1196
1197 if (Changed || !NullIfNotChanged)
1198 return Ref;
1199 else
1200 return null;
1201 }
1202 else if (string.IsNullOrEmpty(Ref.FileName) || !NullIfNotChanged)
1203 return Ref;
1204 else
1205 return null;
1206 }
1207 }
1208
1209 private static bool Compare(byte[] A1, byte[] A2)
1210 {
1211 if ((A1 is null) ^ (A2 is null))
1212 return false;
1213
1214 if (A1 is null)
1215 return true;
1216
1217 int i, c = A1.Length;
1218 if (A2.Length != c)
1219 return false;
1220
1221 for (i = 0; i < c; i++)
1222 {
1223 if (A1[i] != A2[i])
1224 return false;
1225 }
1226
1227 return true;
1228 }
1229
1230 internal async Task<bool> RetrieveBlock(BlockReference Ref, string Url, SynchronizationStatus Status, bool Unpack)
1231 {
1232 string FileName = this.provider?.GetFullFileName(Ref.FileName);
1233
1234 if (string.IsNullOrEmpty(Ref.FileName) || !File.Exists(FileName))
1235 {
1236 string Key = Convert.ToBase64String(Ref.Digest);
1237
1238 lock (this.loading)
1239 {
1240 if (this.loading.ContainsKey(Key))
1241 return true;
1242
1243 this.loading[Key] = true;
1244 }
1245
1246 try
1247 {
1248 this.client.Information("Retrieving block (" + Ref.Collection + "): " + Url);
1249
1251 new KeyValuePair<string, string>("Accept", "application/octet-stream"));
1252 Content.AssertOk();
1253
1254 this.client.Information("Block retrieved (" + Ref.Collection + "): " + Url);
1255
1256 await this.provider.AddBlockFile(Content.Encoded, Ref);
1257
1258 this.client.Information("Block added (" + Ref.Collection + "): " + Url);
1259
1260 if (Ref.AccessDenied)
1261 {
1262 Ref.AccessDenied = false;
1263 await Database.Update(Ref);
1264 }
1265
1266 if (!(Status is null))
1267 {
1268 Status.NrLoaded++;
1269 Status.LoadedBytes += Ref.Bytes;
1270 }
1271
1272 if (Unpack)
1273 await this.UnpackObjects(Ref, Status);
1274
1275 return true;
1276 }
1277 catch (HTTP.ForbiddenException ex)
1278 {
1279 string ErrorMessage = await this.GetErrorMessage(ex);
1280
1281 this.client.Information("Access to block denied (" + Ref.Collection + "): " + Url + ". Cause: " + ErrorMessage);
1282
1283 if (!Ref.AccessDenied)
1284 {
1285 Ref.AccessDenied = true;
1286 await Database.Update(Ref);
1287 }
1288
1289 if (!(Status is null))
1290 Status.NrDenied++;
1291
1292 return false;
1293 }
1294 catch (HTTP.FailedDependencyException ex)
1295 {
1296 string ErrorMessage = await this.GetErrorMessage(ex);
1297
1298 this.client.Information("Access to block denied (" + Ref.Collection + "): " + Url + ". Cause: " + ErrorMessage);
1299
1300 if (!Ref.AccessDenied)
1301 {
1302 Ref.AccessDenied = true;
1303 await Database.Update(Ref);
1304 }
1305
1306 if (!(Status is null))
1307 Status.NrDenied++;
1308
1309 return false;
1310 }
1311 catch (HTTP.NotFoundException ex)
1312 {
1313 string ErrorMessage = await this.GetErrorMessage(ex);
1314
1316 {
1317 this.client.Warning("Block not found (" + Ref.Collection + "): " + Url + ". Cause: " + ErrorMessage);
1318
1319 await Database.Delete(Ref);
1320
1321 if (!(Status is null))
1322 Status.NrBlocks--;
1323 }
1324
1325 return false;
1326 }
1327 catch (Exception ex)
1328 {
1329 this.client.Warning("Unable to retrieve block. Trying again later. Error reported: " + ex.Message + " (" + ex.GetType().FullName + ")");
1330 this.scheduler?.Add(DateTime.Now.AddHours(1), this.RetryProcessing, new object[] { Ref, Url });
1331
1332 Status?.IncBlockError(Url, ex.Message);
1333
1334 return false;
1335 }
1336 finally
1337 {
1338 lock (this.loading)
1339 {
1340 this.loading.Remove(Key);
1341 }
1342 }
1343 }
1344 else
1345 return true;
1346 }
1347
1348 private async Task<string> GetErrorMessage(HTTP.HttpException ex)
1349 {
1350 object ContentObject = await ex.GetContentObjectAsync();
1351 if (ContentObject is string s)
1352 return s;
1353 else if (!(ex.Content is null))
1354 return Encoding.UTF8.GetString(ex.Content);
1355 else
1356 return ex.Message;
1357 }
1358
1359 private async Task UnpackObjects(BlockReference Ref, SynchronizationStatus Status)
1360 {
1361 using (BlockEnumerator TempBlockEnumerator = new BlockEnumerator(Ref, this.provider))
1362 {
1363 using ObjectEnumerator<GenericObject> e = await ObjectEnumerator<GenericObject>.Create(TempBlockEnumerator, this.provider);
1364 Dictionary<string, bool> UnpackType = new Dictionary<string, bool>();
1365 int NrAdded = 0;
1366 int NrUpdated = 0;
1367 int NrDeleted = 0;
1368 int NrErrors = 0;
1369 int NrIgnored = 0;
1370
1371 await Database.StartBulk();
1372 try
1373 {
1374 while (await e.MoveNextAsync())
1375 {
1376 GenericObject Obj = e.Current;
1377
1378 if (!UnpackType.TryGetValue(Obj.TypeName, out bool UnpackObject))
1379 {
1380 Type T = Types.GetType(Obj.TypeName);
1381 UnpackObject = (T is null) || !(T.GetCustomAttribute(typeof(ArchivingTimeAttribute)) is null);
1382 UnpackType[Obj.TypeName] = UnpackObject;
1383 }
1384
1385 if (!UnpackObject)
1386 {
1387 NrIgnored++;
1388 continue;
1389 }
1390
1391 switch (e.CurrentEntry.Type)
1392 {
1393 case EntryType.New:
1394 try
1395 {
1396 await Database.Provider.Insert(Obj); // Calling Provider method avoids raising event.
1397 NrAdded++;
1398
1399 this.externalEvents?.RaiseEntryAdded(Obj);
1400
1401 if (!(Status is null))
1402 Status.ObjectsAdded++;
1403 }
1405 {
1406 // Object already exists, perhaps in a newer state. Keep that version for now.
1407 }
1408 catch (Exception ex)
1409 {
1410 this.client.Error(ex.Message);
1411 NrErrors++; // Object already exists locally.
1412
1413 if (!(Status is null))
1414 Status.ObjectErrors++;
1415 }
1416 break;
1417
1418 case EntryType.Update:
1419 try
1420 {
1421 await Database.Provider.Update(Obj); // Calling Provider method avoids raising event.
1422 NrUpdated++;
1423
1424 this.externalEvents?.RaiseEntryUpdated(Obj);
1425
1426 if (!(Status is null))
1427 Status.ObjectsUpdated++;
1428 }
1429 catch (KeyNotFoundException)
1430 {
1431 try
1432 {
1433 await Database.Provider.Insert(Obj); // Calling Provider method avoids raising event.
1434 NrAdded++;
1435
1436 this.externalEvents?.RaiseEntryAdded(Obj);
1437
1438 if (!(Status is null))
1439 Status.ObjectsAdded++;
1440 }
1441 catch (Exception ex)
1442 {
1443 this.client.Error(ex.Message);
1444 NrErrors++;
1445
1446 if (!(Status is null))
1447 Status.ObjectErrors++;
1448 }
1449 }
1450 catch (Exception ex)
1451 {
1452 this.client.Error(ex.Message);
1453 NrErrors++;
1454
1455 if (!(Status is null))
1456 Status.ObjectErrors++;
1457 }
1458 break;
1459
1460 case EntryType.Delete:
1461 try
1462 {
1463 await Database.Provider.Delete(Obj); // Calling Provider method avoids raising event.
1464 NrDeleted++;
1465
1466 this.externalEvents?.RaiseEntryDeleted(Obj);
1467
1468 if (!(Status is null))
1469 Status.ObjectsDeleted++;
1470 }
1471 catch (KeyNotFoundException)
1472 {
1473 // Already deleted.
1474 }
1475 catch (Exception ex)
1476 {
1477 this.client.Error(ex.Message);
1478 NrErrors++; // Object does not exist locally.
1479
1480 if (!(Status is null))
1481 Status.ObjectErrors++;
1482 }
1483 break;
1484
1485 case EntryType.Clear:
1486 //await Database.Provider.Clear(Ref.Collection); // Calling Provider method avoids raising event.
1487 //this.client.Information("Collection cleared: " + Ref.Collection);
1488 //
1489 //NrAdded = 0;
1490 //NrUpdated = 0;
1491 //NrDeleted = 0;
1492 //NrErrors = 0;
1493
1494 // TODO: Clear only objects from corresponding collection & creator. Also raise corresponding events.
1495 continue;
1496
1497 default:
1498 continue;
1499 }
1500 }
1501 }
1502 finally
1503 {
1504 await Database.EndBulk();
1505 }
1506
1507 if (NrAdded > 0)
1508 this.client.Information("Number of objects added to " + Ref.Collection + ": " + NrAdded.ToString());
1509
1510 if (NrUpdated > 0)
1511 this.client.Information("Number of objects updated in " + Ref.Collection + ": " + NrUpdated.ToString());
1512
1513 if (NrDeleted > 0)
1514 this.client.Information("Number of objects deleted from " + Ref.Collection + ": " + NrDeleted.ToString());
1515
1516 if (NrErrors > 0)
1517 this.client.Information("Number of errors related to " + Ref.Collection + ": " + NrErrors.ToString());
1518
1519 if (NrIgnored > 0)
1520 this.client.Information("Number of objects ignored in " + Ref.Collection + ": " + NrIgnored.ToString());
1521 }
1522
1523 if (!Ref.Unpacked)
1524 {
1525 Ref.Unpacked = true;
1526 await Database.Update(Ref);
1527 }
1528 }
1529
1530 private async void RetryProcessing(object State)
1531 {
1532 try
1533 {
1534 if (this.ClientDisposed)
1535 return;
1536
1537 object[] P = (object[])State;
1538 BlockReference Ref = (BlockReference)P[0];
1539 string Url = (string)P[1];
1540
1541 RosterItem Item = this.client[Ref.Creator];
1542 if (Item is null)
1543 return;
1544
1545 foreach (PresenceEventArgs e in Item.Resources)
1546 {
1547 PeerStatus PeerStatus = await this.GetPeerStatus(e);
1548 if (!(PeerStatus is null) && PeerStatus.IsNeuroLedger)
1549 {
1550 await this.RetrieveBlock(Ref, Url, null, true);
1551 return;
1552 }
1553 }
1554 }
1555 catch (Exception ex)
1556 {
1557 Log.Exception(ex);
1558 }
1559 }
1560
1561 private Task BlockDeletedReceived(object Sender, PersonalEventNotificationEventArgs e)
1562 {
1563 if (e.PersonalEvent is BlockDeleted)
1564 {
1565 // TODO: Delete BlockReference
1566 // TODO: Delete Objects
1567 }
1568
1569 return Task.CompletedTask;
1570 }
1571
1575 public Task ProcessBlockRequest(HTTP.HeaderFields.HttpFieldAccept Accept, HTTP.HttpResponse Response, BlockReference Ref)
1576 {
1577 return this.blockResource.ProcessRequest(Accept, Response, Ref);
1578 }
1579
1580 }
1581}
Static class that does BASE64URL encoding (using URL and filename safe alphabet), as defined in RFC46...
Definition: Base64Url.cs:11
static string Encode(byte[] Data)
Converts a binary block of data to a Base64URL-encoded string.
Definition: Base64Url.cs:48
Contains information about a binary response to a content request.
string ContentType
Internet Content-Type of encoded object.
void AssertOk()
Asserts response is OK.
Contains information about a response to a content request.
object Decoded
Decoded object.
void AssertOk()
Asserts response is OK.
Contains information about a stream response to a content request.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
static Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, given its URI.
static Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Represents content embedded in other content.
string Description
Content-Description of embedded object, if defined.
object Decoded
Decoded body of embedded object. ContentType defines how TransferDecoded is transformed into Decoded.
byte[] TransferDecoded
Transformed body of embedded object. TransferEncoding defines how Raw is transformed into TransferDec...
Represents mixed content, encoded with multipart/mixed
Definition: MixedContent.cs:7
XML encoder/decoder.
Definition: XmlCodec.cs:19
const string DefaultContentType
Default content type for XML documents.
Definition: XmlCodec.cs:30
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
Class representing an event.
Definition: Event.cs:11
Event(DateTime Timestamp, EventType Type, string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Class representing an event.
Definition: Event.cs:39
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
void Warning(string Warning)
Called to inform the viewer of a warning state.
void Information(string Comment)
Called to inform the viewer of something.
Base class for all HTTP resources.
Definition: HttpResource.cs:23
Implements an HTTP server.
Definition: HttpServer.cs:41
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
async Task IqError(string Xml)
Returns an error response to the current request.
Definition: IqEventArgs.cs:208
async Task IqResult(string Xml)
Returns a response to the current request.
Definition: IqEventArgs.cs:194
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:119
string FromBareJid
Bare version of the "from" JID.
Definition: IqEventArgs.cs:157
Event arguments for presence events.
string EntityCapabilityHashFunction
Hash function used in calculation of entity capabilities version of sender. Entity capabilities are d...
string EntityCapabilityNode
Node of entity capabilities of sender. Entity capabilities are defined in XEP-0115: http://xmpp....
bool HasEntityCapabilities
If the presence stanza includes entity capabilities information. Entity capabilities are defined in X...
string EntityCapabilityVersion
Version of entity capabilities of sender. Entity capabilities are defined in XEP-0115: http://xmpp....
string From
From where the presence was received.
string FromBareJID
Bare JID of resource sending the presence.
XmppClient Client
XMPP Client. Is null if event raised by a component.
async Task< string > SynchronizePeer(PeerStatus PeerStatus, bool StartFromBeginning, CallbackAsync< SynchronizationStatus > Callback)
Synchronizes blocks hosted by a peer.
const int DefaultTimeoutMs
Default timeout = 2 minutes.
Task ProcessBlockRequest(HTTP.HeaderFields.HttpFieldAccept Accept, HTTP.HttpResponse Response, BlockReference Ref)
TODO
NeuroLedgerProvider Provider
Neuro-Ledger provider.
override string[] Extensions
Implemented extensions.
void StopSynchronization(PeerStatus PeerStatus)
Stops synchronization, if such is in progress.
NeuroLedgerClient(XmppClient Client, EndpointSecurity E2eEncryption, HTTP.HttpServer WebServer, NeuroLedgerProvider Provider)
Neuro-Ledger XMPP Client
async Task< PeerStatus > GetPeerStatus(PresenceEventArgs e)
Gets status information related to a peer.
Contains information about current synchronization status for a node in the network.
Definition: NodeStatus.cs:13
Contains information about current synchronization status for a peer in the network.
Definition: PeerStatus.cs:13
int SynchCounter
Synchronization Counter
Definition: PeerStatus.cs:137
bool IsSynchronizing
If a synchronizating process is underway.
Definition: PeerStatus.cs:128
string RequestingCapabilitiesFunction
Entity RequestingCapabilities Function being requested
Definition: PeerStatus.cs:101
string RequestingCapabilitiesNode
Entity RequestingCapabilities Node being requested
Definition: PeerStatus.cs:92
string CapabilitiesFunction
Entity Capabilities Function
Definition: PeerStatus.cs:74
string RequestingCapabilitiesVersion
Entity Capabilities Version being requested
Definition: PeerStatus.cs:83
bool IsNeuroLedger
If the peer is a Neuro-Ledger node.
Definition: PeerStatus.cs:119
string CapabilitiesVersion
Entity Capabilities Version
Definition: PeerStatus.cs:56
string CapabilitiesNode
Entity Capabilities Node
Definition: PeerStatus.cs:65
Event raised when a block has been added.
Definition: BlockAdded.cs:14
Event raised when a block has been deleted.
Definition: BlockDeleted.cs:14
Abstract base class for Neuro-Ledger block PEP events.
Definition: BlockEvent.cs:13
Abstract base class for Neuro-Ledger PEP events.
Provides authenticated and authorized clients with lists of available blocks.
Provides authenticated and authorized clients with binary blocks.
const string ErrorMsg_BlockFileHasBeenRemoved
Block file has been removed.
Allows a client to get multiple resources in one call
Class managing end-to-end encryption.
IPersonalEvent PersonalEvent
Parsed personal event, if appropriate type was found.
Client managing the Personal Eventing Protocol (XEP-0163). https://xmpp.org/extensions/xep-0163....
Definition: PepClient.cs:19
Task Publish(string Node, EventHandlerAsync< ItemResultEventArgs > Callback, object State)
Publishes an item on a node.
Definition: PepClient.cs:110
void RegisterHandler(Type PersonalEventType, EventHandlerAsync< PersonalEventNotificationEventArgs > Handler)
Registers an event handler of a specific type of personal events.
Definition: PepClient.cs:345
async Task< string > PublishAsync(string Node)
Publishes an item on a node.
Definition: PepClient.cs:162
bool UnregisterHandler(Type PersonalEventType, EventHandlerAsync< PersonalEventNotificationEventArgs > Handler)
Unregisters an event handler of a specific type of personal events.
Definition: PepClient.cs:380
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
Contains information about an item of an entity.
Definition: Item.cs:11
The sender has sent a stanza containing XML that does not conform to the appropriate schema or that c...
The requesting entity does not possess the necessary permissions to perform an action that only certa...
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
XmppState State
Current state of connection.
Definition: XmppClient.cs:985
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsClientFeature)
Unregisters an IQ-Get handler.
Definition: XmppClient.cs:2802
IXmppExtension[] Extensions
Registered extensions.
Definition: XmppClient.cs:7375
bool Disposed
If the client has been disposed.
Definition: XmppClient.cs:1173
Task< ServiceDiscoveryEventArgs > ServiceDiscoveryAsync(string To)
Performs an asynchronous service discovery request
Definition: XmppClient.cs:6060
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsClientFeature)
Registers an IQ-Get handler.
Definition: XmppClient.cs:2736
RosterItem[] Roster
Items in the roster.
Definition: XmppClient.cs:4720
Base class for XMPP Extensions.
bool ClientDisposed
If the client has been disposed.
XmppClient client
XMPP Client used by the extension.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
XmppClient Client
XMPP Client.
This attribute defines that objects of this type can be archived, and the time objects can be archive...
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 Clear(string CollectionName)
Clears a collection of all objects.
Definition: Database.cs:1965
An attempt to insert a key was done, but the key was already there.
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
Custom filter used to filter objects using an external expression.
Definition: FilterCustom.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 than a given value.
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
Event arguments for block reference events.
Optimizes a persistent IPersistentDictionary using a cache.
EntryType Type
Entry Type
Definition: Entry.cs:32
async Task AddBlockFile(Stream File, BlockReference BlockReference)
Adds a block file to the ledger.
static bool AreEqual(DateTime TP1, DateTime TP2)
Compares two timestamps, to the millisecond (but not tick) level.
ILedgerExternalEvents ExternalEvents
Interface for reporting external events.
static Task< BlockReference > FindReference(byte[] Digest)
Finds a BlockReference object related to a block, given its digest.
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...
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.
bool Unpacked
If objects in the block have been unpacked.
Generic object. Contains a sequence of properties.
Implements an in-memory cache.
Definition: Cache.cs:17
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
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
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:14
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:34
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
Definition: Scheduler.cs:54
Task Update(object Object)
Updates an object in the database.
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.
Persistent dictionary that can contain more entries than possible in the internal memory.
Task ClearAsync()
Clears the dictionary.
Definition: ImplTypes.g.cs:58
EventLevel
Event level.
Definition: EventLevel.cs:7
SubscriptionState
State of a presence subscription.
Definition: RosterItem.cs:16
XmppState
State of XMPP connection.
Definition: XmppState.cs:7
BlockStatus
Status of the block.
Definition: BlockHeader.cs:12
EntryType
Ledger entry type.
Definition: ILedgerEntry.cs:9