Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NetworkService.cs
1using System.Net;
2using System.Runtime.CompilerServices;
3using System.Runtime.ExceptionServices;
7using Waher.Events;
12
14{
15 [Singleton]
16 internal class NetworkService : LoadableService, INetworkService
17 {
18 private const int defaultXmppPortNumber = 5222;
19
20 public event EventHandler<ConnectivityChangedEventArgs>? ConnectivityChanged;
21
22 public NetworkService()
23 {
24 }
25
27 public override Task Load(bool IsResuming, CancellationToken CancellationToken)
28 {
29 if (this.BeginLoad(IsResuming, CancellationToken))
30 {
31 if (DeviceInfo.Platform != DevicePlatform.Unknown && !DesignMode.IsDesignModeEnabled) // Need to check this, as Xamarin.Essentials doesn't work in unit tests. It has no effect when running on a real phone.
32 Connectivity.ConnectivityChanged += this.Connectivity_ConnectivityChanged;
33
34 this.EndLoad(true);
35 }
36
37 return Task.CompletedTask;
38 }
39
41 public override Task Unload()
42 {
43 if (this.BeginUnload())
44 {
45 if (DeviceInfo.Platform != DevicePlatform.Unknown && !DesignMode.IsDesignModeEnabled)
46 Connectivity.ConnectivityChanged -= this.Connectivity_ConnectivityChanged;
47
48 this.EndUnload();
49 }
50
51 return Task.CompletedTask;
52 }
53
54 private void Connectivity_ConnectivityChanged(object? Sender, ConnectivityChangedEventArgs e)
55 {
56 this.ConnectivityChanged.Raise(this, e);
57 }
58
59 public virtual bool IsOnline =>
60 Connectivity.NetworkAccess == NetworkAccess.Internet ||
61 Connectivity.NetworkAccess == NetworkAccess.ConstrainedInternet;
62
63 public async Task<(string HostName, int Port, bool IsIpAddress)> LookupXmppHostnameAndPort(string DomainName)
64 {
65 if (IPAddress.TryParse(DomainName, out IPAddress? _))
66 return (DomainName, defaultXmppPortNumber, true);
67
68 try
69 {
70 SRV endpoint = await DnsResolver.LookupServiceEndpoint(DomainName, "xmpp-client", "tcp");
71
72 if (endpoint is not null && !string.IsNullOrWhiteSpace(endpoint.TargetHost) && endpoint.Port > 0)
73 return (endpoint.TargetHost, endpoint.Port, false);
74 }
75 catch (Exception)
76 {
77 // No service endpoint registered
78 }
79
80 return (DomainName, defaultXmppPortNumber, false);
81 }
82
83 public async Task<bool> TryRequest(Func<Task> func, bool rethrowException = false, bool displayAlert = true,
84 [CallerMemberName] string memberName = "")
85 {
86 (bool succeeded, bool _) = await this.PerformRequestInner(async () =>
87 {
88 await func();
89 return true;
90 }, memberName, rethrowException, displayAlert);
91
92 return succeeded;
93 }
94
95 public Task<(bool Succeeded, TReturn? ReturnValue)> TryRequest<TReturn>(Func<Task<TReturn>> func, bool rethrowException = false, bool displayAlert = true, [CallerMemberName] string memberName = "")
96 {
97 return this.PerformRequestInner(async () => await func(), memberName, rethrowException, displayAlert);
98 }
99
100 private async Task<(bool Succeeded, TReturn? ReturnValue)> PerformRequestInner<TReturn>(Func<Task<TReturn>> func, string memberName, bool rethrowException = false, bool displayAlert = true)
101 {
102 Exception ThrownException;
103 try
104 {
105 if (!this.IsOnline)
106 {
107 ThrownException = new MissingNetworkException(ServiceRef.Localizer[nameof(AppResources.ThereIsNoNetwork)]);
108 ServiceRef.LogService.LogException(ThrownException, GetParameter(memberName));
109
110 if (displayAlert)
111 {
112 await ServiceRef.UiService.DisplayAlert(
113 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
114 ServiceRef.Localizer[nameof(AppResources.ThereIsNoNetwork)]);
115 }
116 }
117 else
118 {
119 TReturn t = await func().TimeoutAfter(Constants.Timeouts.GenericRequest);
120 return (true, t);
121 }
122 }
123 catch (AggregateException ae)
124 {
125 ThrownException = ae;
126
127 if (ae.InnerException is TimeoutException te)
128 {
129 ServiceRef.LogService.LogException(te, GetParameter(memberName));
130
131 if (displayAlert)
132 {
133 await ServiceRef.UiService.DisplayAlert(
134 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
135 ServiceRef.Localizer[nameof(AppResources.RequestTimedOut)]);
136 }
137 }
138 else if (ae.InnerException is TaskCanceledException tce)
139 {
140 ServiceRef.LogService.LogException(tce, GetParameter(memberName));
141
142 if (displayAlert)
143 {
144 await ServiceRef.UiService.DisplayAlert(
145 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
146 ServiceRef.Localizer[nameof(AppResources.RequestWasCancelled)]);
147 }
148 }
149 else if (ae.InnerException is not null)
150 {
151 ServiceRef.LogService.LogException(ae.InnerException, GetParameter(memberName));
152
153 if (displayAlert)
154 {
155 await ServiceRef.UiService.DisplayAlert(
156 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
157 ae.InnerException.Message);
158 }
159 }
160 else
161 {
162 ServiceRef.LogService.LogException(ae, GetParameter(memberName));
163
164 if (displayAlert)
165 {
166 await ServiceRef.UiService.DisplayAlert(
167 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
168 ae.Message);
169 }
170 }
171 }
172 catch (TimeoutException te)
173 {
174 ThrownException = te;
175 ServiceRef.LogService.LogException(te, GetParameter(memberName));
176
177 if (displayAlert)
178 {
179 await ServiceRef.UiService.DisplayAlert(
180 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
181 ServiceRef.Localizer[nameof(AppResources.RequestTimedOut)]);
182 }
183 }
184 catch (TaskCanceledException tce)
185 {
186 ThrownException = tce;
187 ServiceRef.LogService.LogException(tce, GetParameter(memberName));
188
189 if (displayAlert)
190 {
191 await ServiceRef.UiService.DisplayAlert(
192 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
193 ServiceRef.Localizer[nameof(AppResources.RequestWasCancelled)]);
194 }
195 }
196 catch (Exception ex)
197 {
198 string message;
199
200 ThrownException = ex;
201
202 if (ex is XmppException xe && xe.Stanza is not null)
203 message = xe.Stanza.InnerText;
204 else
205 message = ex.Message;
206
207 ServiceRef.LogService.LogException(ex, GetParameter(memberName));
208
209 if (displayAlert)
210 {
211 await ServiceRef.UiService.DisplayAlert(
212 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
213 message);
214 }
215 }
216
217 if (rethrowException)
218 ExceptionDispatchInfo.Capture(ThrownException).Throw();
219
220 return (false, default);
221 }
222
223 private static KeyValuePair<string, object?>[] GetParameter(string MemberName)
224 {
225 if (!string.IsNullOrWhiteSpace(MemberName))
226 {
227 return
228 [
229 new KeyValuePair<string, object?>("Caller", MemberName)
230 ];
231 }
232
233 return [];
234 }
235 }
236}
static readonly TimeSpan GenericRequest
Generic request timeout
Definition: Constants.cs:692
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string RequestTimedOut
Looks up a localized string similar to The request timed out.
static string ThereIsNoNetwork
Looks up a localized string similar to There is no network.
static string RequestWasCancelled
Looks up a localized string similar to The request was cancelled.
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
bool BeginLoad(bool IsResuming, CancellationToken CancellationToken)
Sets the IsLoading flag if the service isn't already loading.
void EndLoad(bool isLoaded)
Sets the IsLoading and IsLoaded flags and fires an event representing the current load state of the s...
bool IsResuming
If App is resuming service.
bool BeginUnload()
Sets the IsLoading flag if the service isn't already unloading.
void EndUnload()
Sets the IsLoading and IsLoaded flags and fires an event representing the current load state of the s...
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< SRV > LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
Looks up a service endpoint for a domain. If multiple are available, an appropriate one is selected a...
Base class of XMPP exceptions
XmlElement Stanza
Stanza causing exception.
Definition: ImplTypes.g.cs:58