Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NotificationService.cs
1using System.Diagnostics.CodeAnalysis;
2using Waher.Events;
5
7{
11 [Singleton]
13 {
14 private const int nrTypes = 4;
15
16 private readonly SortedDictionary<CaseInsensitiveString, List<NotificationEvent>>[] events;
17 private readonly LinkedList<KeyValuePair<Type, DateTime>> expected;
18
23 {
24 int i;
25
26 this.events = new SortedDictionary<CaseInsensitiveString, List<NotificationEvent>>[nrTypes];
27 this.expected = new LinkedList<KeyValuePair<Type, DateTime>>();
28
29 for (i = 0; i < nrTypes; i++)
30 this.events[i] = [];
31 }
32
38 public override async Task Load(bool isResuming, CancellationToken cancellationToken)
39 {
40 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>>? ByCategory = null;
41 List<NotificationEvent>? Events = null;
42 string? PrevCategory = null;
43 int PrevType = -1;
44 int Type;
45
46 IEnumerable<NotificationEvent> LoadedEvents;
47
48 try
49 {
50 LoadedEvents = await Database.Find<NotificationEvent>("Type", "Category");
51 }
52 catch (Exception ex)
53 {
54 ServiceRef.LogService.LogException(ex);
55
56 await Database.Clear("Notifications");
57 LoadedEvents = [];
58 }
59
60 foreach (NotificationEvent Event in LoadedEvents)
61 {
62 if (Event.Type is null || Event.Category is null)
63 continue;
64
65 Type = (int)Event.Type;
66 if (Type < 0 || Type >= nrTypes)
67 continue;
68
70 {
71 Log.Debug("Notification event of type " + Event.GetType().FullName + " lacked Category.");
72 await Database.Delete(Event);
73 continue;
74 }
75
76 lock (this.events)
77 {
78 if (ByCategory is null || Type != PrevType)
79 {
80 ByCategory = this.events[Type];
81 PrevType = Type;
82 }
83
84 if (Events is null || Event.Category != PrevCategory)
85 {
86 if (!ByCategory.TryGetValue(Event.Category, out Events))
87 {
88 Events = [];
89 ByCategory[Event.Category] = Events;
90 }
91
92 PrevCategory = Event.Category;
93 }
94
95 Events.Add(Event);
96 }
97 }
98
99 await base.Load(isResuming, cancellationToken);
100 }
101
107 public void ExpectEvent<T>(DateTime Before)
108 where T : NotificationEvent
109 {
110 lock (this.expected)
111 {
112 this.expected.AddLast(new KeyValuePair<Type, DateTime>(typeof(T), Before));
113 }
114 }
115
121 {
122 if (Event.Type is null || Event.Category is null)
123 return;
124
125 DateTime Now = DateTime.Now;
126 bool Expected = false;
127
128 lock (this.expected)
129 {
130 LinkedListNode<KeyValuePair<Type, DateTime>>? Loop = this.expected.First;
131 LinkedListNode<KeyValuePair<Type, DateTime>>? Next;
132 Type EventType = Event.GetType();
133
134 while (Loop is not null)
135 {
136 Next = Loop.Next;
137
138 if (Loop.Value.Value < Now)
139 this.expected.Remove(Loop);
140 else if (Loop.Value.Key == EventType)
141 {
142 this.expected.Remove(Loop);
143 Expected = true;
144 break;
145 }
146
147 Loop = Next;
148 }
149 }
150
151 if (Expected)
152 {
153 MainThread.BeginInvokeOnMainThread(async () =>
154 {
155 try
156 {
157 await Event.Open();
158 }
159 catch (Exception ex)
160 {
161 ServiceRef.LogService.LogException(ex);
162 }
163 });
164 }
165 else
166 {
167 await Database.Insert(Event);
168
169 int Type = (int)Event.Type;
170 if (Type >= 0 && Type < nrTypes)
171 {
172 lock (this.events)
173 {
174 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[Type];
175
176 if (!ByCategory.TryGetValue(Event.Category, out List<NotificationEvent>? Events))
177 {
178 Events = [];
179 ByCategory[Event.Category] = Events;
180 }
181
182 Events.Add(Event);
183 }
184
185 try
186 {
187 this.OnNewNotification?.Invoke(this, new NotificationEventArgs(Event));
188 }
189 catch (Exception ex)
190 {
191 ServiceRef.LogService.LogException(ex);
192 }
193 }
194
195 Task _ = Task.Run(async () =>
196 {
197 try
198 {
199 await Event.Prepare();
200 }
201 catch (Exception ex)
202 {
203 ServiceRef.LogService.LogException(ex);
204 }
205 });
206 }
207 }
208
215 {
216 int TypeIndex = (int)Type;
217
218 if (TypeIndex >= 0 && TypeIndex < nrTypes)
219 {
220 NotificationEvent[] ToDelete;
221
222 lock (this.events)
223 {
224 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[TypeIndex];
225
226 if (!ByCategory.TryGetValue(Category, out List<NotificationEvent>? Events))
227 return;
228
229 ToDelete = [.. Events];
230 ByCategory.Remove(Category);
231 }
232
233 await this.DoDeleteEvents(ToDelete);
234 }
235 }
236
241 public Task DeleteEvents(params NotificationEvent[] Events)
242 {
243 foreach (NotificationEvent Event in Events)
244 {
245 if (Event.Type is null || Event.Category is null)
246 continue;
247
248 int TypeIndex = (int)Event.Type;
249
250 if (TypeIndex >= 0 && TypeIndex < nrTypes)
251 {
252 lock (this.events)
253 {
254 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[TypeIndex];
255
256 if (ByCategory.TryGetValue(Event.Category, out List<NotificationEvent>? List) &&
257 List.Remove(Event) &&
258 List.Count == 0)
259 {
260 ByCategory.Remove(Event.Category);
261 }
262 }
263 }
264
265 }
266
267 return this.DoDeleteEvents(Events);
268 }
269
270 private async Task DoDeleteEvents(NotificationEvent[] Events)
271 {
272 try
273 {
274 await Database.StartBulk();
275
276 try
277 {
278 foreach (NotificationEvent Event in Events)
279 {
280 try
281 {
282 await Database.Delete(Event);
283 }
284 catch (KeyNotFoundException)
285 {
286 // Ignore, already deleted.
287 }
288 }
289 }
290 finally
291 {
292 await Database.EndBulk();
293 }
294
295 this.OnNotificationsDeleted?.Invoke(this, new NotificationEventsArgs(Events));
296 }
297 catch (Exception ex)
298 {
299 ServiceRef.LogService.LogException(ex);
300 }
301 }
302
309 {
310 int i = (int)Type;
311 if (i < 0 || i >= nrTypes)
312 return [];
313
314 lock (this.events)
315 {
316 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[i];
317 List<NotificationEvent> Result = [];
318
319 foreach (List<NotificationEvent> Events in ByCategory.Values)
320 Result.AddRange(Events);
321
322 return [.. Result];
323 }
324 }
325
332 {
333 int i = (int)Type;
334 if (i < 0 || i >= nrTypes)
335 return [];
336
337 lock (this.events)
338 {
339 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[i];
340 List<CaseInsensitiveString> Result = [.. ByCategory.Keys];
341
342 return [.. Result];
343 }
344 }
345
351 public SortedDictionary<CaseInsensitiveString, NotificationEvent[]> GetEventsByCategory(NotificationEventType Type)
352 {
353 int i = (int)Type;
354 if (i < 0 || i >= nrTypes)
355 return [];
356
357 lock (this.events)
358 {
359 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[i];
360 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> Result = [];
361
362 foreach (KeyValuePair<CaseInsensitiveString, List<NotificationEvent>> P in ByCategory)
363 Result[P.Key] = [.. P.Value];
364
365 return Result;
366 }
367 }
368
374 public SortedDictionary<CaseInsensitiveString, T[]> GetEventsByCategory<T>(NotificationEventType Type)
375 where T : NotificationEvent
376 {
377 int i = (int)Type;
378 if (i < 0 || i >= nrTypes)
379 return [];
380
381 lock (this.events)
382 {
383 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[i];
384 SortedDictionary<CaseInsensitiveString, T[]> Result = [];
385
386 foreach (KeyValuePair<CaseInsensitiveString, List<NotificationEvent>> P in ByCategory)
387 {
388 List<T>? Items = null;
389
390 foreach (NotificationEvent Event in P.Value)
391 {
392 if (Event is T TypedItem)
393 {
394 Items ??= [];
395 Items.Add(TypedItem);
396 }
397 }
398
399 if (Items is not null)
400 Result[P.Key] = [.. Items];
401 }
402
403 return Result;
404 }
405 }
406
415 [NotNullWhen(true)] out NotificationEvent[]? Events)
416 {
417 int i = (int)Type;
418 if (i < 0 || i >= nrTypes)
419 {
420 Events = null;
421 return false;
422 }
423
424 lock (this.events)
425 {
426 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory = this.events[i];
427
428 if (!ByCategory.TryGetValue(Category, out List<NotificationEvent>? Events2))
429 {
430 Events = null;
431 return false;
432 }
433
434 Events = [.. Events2];
435 return true;
436 }
437 }
438
443
448
452 public int NrNotificationsContacts => this.Count((int)NotificationEventType.Contacts);
453
457 public int NrNotificationsThings => this.Count((int)NotificationEventType.Things);
458
462 public int NrNotificationsContracts => this.Count((int)NotificationEventType.Contracts);
463
467 public int NrNotificationsWallet => this.Count((int)NotificationEventType.Wallet);
468
469 private int Count(int Index)
470 {
471 lock (this.events)
472 {
473 SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> Events = this.events[Index];
474 int Result = 0;
475
476 foreach (List<NotificationEvent> List in Events.Values)
477 Result += List.Count;
478
479 return Result;
480 }
481 }
482
487 public async Task DeleteResolvedEvents(IEventResolver Resolver)
488 {
489 List<NotificationEvent>? Resolved = null;
490
491 lock (this.events)
492 {
493 foreach (SortedDictionary<CaseInsensitiveString, List<NotificationEvent>> ByCategory in this.events)
494 {
495 foreach (KeyValuePair<CaseInsensitiveString, List<NotificationEvent>> P in ByCategory)
496 {
497 foreach (NotificationEvent Event in P.Value)
498 {
499 if (Resolver.Resolves(Event))
500 {
501 Resolved ??= [];
502 Resolved.Add(Event);
503 }
504 }
505 }
506 }
507 }
508
509 if (Resolved is not null)
510 await this.DeleteEvents([.. Resolved]);
511 }
512
513 }
514}
SortedDictionary< CaseInsensitiveString, T[]> GetEventsByCategory< T >(NotificationEventType Type)
Gets available notification events for a button, sorted by category.
void ExpectEvent< T >(DateTime Before)
Registers a type of notification as expected.
NotificationEvent[] GetEvents(NotificationEventType Type)
Gets available notification events for a button.
int NrNotificationsThings
Number of notifications but button Things
async Task NewEvent(NotificationEvent Event)
Registers a new event and notifies the user.
NotificationEventsHandler? OnNotificationsDeleted
Event raised when notifications have been deleted.
int NrNotificationsContracts
Number of notifications but button Contracts
NotificationEventHandler? OnNewNotification
Event raised when a new notification has been logged.
int NrNotificationsWallet
Number of notifications but button Wallet
int NrNotificationsContacts
Number of notifications but button Contacts
override async Task Load(bool isResuming, CancellationToken cancellationToken)
Loads the specified service.
SortedDictionary< CaseInsensitiveString, NotificationEvent[]> GetEventsByCategory(NotificationEventType Type)
Gets available notification events for a button, sorted by category.
bool TryGetNotificationEvents(NotificationEventType Type, CaseInsensitiveString Category, [NotNullWhen(true)] out NotificationEvent[]? Events)
Tries to get available notification events.
CaseInsensitiveString[] GetCategories(NotificationEventType Type)
Gets available categories for a button.
async Task DeleteEvents(NotificationEventType Type, CaseInsensitiveString Category)
Deletes events for a given button and category.
Task DeleteEvents(params NotificationEvent[] Events)
Deletes a specified set of events.
async Task DeleteResolvedEvents(IEventResolver Resolver)
Deletes pending events that have already been resolved.
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
Class representing an event.
Definition: Event.cs:10
EventType Type
Type of event.
Definition: Event.cs:121
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Debug(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a debug event.
Definition: Log.cs:218
Represents a case-insensitive string.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:1494
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:1486
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 Clear(string CollectionName)
Clears a collection of all objects.
Definition: Database.cs:1206
Interface for event resolvers. Such can be used to resolve multiple pending notifications at once.
bool Resolves(NotificationEvent Event)
If the resolver resolves an event.
abstract class NotificationEvent()
Abstract base class of notification events.
class NotificationEventsArgs(NotificationEvent[] Events)
Event argument for notification events.
class NotificationEventArgs(NotificationEvent Event)
Event argument for notification events.
delegate void NotificationEventHandler(object? Sender, NotificationEventArgs e)
Delegate for notification event handlers.
NotificationEventType
Button on which event is to be displayed.
delegate void NotificationEventsHandler(object? Sender, NotificationEventsArgs e)
Delegate for notification events handlers.
EventType
Type of event.
Definition: EventType.cs:7