Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SoftwareUpdateClient.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Net.Http;
6using System.Text;
7using System.Threading.Tasks;
8using System.Xml;
10using Waher.Events;
12
14{
22 {
26 public const string Wildcard = "*";
27
28 private readonly string componentAddress;
29 private readonly string packageFolder;
30 private readonly Random rnd = new Random();
31
35 public const string NamespaceSoftwareUpdatesIeeeV1 = "urn:ieee:iot:swu:1.0";
36
40 public const string NamespaceSoftwareUpdatesNeuroFoundationV1 = "urn:nf:iot:swu:1.0";
41
46
50 public static readonly string[] NamespacesSoftwareUpdates = new string[]
51 {
54 };
55
66 : base(Client)
67 {
68 this.componentAddress = ComponentAddress;
69 this.packageFolder = PackageFolder;
70
71 if (!Directory.Exists(PackageFolder))
72 Directory.CreateDirectory(PackageFolder);
73
74 #region Neuro-Foundation V1
75
76 this.Client.RegisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageNotificationHandler, true);
77 this.Client.RegisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageDeletedNotificationHandler, false);
78
79 #endregion
80
81 #region IEEE V1
82
83 this.Client.RegisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesIeeeV1, this.PackageNotificationHandler, true);
84 this.Client.RegisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesIeeeV1, this.PackageDeletedNotificationHandler, false);
85
86 #endregion
87 }
88
90 public override void Dispose()
91 {
92 base.Dispose();
93
94 #region Neuro-Foundation V1
95
96 this.Client.UnregisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageNotificationHandler, true);
97 this.Client.UnregisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageDeletedNotificationHandler, false);
98
99 #endregion
100
101 #region IEEE V1
102
103 this.Client.UnregisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesIeeeV1, this.PackageNotificationHandler, true);
104 this.Client.UnregisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesIeeeV1, this.PackageDeletedNotificationHandler, false);
105
106 #endregion
107 }
108
112 public override string[] Extensions => new string[] { };
113
117 public string ComponentAddress => this.componentAddress;
118
122 public string PackageFolder => this.packageFolder;
123
130 public Task GetPackageInformation(string FileName, EventHandlerAsync<PackageEventArgs> Callback, object State)
131 {
132 StringBuilder Xml = new StringBuilder();
133
134 Xml.Append("<getPackageInfo xmlns='");
136 Xml.Append("' fileName='");
137 Xml.Append(XML.Encode(FileName));
138 Xml.Append("'/>");
139
140 return this.client.SendIqGet(this.componentAddress, Xml.ToString(), async (Sender, e) =>
141 {
142 XmlElement E = e.FirstElement;
143 Package PackageInfo = null;
144
145 if (e.Ok && !(E is null) && E.LocalName == "packageInfo")
146 PackageInfo = Package.Parse(E);
147 else
148 e.Ok = false;
149
150 PackageEventArgs e2 = new PackageEventArgs(PackageInfo, e);
151 await Callback.Raise(this, e2);
152
153 }, State);
154 }
155
162 public async Task<Package> GetPackageInformationAsync(string FileName)
163 {
164 TaskCompletionSource<Package> Result = new TaskCompletionSource<Package>();
165
166 await this.GetPackageInformation(FileName, (Sender, e) =>
167 {
168 if (e.Ok)
169 Result.TrySetResult(e.Package);
170 else
171 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get package information."));
172
173 return Task.CompletedTask;
174
175 }, null);
176
177 return await Result.Task;
178 }
179
185 public Task GetPackagesInformation(EventHandlerAsync<PackagesEventArgs> Callback, object State)
186 {
187 StringBuilder Xml = new StringBuilder();
188
189 Xml.Append("<getPackages xmlns='");
191 Xml.Append("'/>");
192
193 return this.client.SendIqGet(this.componentAddress, Xml.ToString(), async (Sender, e) =>
194 {
195 XmlElement E = e.FirstElement;
196 Package[] PackagesInfo = null;
197
198 if (e.Ok && !(E is null) && E.LocalName == "packages")
199 {
200 List<Package> Packages = new List<Package>();
201
202 foreach (XmlNode N in E.ChildNodes)
203 {
204 if (N is XmlElement E2 && E2.LocalName == "packageInfo")
205 Packages.Add(Package.Parse(E2));
206 }
207
208 PackagesInfo = Packages.ToArray();
209 }
210 else
211 e.Ok = false;
212
213 PackagesEventArgs e2 = new PackagesEventArgs(PackagesInfo, e);
214 await Callback.Raise(this, e2);
215
216 }, State);
217 }
218
224 public async Task<Package[]> GetPackagesAsync()
225 {
226 TaskCompletionSource<Package[]> Result = new TaskCompletionSource<Package[]>();
227
228 await this.GetPackagesInformation((Sender, e) =>
229 {
230 if (e.Ok)
231 Result.TrySetResult(e.Packages);
232 else
233 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get packages."));
234
235 return Task.CompletedTask;
236
237 }, null);
238
239 return await Result.Task;
240 }
241
249 public Task Subscribe(string FileName, EventHandlerAsync<IqResultEventArgs> Callback, object State)
250 {
251 StringBuilder Xml = new StringBuilder();
252
253 Xml.Append("<subscribe xmlns='");
254 Xml.Append(NamespaceSoftwareUpdatesCurrent);
255 Xml.Append("' fileName='");
256 Xml.Append(XML.Encode(FileName));
257 Xml.Append("'/>");
258
259 return this.client.SendIqSet(this.componentAddress, Xml.ToString(), Callback, State);
260 }
261
268 public async Task SubscribeAsync(string FileName)
269 {
270 TaskCompletionSource<bool> Result = new TaskCompletionSource<bool>();
271
272 await this.Subscribe(FileName, (Sender, e) =>
273 {
274 if (e.Ok)
275 Result.TrySetResult(true);
276 else
277 Result.TrySetException(e.StanzaError ?? new Exception("Unable to subscribe to software updates for " + FileName + "."));
278
279 return Task.CompletedTask;
280
281 }, null);
282
283 await Result.Task;
284 }
285
293 public Task Unsubscribe(string FileName, EventHandlerAsync<IqResultEventArgs> Callback, object State)
294 {
295 StringBuilder Xml = new StringBuilder();
296
297 Xml.Append("<unsubscribe xmlns='");
298 Xml.Append(NamespaceSoftwareUpdatesCurrent);
299 Xml.Append("' fileName='");
300 Xml.Append(XML.Encode(FileName));
301 Xml.Append("'/>");
302
303 return this.client.SendIqSet(this.componentAddress, Xml.ToString(), Callback, State);
304 }
305
312 public async Task UnsubscribeAsync(string FileName)
313 {
314 TaskCompletionSource<bool> Result = new TaskCompletionSource<bool>();
315
316 await this.Unsubscribe(FileName, (Sender, e) =>
317 {
318 if (e.Ok)
319 Result.TrySetResult(true);
320 else
321 {
322 Result.TrySetException(e.StanzaError ?? new Exception("Unable to unsubscribe from software updates for " + FileName + "."));
323 }
324
325 return Task.CompletedTask;
326
327 }, null);
328
329 await Result.Task;
330 }
331
337 public Task GetSubscriptions(EventHandlerAsync<SubscriptionsEventArgs> Callback, object State)
338 {
339 StringBuilder Xml = new StringBuilder();
340
341 Xml.Append("<getSubscriptions xmlns='");
342 Xml.Append(NamespaceSoftwareUpdatesCurrent);
343 Xml.Append("'/>");
344
345 return this.client.SendIqGet(this.componentAddress, Xml.ToString(), async (Sender, e) =>
346 {
347 XmlElement E = e.FirstElement;
348 string[] FileNames = null;
349
350 if (e.Ok && !(E is null) && E.LocalName == "subscriptions")
351 {
352 List<string> Subscriptions = new List<string>();
353
354 foreach (XmlNode N in E.ChildNodes)
355 {
356 if (N is XmlElement E2 && E2.LocalName == "subscription")
357 Subscriptions.Add(E2.InnerText);
358 }
359
360 FileNames = Subscriptions.ToArray();
361 }
362 else
363 e.Ok = false;
364
365 SubscriptionsEventArgs e2 = new SubscriptionsEventArgs(FileNames, e);
366 await Callback.Raise(this, e2);
367 }, State);
368 }
369
375 public async Task<string[]> GetSubscriptionsAsync()
376 {
377 TaskCompletionSource<string[]> Result = new TaskCompletionSource<string[]>();
378
379 await this.GetSubscriptions((Sender, e) =>
380 {
381 if (e.Ok)
382 Result.TrySetResult(e.FileNames);
383 else
384 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get list of current subscriptions."));
385
386 return Task.CompletedTask;
387
388 }, null);
389
390 return await Result.Task;
391 }
392
393 private async Task PackageNotificationHandler(object Sender, MessageEventArgs e)
394 {
395 if (string.Compare(e.From, this.componentAddress, true) != 0)
396 {
397 await this.client.Warning("Discarding package notification. Expected source: " + this.componentAddress + ". Actual source: " + e.From);
398 return;
399 }
400
401 Package PackageInfo = Package.Parse(e.Content);
402 PackageUpdatedEventArgs e2 = new PackageUpdatedEventArgs(PackageInfo, e);
403
404 await this.OnSoftwareUpdated.Raise(this, e2, false);
405
406 if (e2.Download)
407 {
408 Task _ = Task.Run(() => this.Download(PackageInfo, e));
409 }
410 }
411
412 private async Task Download(Package PackageInfo, MessageEventArgs e)
413 {
414 try
415 {
416 string FileName = await this.DownloadPackageAsync(PackageInfo);
417 PackageFileEventArgs e3 = new PackageFileEventArgs(PackageInfo, FileName, e);
418
419 if (!await this.OnSoftwareValidation.Raise(this, e3, false))
420 {
421 File.Delete(FileName);
422 Log.Warning("Package with invalid signature downloaded and deleted.", FileName);
423 return;
424 }
425
426 await this.OnSoftwareDownloaded.Raise(this, e3);
427 }
428 catch (Exception ex)
429 {
430 Log.Exception(ex);
431 }
432 }
433
440 public async Task<string> DownloadPackageAsync(Package PackageInfo)
441 {
442 string FileName = Path.Combine(this.packageFolder, PackageInfo.FileName);
443 int MaxRetryDelayMinutes = 5;
444
445 while (true)
446 {
447 HttpStatusCode StatusCode;
448
449 try
450 {
451 using (HttpClient WebClient = new HttpClient())
452 {
453 using (HttpResponseMessage Response = await WebClient.GetAsync(PackageInfo.Url, HttpCompletionOption.ResponseHeadersRead))
454 {
455 if (Response.IsSuccessStatusCode)
456 {
457 using (Stream Input = await Response.Content.ReadAsStreamAsync())
458 {
459 using (Stream Output = File.Create(FileName))
460 {
461 await Input.CopyToAsync(Output);
462 }
463 }
464
465 return FileName;
466 }
467 else
468 StatusCode = Response.StatusCode;
469 }
470 }
471 }
472 catch (Exception)
473 {
474 StatusCode = HttpStatusCode.InternalServerError;
475 }
476
477 if ((int)StatusCode < 500)
478 {
479 throw new IOException("Unable to download new package from server. HTTP Status Code returned: " +
480 StatusCode.ToString() + " (" + ((int)StatusCode).ToString() + ")");
481 }
482
483 int MsDelay;
484
485 lock (this.rnd)
486 {
487 MsDelay = this.rnd.Next(1000, MaxRetryDelayMinutes * 60000);
488
489 MaxRetryDelayMinutes <<= 1;
490 if (MaxRetryDelayMinutes > 1440)
491 MaxRetryDelayMinutes = 1440; // Try at least once a day.
492 }
493
494 await Task.Delay(MsDelay);
495 }
496 }
497
502 public event EventHandlerAsync<PackageUpdatedEventArgs> OnSoftwareUpdated = null;
503
514 public event EventHandlerAsync<PackageFileEventArgs> OnSoftwareValidation = null;
515
519 public event EventHandlerAsync<PackageFileEventArgs> OnSoftwareDownloaded = null;
520
521 private async Task PackageDeletedNotificationHandler(object Sender, MessageEventArgs e)
522 {
523 if (string.Compare(e.From, this.componentAddress, true) != 0)
524 return;
525
526 Package PackageInfo = Package.Parse(e.Content);
527 PackageDeletedEventArgs e2 = new PackageDeletedEventArgs(PackageInfo, e);
528 await this.OnSoftwareDeleted.Raise(this, e2, false);
529
530 if (e2.Delete)
531 {
532 try
533 {
534 string FileName = Path.Combine(this.packageFolder, PackageInfo.FileName);
535
536 if (File.Exists(FileName))
537 {
538 File.Delete(FileName);
539
540 await this.OnDownloadedSoftwareDeleted.Raise(this, new PackageFileEventArgs(PackageInfo, FileName, e));
541 }
542 }
543 catch (Exception ex)
544 {
545 Log.Exception(ex);
546 }
547 }
548 }
549
554 public event EventHandlerAsync<PackageDeletedEventArgs> OnSoftwareDeleted = null;
555
559 public event EventHandlerAsync<PackageFileEventArgs> OnDownloadedSoftwareDeleted = null;
560
561 }
562}
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static void 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
Event arguments for message events.
string From
From where the message was received.
XmlElement Content
Content of the message. For messages that are processed by registered message handlers,...
Event arguments for software package deletion events.
bool Delete
If the downloaded package is to be deleted. Default=true.
Event arguments for software package events.
Event arguments for software package file events.
Information about a software package.
Definition: Package.cs:11
static Package Parse(XmlElement Xml)
Parses a package information element.
Definition: Package.cs:97
string FileName
Name of software package file.
Definition: Package.cs:31
string Url
URL to download software package.
Definition: Package.cs:50
Event arguments for Software packages events.
Implements an XMPP interface for remote software updates.
string PackageFolder
Folder of downloaded packages.
const string NamespaceSoftwareUpdatesCurrent
Current namespace for software updates.
const string Wildcard
Subscribing to a wildcard allows you to receive events when any software package is updated.
Task Subscribe(string FileName, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Subscribes to updates to a given software package.
static readonly string[] NamespacesSoftwareUpdates
Namespaces supported for software updates.
async Task< string[]> GetSubscriptionsAsync()
Gets current software update subscriptions.
async Task< Package[]> GetPackagesAsync()
Gets information about available software packages.
override void Dispose()
Disposes of the extension.
Task GetSubscriptions(EventHandlerAsync< SubscriptionsEventArgs > Callback, object State)
Gets current software update subscriptions.
async Task< Package > GetPackageInformationAsync(string FileName)
Gets information about a software package.
Task GetPackagesInformation(EventHandlerAsync< PackagesEventArgs > Callback, object State)
Gets information about available software packages.
SoftwareUpdateClient(XmppClient Client, string ComponentAddress, string PackageFolder)
Implements an XMPP interface for remote software updates.
const string NamespaceSoftwareUpdatesNeuroFoundationV1
urn:nf:iot:swu:1.0
Task GetPackageInformation(string FileName, EventHandlerAsync< PackageEventArgs > Callback, object State)
Gets information about a software package.
Task Unsubscribe(string FileName, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Unsubscribes to updates to a given software package.
async Task SubscribeAsync(string FileName)
Subscribes to updates to a given software package.
async Task< string > DownloadPackageAsync(Package PackageInfo)
Downloads a software package.
override string[] Extensions
Implemented extensions.
async Task UnsubscribeAsync(string FileName)
Unsubscribes to updates to a given software package.
const string NamespaceSoftwareUpdatesIeeeV1
urn:ieee:iot:swu:1.0
Event arguments for subscription list responses responsess.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsClientFeature)
Unregisters a Message handler.
Definition: XmppClient.cs:2855
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsClientFeature)
Registers a Message handler.
Definition: XmppClient.cs:2828
Task< uint > SendIqGet(string To, string Xml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ Get request.
Definition: XmppClient.cs:3559
Base class for XMPP Extensions.
XmppClient client
XMPP Client used by the extension.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
XmppClient Client
XMPP Client.