/trunk/Winify/Gotify/GotifyApplication.cs |
@@ -1,25 +1,107 @@ |
using Newtonsoft.Json; |
using System.Collections.Generic; |
using Newtonsoft.Json; |
using System.ComponentModel; |
using System.Runtime.CompilerServices; |
using Winify.Properties; |
|
namespace Winify.Gotify |
{ |
public class GotifyApplication |
public class GotifyApplication : INotifyPropertyChanged |
{ |
private int _id; |
private string _token; |
private string _name; |
private string _description; |
private bool _internal; |
private string _image; |
|
#region Public Enums, Properties and Fields |
|
[JsonProperty(PropertyName = "id")] public int Id { get; set; } |
[JsonProperty(PropertyName = "id")] |
public int Id |
{ |
get => _id; |
set |
{ |
if (value == _id) return; |
_id = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "token")] public string Token { get; set; } |
[JsonProperty(PropertyName = "token")] |
public string Token |
{ |
get => _token; |
set |
{ |
if (value == _token) return; |
_token = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "name")] public string Name { get; set; } |
[JsonProperty(PropertyName = "name")] |
public string Name |
{ |
get => _name; |
set |
{ |
if (value == _name) return; |
_name = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "description")] |
public string Description { get; set; } |
public string Description |
{ |
get => _description; |
set |
{ |
if (value == _description) return; |
_description = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "internal")] |
public bool Internal { get; set; } |
public bool Internal |
{ |
get => _internal; |
set |
{ |
if (value == _internal) return; |
_internal = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "image")] public string Image { get; set; } |
[JsonProperty(PropertyName = "image")] |
public string Image |
{ |
get => _image; |
set |
{ |
if (value == _image) return; |
_image = value; |
OnPropertyChanged(); |
} |
} |
|
#endregion |
|
[UsedImplicitly] |
public GotifyApplication() |
{ |
|
} |
|
public event PropertyChangedEventHandler PropertyChanged; |
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) |
{ |
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
} |
} |
} |
/trunk/Winify/Gotify/GotifyConnection.cs |
@@ -58,6 +58,9 @@ |
|
private Task _retrievePastMessagesTask; |
private static JsonSerializer _jsonSerializer; |
private readonly CancellationToken _programCancellationToken; |
private CancellationTokenSource _localCancellationTokenSource; |
private CancellationToken _localCancellationToken; |
|
#endregion |
|
@@ -118,10 +121,11 @@ |
new DataflowLinkOptions { PropagateCompletion = true }); |
} |
|
public GotifyConnection(Server server, Configuration.Configuration configuration) : this() |
public GotifyConnection(Server server, Configuration.Configuration configuration, CancellationToken cancellationToken) : this() |
{ |
_server = server; |
_configuration = configuration; |
_programCancellationToken = cancellationToken; |
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; |
var httpClientHandler = new HttpClientHandler |
@@ -172,10 +176,10 @@ |
|
public void Dispose() |
{ |
if (_cancellationTokenSource != null) |
if (_localCancellationTokenSource != null) |
{ |
_cancellationTokenSource.Dispose(); |
_cancellationTokenSource = null; |
_localCancellationTokenSource.Dispose(); |
_localCancellationTokenSource = null; |
} |
|
if (_tplWebSocketsBufferBlockTransformLink != null) |
@@ -207,6 +211,32 @@ |
|
#region Public Methods |
|
public async Task Stop() |
{ |
_localCancellationTokenSource.Cancel(); |
|
if (_heartBeatTask != null) |
{ |
await _heartBeatTask; |
} |
|
if (_retrievePastMessagesTask != null) |
{ |
await _retrievePastMessagesTask; |
} |
|
if (_webSocketSharp != null) |
{ |
_webSocketSharp.OnMessage -= WebSocketSharp_OnMessage; |
_webSocketSharp.OnError -= WebSocketSharp_OnError; |
_webSocketSharp.OnOpen -= WebSocketSharp_OnOpen; |
_webSocketSharp.OnClose -= WebSocketSharp_OnClose; |
|
_webSocketSharp.Close(); |
_webSocketSharp = null; |
} |
} |
|
public void Start() |
{ |
if (_webSocketsUri == null || _httpUri == null) |
@@ -215,7 +245,11 @@ |
return; |
} |
|
_cancellationTokenSource = new CancellationTokenSource(); |
|
_localCancellationTokenSource = new CancellationTokenSource(); |
_localCancellationToken = _localCancellationTokenSource.Token; |
|
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(new [] { _programCancellationToken, _localCancellationToken }); |
_cancellationToken = _cancellationTokenSource.Token; |
|
if (_webSocketSharp != null) |
@@ -269,14 +303,20 @@ |
|
private async void WebSocketSharp_OnClose(object sender, CloseEventArgs e) |
{ |
Log.Information( |
$"WebSockets connection to server {_webSocketsUri.AbsoluteUri} closed with reason {e.Reason}"); |
Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} closed with reason {e.Reason}"); |
|
await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken); |
try |
{ |
await Task.Delay(TimeSpan.FromSeconds(1), _programCancellationToken); |
|
Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
|
await Stop().ContinueWith(task => Start(), CancellationToken.None); |
await Stop().ContinueWith(task => Start(), _programCancellationToken); |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Error restarting WebSockets connection on connection closed."); |
} |
} |
|
private void WebSocketSharp_OnOpen(object sender, EventArgs e) |
@@ -283,18 +323,25 @@ |
{ |
Log.Information($"WebSockets connection to server {_webSocketsUri.AbsoluteUri} is now open"); |
|
_webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
_webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnServerNotResponding, _cancellationToken); |
} |
|
private async void OnUnresponsiveServer() |
private async void OnServerNotResponding() |
{ |
Log.Warning($"Server {_server.Name} has not responded in a long while..."); |
|
await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken); |
try |
{ |
await Task.Delay(TimeSpan.FromSeconds(1), _programCancellationToken); |
|
Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
|
await Stop().ContinueWith(task => Start(), CancellationToken.None); |
await Stop().ContinueWith(task => Start(), _programCancellationToken); |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Error restarting WebSockets connection on connection closed."); |
} |
} |
|
private async void WebSocketSharp_OnError(object sender, ErrorEventArgs e) |
@@ -303,11 +350,18 @@ |
$"Connection to WebSockets server {_webSocketsUri.AbsoluteUri} terminated unexpectedly with message {e.Message}", |
e.Exception); |
|
await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken); |
try |
{ |
await Task.Delay(TimeSpan.FromSeconds(1), _programCancellationToken); |
|
Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
Log.Information($"Reconnecting to websocket server {_webSocketsUri.AbsoluteUri}"); |
|
await Stop().ContinueWith(task => Start(), CancellationToken.None); |
await Stop().ContinueWith(task => Start(), _programCancellationToken); |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Error restarting WebSockets connection on connection closed."); |
} |
} |
|
private async void WebSocketSharp_OnMessage(object sender, MessageEventArgs e) |
@@ -316,7 +370,7 @@ |
{ |
Log.Information($"Server {_server.Name} sent PING message"); |
|
_webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
_webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnServerNotResponding, _cancellationToken); |
return; |
} |
|
@@ -323,41 +377,6 @@ |
await _webSocketMessageBufferBlock.SendAsync(new GotifyConnectionData(e.RawData, _server), _cancellationToken); |
} |
|
public async Task Stop() |
{ |
|
if (_webSocketSharp == null || _cancellationTokenSource == null) |
{ |
return; |
} |
|
_cancellationTokenSource.Cancel(); |
|
if (_heartBeatTask != null) |
{ |
await _heartBeatTask; |
} |
|
if (_retrievePastMessagesTask != null) |
{ |
await _retrievePastMessagesTask; |
} |
|
if (_webSocketSharp != null) |
{ |
_webSocketSharp.OnMessage -= WebSocketSharp_OnMessage; |
_webSocketSharp.OnError -= WebSocketSharp_OnError; |
_webSocketSharp.OnOpen -= WebSocketSharp_OnOpen; |
_webSocketSharp.OnClose -= WebSocketSharp_OnClose; |
|
_webSocketSharp.Close(); |
_webSocketSharp = null; |
} |
|
_cancellationTokenSource.Dispose(); |
_cancellationTokenSource = null; |
} |
|
#endregion |
|
#region Private Methods |
@@ -461,7 +480,7 @@ |
|
Log.Information($"PING response latency for {_server.Name} is {delta}ms"); |
|
_webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnUnresponsiveServer, _cancellationToken); |
_webSocketsServerResponseScheduledContinuation.Schedule(TimeSpan.FromMinutes(1), OnServerNotResponding, _cancellationToken); |
|
} while (!cancellationToken.IsCancellationRequested); |
} |
/trunk/Winify/Gotify/GotifyMessage.cs |
@@ -1,6 +1,10 @@ |
using System; |
using System.Collections.Generic; |
using System.ComponentModel; |
using System.Runtime.CompilerServices; |
using Newtonsoft.Json; |
using Servers; |
using Winify.Properties; |
|
namespace Winify.Gotify |
{ |
@@ -8,29 +12,129 @@ |
/// {"id":22,"appid":1,"message":"iot","title":"Arcade |
/// Netplay","priority":0,"date":"2022-10-26T14:55:59.050734643+03:00"} |
/// </summary> |
public class GotifyMessage |
public class GotifyMessage : INotifyPropertyChanged |
{ |
private int _id; |
private int _appId; |
private string _message; |
private string _title; |
private int _priority; |
private DateTime _date; |
private GotifyMessageExtras _extras = new GotifyMessageExtras(); |
private Server _server; |
|
#region Public Enums, Properties and Fields |
|
[JsonProperty(PropertyName = "id")] public int Id { get; set; } |
[JsonProperty(PropertyName = "id")] |
public int Id |
{ |
get => _id; |
set |
{ |
if (value == _id) return; |
_id = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "appid")] public int AppId { get; set; } |
[JsonProperty(PropertyName = "appid")] |
public int AppId |
{ |
get => _appId; |
set |
{ |
if (value == _appId) return; |
_appId = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "message")] |
public string Message { get; set; } |
public string Message |
{ |
get => _message; |
set |
{ |
if (value == _message) return; |
_message = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "title")] public string Title { get; set; } |
[JsonProperty(PropertyName = "title")] |
public string Title |
{ |
get => _title; |
set |
{ |
if (value == _title) return; |
_title = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "priority")] |
public int Priority { get; set; } |
public int Priority |
{ |
get => _priority; |
set |
{ |
if (value == _priority) return; |
_priority = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "date")] public DateTime Date { get; set; } |
[JsonProperty(PropertyName = "date")] |
public DateTime Date |
{ |
get => _date; |
set |
{ |
if (value.Equals(_date)) return; |
_date = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonProperty(PropertyName = "extras")] |
public GotifyMessageExtras Extras { get; set; } |
public GotifyMessageExtras Extras |
{ |
get => _extras; |
set |
{ |
if (Equals(value, _extras)) return; |
_extras = value; |
OnPropertyChanged(); |
} |
} |
|
[JsonIgnore] public Server Server { get; set; } |
[JsonIgnore] |
public Server Server |
{ |
get => _server; |
set |
{ |
if (Equals(value, _server)) return; |
_server = value; |
OnPropertyChanged(); |
OnPropertyChanged(); |
} |
} |
|
#endregion |
|
[UsedImplicitly] |
public GotifyMessage() |
{ |
|
} |
|
public event PropertyChangedEventHandler PropertyChanged; |
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) |
{ |
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
} |
} |
} |
/trunk/Winify/MainForm.cs |
@@ -130,7 +130,7 @@ |
var servers = await LoadServers(); |
foreach (var server in servers.Server) |
{ |
var gotifyConnection = new GotifyConnection(server, Configuration); |
var gotifyConnection = new GotifyConnection(server, Configuration, _cancellationToken); |
gotifyConnection.GotifyMessage += GotifyConnectionGotifyMessage; |
gotifyConnection.Start(); |
_gotifyConnections.Add(gotifyConnection); |
@@ -147,7 +147,7 @@ |
_gotifyConnections = new ConcurrentBag<GotifyConnection>(); |
foreach (var server in servers.Server) |
{ |
var gotifyConnection = new GotifyConnection(server, Configuration); |
var gotifyConnection = new GotifyConnection(server, Configuration, _cancellationToken); |
gotifyConnection.GotifyMessage += GotifyConnectionGotifyMessage; |
gotifyConnection.Start(); |
_gotifyConnections.Add(gotifyConnection); |
@@ -212,7 +212,7 @@ |
|
foreach (var server in e.Servers.Server) |
{ |
var gotifyConnection = new GotifyConnection(server, Configuration); |
var gotifyConnection = new GotifyConnection(server, Configuration, _cancellationToken); |
gotifyConnection.GotifyMessage += GotifyConnectionGotifyMessage; |
gotifyConnection.Start(); |
_gotifyConnections.Add(gotifyConnection); |