Complete refactor, reasync, rebuild listener
This commit is contained in:
@@ -15,33 +15,35 @@ namespace ntfysh_client
|
|||||||
{
|
{
|
||||||
public partial class Form1 : Form
|
public partial class Form1 : Form
|
||||||
{
|
{
|
||||||
private NotificationListener notificationListener;
|
private readonly NotificationListener _notificationListener;
|
||||||
|
private bool _trueExit;
|
||||||
|
|
||||||
public Form1()
|
public Form1(NotificationListener notificationListener)
|
||||||
{
|
{
|
||||||
notificationListener = new NotificationListener();
|
_notificationListener = notificationListener;
|
||||||
notificationListener.OnNotificationReceive += OnNotificationReceive;
|
_notificationListener.OnNotificationReceive += OnNotificationReceive;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Form1_Load(object sender, EventArgs e)
|
private void Form1_Load(object sender, EventArgs e) => LoadTopics();
|
||||||
{
|
|
||||||
this.LoadTopics();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void subscribeNewTopic_Click(object sender, EventArgs e)
|
private void subscribeNewTopic_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var dialog = new SubscribeDialog(notificationTopics))
|
using SubscribeDialog dialog = new SubscribeDialog(notificationTopics);
|
||||||
{
|
DialogResult result = dialog.ShowDialog();
|
||||||
var result = dialog.ShowDialog();
|
|
||||||
|
|
||||||
if (result == DialogResult.OK)
|
//Do not subscribe on cancelled dialog
|
||||||
{
|
if (result != DialogResult.OK) return;
|
||||||
notificationListener.SubscribeToTopic(dialog.getUniqueString(), dialog.getTopicId(), dialog.getServerUrl(), dialog.getUsername(), dialog.getPassword());
|
|
||||||
notificationTopics.Items.Add(dialog.getUniqueString());
|
//Subscribe
|
||||||
this.SaveTopicsToFile();
|
_notificationListener.SubscribeToTopicUsingLongHttpJson(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
|
||||||
}
|
|
||||||
}
|
//Add to the user visible list
|
||||||
|
notificationTopics.Items.Add(dialog.Unique);
|
||||||
|
|
||||||
|
//Save the topics persistently
|
||||||
|
SaveTopicsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSelectedTopics_Click(object sender, EventArgs e)
|
private void removeSelectedTopics_Click(object sender, EventArgs e)
|
||||||
@@ -50,11 +52,11 @@ namespace ntfysh_client
|
|||||||
{
|
{
|
||||||
string topicUniqueString = (string)notificationTopics.Items[notificationTopics.SelectedIndex];
|
string topicUniqueString = (string)notificationTopics.Items[notificationTopics.SelectedIndex];
|
||||||
|
|
||||||
notificationListener.RemoveTopicByUniqueString(topicUniqueString);
|
_notificationListener.UnsubscribeFromTopic(topicUniqueString);
|
||||||
notificationTopics.Items.Remove(topicUniqueString);
|
notificationTopics.Items.Remove(topicUniqueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.SaveTopicsToFile();
|
SaveTopicsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notificationTopics_SelectedValueChanged(object sender, EventArgs e)
|
private void notificationTopics_SelectedValueChanged(object sender, EventArgs e)
|
||||||
@@ -64,51 +66,48 @@ namespace ntfysh_client
|
|||||||
|
|
||||||
private void notificationTopics_Click(object sender, EventArgs e)
|
private void notificationTopics_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var ev = (MouseEventArgs)e;
|
MouseEventArgs ev = (MouseEventArgs)e;
|
||||||
var clickedItemIndex = notificationTopics.IndexFromPoint(new Point(ev.X, ev.Y));
|
int clickedItemIndex = notificationTopics.IndexFromPoint(new Point(ev.X, ev.Y));
|
||||||
|
|
||||||
if (clickedItemIndex == -1)
|
if (clickedItemIndex == -1) notificationTopics.ClearSelected();
|
||||||
{
|
|
||||||
notificationTopics.ClearSelected();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void button1_Click(object sender, EventArgs e)
|
private void button1_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
this.Visible = false;
|
Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyIcon_Click(object sender, EventArgs e)
|
private void notifyIcon_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var mouseEv = (MouseEventArgs)e;
|
MouseEventArgs mouseEv = (MouseEventArgs)e;
|
||||||
if (mouseEv.Button == MouseButtons.Left)
|
|
||||||
{
|
if (mouseEv.Button != MouseButtons.Left) return;
|
||||||
this.Visible = !this.Visible;
|
|
||||||
this.BringToFront();
|
Visible = !Visible;
|
||||||
}
|
BringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showControlWindowToolStripMenuItem_Click(object sender, EventArgs e)
|
private void showControlWindowToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
this.Visible = true;
|
Visible = true;
|
||||||
this.BringToFront();
|
BringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTopicsFilePath()
|
private string GetTopicsFilePath()
|
||||||
{
|
{
|
||||||
string binaryDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
string binaryDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Unable to determine path for application");
|
||||||
return Path.Combine(binaryDirectory ?? throw new InvalidOperationException("Unable to determine path for topics file"), "topics.json");
|
return Path.Combine(binaryDirectory ?? throw new InvalidOperationException("Unable to determine path for topics file"), "topics.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetLegacyTopicsFilePath()
|
private string GetLegacyTopicsFilePath()
|
||||||
{
|
{
|
||||||
string binaryDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
string binaryDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Unable to determine path for application");
|
||||||
return Path.Combine(binaryDirectory ?? throw new InvalidOperationException("Unable to determine path for legacy topics file"), "topics.txt");
|
return Path.Combine(binaryDirectory ?? throw new InvalidOperationException("Unable to determine path for legacy topics file"), "topics.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveTopicsToFile()
|
private void SaveTopicsToFile()
|
||||||
{
|
{
|
||||||
string topicsSerialised = JsonConvert.SerializeObject(notificationListener.SubscribedTopicsByUnique.Select(st => st.Value).ToList(), Formatting.Indented);
|
string topicsSerialised = JsonConvert.SerializeObject(_notificationListener.SubscribedTopicsByUnique.Select(st => st.Value).ToList(), Formatting.Indented);
|
||||||
|
|
||||||
File.WriteAllText(GetTopicsFilePath(), topicsSerialised);
|
File.WriteAllText(GetTopicsFilePath(), topicsSerialised);
|
||||||
}
|
}
|
||||||
@@ -128,13 +127,13 @@ namespace ntfysh_client
|
|||||||
{
|
{
|
||||||
while (!reader.EndOfStream)
|
while (!reader.EndOfStream)
|
||||||
{
|
{
|
||||||
string legacyTopic = reader.ReadLine();
|
string? legacyTopic = reader.ReadLine();
|
||||||
legacyTopics.Add(legacyTopic);
|
if (!string.IsNullOrWhiteSpace(legacyTopic)) legacyTopics.Add(legacyTopic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Assemble new format
|
//Assemble new format
|
||||||
List<SubscribedTopic> newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null, null)).ToList();
|
List<SubscribedTopic> newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null, null, null)).ToList();
|
||||||
|
|
||||||
string newFormatSerialised = JsonConvert.SerializeObject(newTopics, Formatting.Indented);
|
string newFormatSerialised = JsonConvert.SerializeObject(newTopics, Formatting.Indented);
|
||||||
|
|
||||||
@@ -160,9 +159,9 @@ namespace ntfysh_client
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Deserialise the topics
|
//Deserialise the topics
|
||||||
List<SubscribedTopic> topics = JsonConvert.DeserializeObject<List<SubscribedTopic>>(topicsSerialised);
|
List<SubscribedTopic>? topics = JsonConvert.DeserializeObject<List<SubscribedTopic>>(topicsSerialised);
|
||||||
|
|
||||||
if (topics == null)
|
if (topics is null)
|
||||||
{
|
{
|
||||||
//TODO Deserialise error!
|
//TODO Deserialise error!
|
||||||
return;
|
return;
|
||||||
@@ -171,7 +170,7 @@ namespace ntfysh_client
|
|||||||
//Load them in
|
//Load them in
|
||||||
foreach (SubscribedTopic topic in topics)
|
foreach (SubscribedTopic topic in topics)
|
||||||
{
|
{
|
||||||
notificationListener.SubscribeToTopic($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
|
_notificationListener.SubscribeToTopicUsingLongHttpJson($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
|
||||||
notificationTopics.Items.Add($"{topic.TopicId}@{topic.ServerUrl}");
|
notificationTopics.Items.Add($"{topic.TopicId}@{topic.ServerUrl}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,24 +184,22 @@ namespace ntfysh_client
|
|||||||
{
|
{
|
||||||
notifyIcon.Dispose();
|
notifyIcon.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool trueExit = false;
|
|
||||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
{
|
{
|
||||||
// Let it close
|
// Let it close
|
||||||
if (trueExit) return;
|
if (_trueExit) return;
|
||||||
|
|
||||||
if (e.CloseReason == CloseReason.UserClosing)
|
if (e.CloseReason != CloseReason.UserClosing) return;
|
||||||
{
|
|
||||||
this.Visible = false;
|
Visible = false;
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
trueExit = true;
|
_trueExit = true;
|
||||||
this.Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ntfyshWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
|
private void ntfyshWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
@@ -212,9 +209,8 @@ namespace ntfysh_client
|
|||||||
|
|
||||||
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var d = new AboutBox();
|
using AboutBox d = new AboutBox();
|
||||||
d.ShowDialog();
|
d.ShowDialog();
|
||||||
d.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,153 +15,147 @@ using System.Web;
|
|||||||
|
|
||||||
namespace ntfysh_client
|
namespace ntfysh_client
|
||||||
{
|
{
|
||||||
class NotificationListener : IDisposable
|
public class NotificationListener : IDisposable
|
||||||
{
|
{
|
||||||
private HttpClient httpClient;
|
private readonly HttpClient _httpClient = new();
|
||||||
|
private bool _isDisposed;
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
public readonly Dictionary<string, SubscribedTopic> SubscribedTopicsByUnique = new Dictionary<string, SubscribedTopic>();
|
public readonly Dictionary<string, SubscribedTopic> SubscribedTopicsByUnique = new();
|
||||||
|
|
||||||
public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
|
public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
|
||||||
public event NotificationReceiveHandler OnNotificationReceive;
|
public event NotificationReceiveHandler? OnNotificationReceive;
|
||||||
|
|
||||||
public NotificationListener()
|
public NotificationListener()
|
||||||
{
|
{
|
||||||
httpClient = new HttpClient();
|
_httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
||||||
|
|
||||||
httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
|
||||||
ServicePointManager.DefaultConnectionLimit = 100;
|
ServicePointManager.DefaultConnectionLimit = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubscribeToTopic(string unique, string topicId, string serverUrl, string username, string password)
|
private async Task ListenToTopicAsync(HttpRequestMessage message, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
|
using Stream body = await response.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StringBuilder mainBuffer = new();
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
//Read as much as possible
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int readBytes = await body.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||||
|
|
||||||
|
//Append it to our main buffer
|
||||||
|
mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, readBytes));
|
||||||
|
|
||||||
|
List<string> lines = mainBuffer.ToString().Split('\n').ToList();
|
||||||
|
|
||||||
|
//If we have not yet received a full line, meaning theres only 1 part, go back to reading
|
||||||
|
if (lines.Count <= 1) continue;
|
||||||
|
|
||||||
|
//We now have at least 1 line! Count how many full lines. There will always be a partial line at the end, even if that partial line is empty
|
||||||
|
|
||||||
|
//Separate the partial line from the full lines
|
||||||
|
int partialLineIndex = lines.Count - 1;
|
||||||
|
string partialLine = lines[partialLineIndex];
|
||||||
|
lines.RemoveAt(partialLineIndex);
|
||||||
|
|
||||||
|
//Process the full lines
|
||||||
|
foreach (string line in lines) ProcessMessage(line);
|
||||||
|
|
||||||
|
//Write back the partial line
|
||||||
|
mainBuffer.Clear();
|
||||||
|
mainBuffer.Append(partialLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(ex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//Fall back to the outer loop to restart the listen, or cancel if requested
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessMessage(string message)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(message);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NtfyEvent? evt = JsonConvert.DeserializeObject<NtfyEvent>(message);
|
||||||
|
|
||||||
|
//If we hit this, ntfy sent us an invalid message
|
||||||
|
if (evt is null) return;
|
||||||
|
|
||||||
|
if (evt.Event == "message")
|
||||||
|
{
|
||||||
|
OnNotificationReceive?.Invoke(this, new NotificationReceiveEventArgs(evt.Title, evt.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubscribeToTopicUsingLongHttpJson(string unique, string topicId, string serverUrl, string? username, string? password)
|
||||||
|
{
|
||||||
|
if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
|
||||||
|
|
||||||
|
if (SubscribedTopicsByUnique.ContainsKey(unique)) throw new InvalidOperationException("A topic with this unique already exists");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username)) username = null;
|
if (string.IsNullOrWhiteSpace(username)) username = null;
|
||||||
if (string.IsNullOrWhiteSpace(password)) password = null;
|
if (string.IsNullOrWhiteSpace(password)) password = null;
|
||||||
|
|
||||||
HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, $"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/json");
|
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, $"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/json");
|
||||||
|
|
||||||
if (username != null && password != null)
|
if (username != null && password != null)
|
||||||
{
|
{
|
||||||
byte[] boundCredentialsBytes = Encoding.UTF8.GetBytes($"{username}:{password}");
|
byte[] boundCredentialsBytes = Encoding.UTF8.GetBytes($"{username}:{password}");
|
||||||
|
|
||||||
msg.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(boundCredentialsBytes));
|
message.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(boundCredentialsBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (HttpResponseMessage response = await httpClient.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead))
|
CancellationTokenSource listenCanceller = new();
|
||||||
{
|
Task listenTask = ListenToTopicAsync(message, listenCanceller.Token);
|
||||||
using (Stream body = await response.Content.ReadAsStreamAsync())
|
|
||||||
{
|
|
||||||
using (StreamReader reader = new StreamReader(body))
|
|
||||||
{
|
|
||||||
SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, reader));
|
|
||||||
|
|
||||||
try
|
SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
|
||||||
{
|
|
||||||
// The loop will be broken when this stream is closed
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var line = await reader.ReadLineAsync();
|
|
||||||
|
|
||||||
Debug.WriteLine(line);
|
|
||||||
|
|
||||||
NtfyEventObject nev = JsonConvert.DeserializeObject<NtfyEventObject>(line);
|
|
||||||
|
|
||||||
if (nev.Event == "message")
|
|
||||||
{
|
|
||||||
if (OnNotificationReceive != null)
|
|
||||||
{
|
|
||||||
var evArgs = new NotificationReceiveEventArgs(nev.Title, nev.Message);
|
|
||||||
OnNotificationReceive(this, evArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine(ex);
|
|
||||||
|
|
||||||
// If the topic is still registered, then that stream wasn't mean to be closed (maybe network failure?)
|
|
||||||
// Restart it
|
|
||||||
if (SubscribedTopicsByUnique.ContainsKey(unique))
|
|
||||||
{
|
|
||||||
SubscribeToTopic(unique, topicId, serverUrl, username, password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveTopicByUniqueString(string topicUniqueString)
|
public void UnsubscribeFromTopic(string topicUniqueString)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Removing topic {topicUniqueString}");
|
if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
|
||||||
|
|
||||||
if (SubscribedTopicsByUnique.ContainsKey(topicUniqueString))
|
|
||||||
{
|
|
||||||
// Not moronic to store it in a variable; this solves a race condition in SubscribeToTopic
|
|
||||||
SubscribedTopic topic = SubscribedTopicsByUnique[topicUniqueString];
|
|
||||||
topic.Stream.Close();
|
|
||||||
|
|
||||||
SubscribedTopicsByUnique.Remove(topicUniqueString);
|
#if DEBUG
|
||||||
}
|
Debug.WriteLine($"Removing topic {topicUniqueString}");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//Topic isn't even subscribed, ignore
|
||||||
|
if (!SubscribedTopicsByUnique.TryGetValue(topicUniqueString, out SubscribedTopic topic)) return;
|
||||||
|
|
||||||
|
//Cancel and dispose the task runner
|
||||||
|
topic.RunnerCanceller.Cancel();
|
||||||
|
|
||||||
|
//Wait for the task runner to shut down
|
||||||
|
while (!topic.Runner.IsCompleted) Thread.Sleep(100);
|
||||||
|
|
||||||
|
//Dispose task
|
||||||
|
topic.Runner.Dispose();
|
||||||
|
|
||||||
|
//Remove the old topic
|
||||||
|
SubscribedTopicsByUnique.Remove(topicUniqueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~NotificationListener()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
if (_isDisposed) return;
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
_httpClient.Dispose();
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NotificationReceiveEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public string Title { get; private set; }
|
|
||||||
public string Message { get; private set; }
|
|
||||||
|
|
||||||
public NotificationReceiveEventArgs(string title, string message)
|
|
||||||
{
|
|
||||||
Title = title;
|
|
||||||
Message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NtfyEventObject
|
|
||||||
{
|
|
||||||
[JsonProperty("id")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
[JsonProperty("time")]
|
|
||||||
public Int64 Time { get; set; }
|
|
||||||
[JsonProperty("event")]
|
|
||||||
public string Event { get; set; }
|
|
||||||
[JsonProperty("topic")]
|
|
||||||
public string Topic { get; set; }
|
|
||||||
[JsonProperty("message")]
|
|
||||||
public string Message { get; set; }
|
|
||||||
[JsonProperty("title")]
|
|
||||||
public string Title { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
16
ntfysh_client/NotificationReceiveEventArgs.cs
Normal file
16
ntfysh_client/NotificationReceiveEventArgs.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ntfysh_client
|
||||||
|
{
|
||||||
|
public class NotificationReceiveEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string Title { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
public NotificationReceiveEventArgs(string title, string message)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
ntfysh_client/NtfyEvent.cs
Normal file
25
ntfysh_client/NtfyEvent.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace ntfysh_client
|
||||||
|
{
|
||||||
|
public class NtfyEvent
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string Id { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("time")]
|
||||||
|
public long Time { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("event")]
|
||||||
|
public string Event { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("topic")]
|
||||||
|
public string Topic { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("message")]
|
||||||
|
public string Message { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("title")]
|
||||||
|
public string Title { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,8 @@ namespace ntfysh_client
|
|||||||
{
|
{
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
|
private static readonly NotificationListener NotificationListener = new NotificationListener();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entry point for the application.
|
/// The main entry point for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -16,7 +18,7 @@ namespace ntfysh_client
|
|||||||
{
|
{
|
||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
Application.SetCompatibleTextRenderingDefault(false);
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
Application.Run(new Form1());
|
Application.Run(new Form1(NotificationListener));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,16 @@ namespace ntfysh_client
|
|||||||
public partial class SubscribeDialog : Form
|
public partial class SubscribeDialog : Form
|
||||||
{
|
{
|
||||||
private readonly ListBox _notificationTopics;
|
private readonly ListBox _notificationTopics;
|
||||||
|
|
||||||
|
public string TopicId => topicId.Text;
|
||||||
|
|
||||||
|
public string ServerUrl => serverUrl.Text;
|
||||||
|
|
||||||
|
public string Username => username.Text;
|
||||||
|
|
||||||
|
public string Password => password.Text;
|
||||||
|
|
||||||
|
public string Unique => $"{topicId.Text}@{serverUrl.Text}";
|
||||||
|
|
||||||
public SubscribeDialog(ListBox notificationTopics)
|
public SubscribeDialog(ListBox notificationTopics)
|
||||||
{
|
{
|
||||||
@@ -13,31 +23,6 @@ namespace ntfysh_client
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string getTopicId()
|
|
||||||
{
|
|
||||||
return topicId.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getServerUrl()
|
|
||||||
{
|
|
||||||
return serverUrl.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getUsername()
|
|
||||||
{
|
|
||||||
return username.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getPassword()
|
|
||||||
{
|
|
||||||
return password.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getUniqueString()
|
|
||||||
{
|
|
||||||
return $"{topicId.Text}@{serverUrl.Text}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void button1_Click(object sender, EventArgs e)
|
private void button1_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (topicId.Text.Length < 1)
|
if (topicId.Text.Length < 1)
|
||||||
@@ -72,7 +57,7 @@ namespace ntfysh_client
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_notificationTopics.Items.Contains(getUniqueString()))
|
if (_notificationTopics.Items.Contains(Unique))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"The specified topic '{topicId.Text}' on the server '{serverUrl.Text}' is already subscribed", "Topic already subscribed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show($"The specified topic '{topicId.Text}' on the server '{serverUrl.Text}' is already subscribed", "Topic already subscribed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
DialogResult = DialogResult.None;
|
DialogResult = DialogResult.None;
|
||||||
|
@@ -1,25 +1,30 @@
|
|||||||
using System.IO;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace ntfysh_client
|
namespace ntfysh_client
|
||||||
{
|
{
|
||||||
public class SubscribedTopic
|
public class SubscribedTopic
|
||||||
{
|
{
|
||||||
public SubscribedTopic(string topicId, string serverUrl, string username, string password, StreamReader stream)
|
public SubscribedTopic(string topicId, string serverUrl, string? username, string? password, Task runner, CancellationTokenSource runnerCanceller)
|
||||||
{
|
{
|
||||||
TopicId = topicId;
|
TopicId = topicId;
|
||||||
ServerUrl = serverUrl;
|
ServerUrl = serverUrl;
|
||||||
Username = username;
|
Username = username;
|
||||||
Password = password;
|
Password = password;
|
||||||
Stream = stream;
|
Runner = runner;
|
||||||
|
RunnerCanceller = runnerCanceller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string TopicId { get; }
|
public string TopicId { get; }
|
||||||
public string ServerUrl { get; }
|
public string ServerUrl { get; }
|
||||||
public string Username { get; }
|
public string? Username { get; }
|
||||||
public string Password { get; }
|
public string? Password { get; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public StreamReader Stream { get; }
|
public Task Runner { get; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public CancellationTokenSource RunnerCanceller { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,6 +12,8 @@
|
|||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@@ -66,6 +68,8 @@
|
|||||||
<DependentUpon>Form1.cs</DependentUpon>
|
<DependentUpon>Form1.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="NotificationListener.cs" />
|
<Compile Include="NotificationListener.cs" />
|
||||||
|
<Compile Include="NotificationReceiveEventArgs.cs" />
|
||||||
|
<Compile Include="NtfyEvent.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SubscribeDialog.cs">
|
<Compile Include="SubscribeDialog.cs">
|
||||||
|
Reference in New Issue
Block a user