From 0a11d5a583b43cf09020a0af9e8561bb28b76640 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 19:37:39 +0000
Subject: [PATCH 01/14] Add very basic but functional support for
authentication and custom servers
---
.gitignore | 401 +++++++++++++++++++++-
ntfysh_client/Form1.Designer.cs | 70 ++--
ntfysh_client/Form1.cs | 4 +-
ntfysh_client/Form1.resx | 44 +--
ntfysh_client/NotificationListener.cs | 37 +-
ntfysh_client/SubscribeDialog.Designer.cs | 114 +++++-
ntfysh_client/SubscribeDialog.cs | 73 +++-
ntfysh_client/SubscribedTopic.cs | 23 ++
ntfysh_client/ntfysh_client.csproj | 1 +
9 files changed, 662 insertions(+), 105 deletions(-)
create mode 100644 ntfysh_client/SubscribedTopic.cs
diff --git a/.gitignore b/.gitignore
index 103db3d..f08db5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,399 @@
-bin/
-obj/
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
.vs/
-packages/
\ No newline at end of file
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+/.idea
\ No newline at end of file
diff --git a/ntfysh_client/Form1.Designer.cs b/ntfysh_client/Form1.Designer.cs
index e9b6d1e..91caa7e 100644
--- a/ntfysh_client/Form1.Designer.cs
+++ b/ntfysh_client/Form1.Designer.cs
@@ -40,12 +40,12 @@ namespace ntfysh_client
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.label1 = new System.Windows.Forms.Label();
this.exitToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
- this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ntfyshWebsiteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
+ this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.label1 = new System.Windows.Forms.Label();
this.trayContextMenu.SuspendLayout();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
@@ -75,8 +75,7 @@ namespace ntfysh_client
//
// notificationTopics
//
- this.notificationTopics.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
- | System.Windows.Forms.AnchorStyles.Right)));
+ this.notificationTopics.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
this.notificationTopics.FormattingEnabled = true;
this.notificationTopics.Location = new System.Drawing.Point(12, 50);
this.notificationTopics.Name = "notificationTopics";
@@ -96,9 +95,7 @@ namespace ntfysh_client
//
// trayContextMenu
//
- this.trayContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.showControlWindowToolStripMenuItem,
- this.exitToolStripMenuItem});
+ this.trayContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.showControlWindowToolStripMenuItem, this.exitToolStripMenuItem });
this.trayContextMenu.Name = "trayContextMenu";
this.trayContextMenu.Size = new System.Drawing.Size(190, 48);
//
@@ -121,9 +118,7 @@ namespace ntfysh_client
// menuStrip1
//
this.menuStrip1.BackColor = System.Drawing.Color.White;
- this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.fileToolStripMenuItem,
- this.helpToolStripMenuItem});
+ this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileToolStripMenuItem, this.helpToolStripMenuItem });
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(353, 24);
@@ -132,45 +127,24 @@ namespace ntfysh_client
//
// fileToolStripMenuItem
//
- this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.exitToolStripMenuItem1});
+ this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.exitToolStripMenuItem1 });
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
this.fileToolStripMenuItem.Text = "File";
//
- // helpToolStripMenuItem
- //
- this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.ntfyshWebsiteToolStripMenuItem,
- this.toolStripMenuItem1,
- this.aboutToolStripMenuItem});
- this.helpToolStripMenuItem.Name = "helpToolStripMenuItem";
- this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20);
- this.helpToolStripMenuItem.Text = "Help";
- //
- // label1
- //
- this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(12, 34);
- this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(145, 13);
- this.label1.TabIndex = 1;
- this.label1.Text = "Subscribed notification topics";
- //
// exitToolStripMenuItem1
//
this.exitToolStripMenuItem1.Image = ((System.Drawing.Image)(resources.GetObject("exitToolStripMenuItem1.Image")));
this.exitToolStripMenuItem1.Name = "exitToolStripMenuItem1";
- this.exitToolStripMenuItem1.Size = new System.Drawing.Size(180, 22);
+ this.exitToolStripMenuItem1.Size = new System.Drawing.Size(93, 22);
this.exitToolStripMenuItem1.Text = "Exit";
//
- // aboutToolStripMenuItem
+ // helpToolStripMenuItem
//
- this.aboutToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("aboutToolStripMenuItem.Image")));
- this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
- this.aboutToolStripMenuItem.Size = new System.Drawing.Size(185, 22);
- this.aboutToolStripMenuItem.Text = "About";
- this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
+ this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.ntfyshWebsiteToolStripMenuItem, this.toolStripMenuItem1, this.aboutToolStripMenuItem });
+ this.helpToolStripMenuItem.Name = "helpToolStripMenuItem";
+ this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20);
+ this.helpToolStripMenuItem.Text = "Help";
//
// ntfyshWebsiteToolStripMenuItem
//
@@ -185,6 +159,23 @@ namespace ntfysh_client
this.toolStripMenuItem1.Name = "toolStripMenuItem1";
this.toolStripMenuItem1.Size = new System.Drawing.Size(182, 6);
//
+ // aboutToolStripMenuItem
+ //
+ this.aboutToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("aboutToolStripMenuItem.Image")));
+ this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
+ this.aboutToolStripMenuItem.Size = new System.Drawing.Size(185, 22);
+ this.aboutToolStripMenuItem.Text = "About";
+ this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(12, 34);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(145, 13);
+ this.label1.TabIndex = 1;
+ this.label1.Text = "Subscribed notification topics";
+ //
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -213,7 +204,6 @@ namespace ntfysh_client
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
-
}
#endregion
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index 2963b0b..e3e441c 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -36,7 +36,7 @@ namespace ntfysh_client
if (result == DialogResult.OK)
{
- notificationListener.SubscribeToTopic(dialog.getTopicId());
+ notificationListener.SubscribeToTopic(dialog.getTopicId(), dialog.getServerUrl(), dialog.getUsername(), dialog.getPassword());
notificationTopics.Items.Add(dialog.getTopicId());
this.SaveTopicsToFile();
}
@@ -117,7 +117,7 @@ namespace ntfysh_client
while (!reader.EndOfStream)
{
var topic = reader.ReadLine();
- notificationListener.SubscribeToTopic(topic);
+ notificationListener.SubscribeToTopic(topic, "https://ntfy.sh", null, null);
notificationTopics.Items.Add(topic);
}
}
diff --git a/ntfysh_client/Form1.resx b/ntfysh_client/Form1.resx
index 4b4cee7..7adc2ca 100644
--- a/ntfysh_client/Form1.resx
+++ b/ntfysh_client/Form1.resx
@@ -124,6 +124,24 @@
123, 17
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAiklE
+ QVQ4T8WPQQqAIBBFvUOrXIhu3dcZOkqtu5R1ia4SdRDrxyyGGKmBIOGBDL6nmk9WCGE9yUpW0q9Ads7V
+ GuCQ/kHAe79joAEO6dcLFmttpQEO6c+Bvpm2oZ0zwB4zVQBiF8cIsMdMDPCb+G2vA/wgP/z6C6WAhBgo
+ fUFCDGi4BxIGShLpvy5jDoPes/0oNG3VAAAAAElFTkSuQmCC
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAgUlE
+ QVQ4T2OgGjjOLq4AZeIFWNWBBI9yib06zC3uABXCCkDyIHVYDTnMI2pzhEvs5VFucSeoEAo4wiNii08e
+ DHAZQshwFICumCTNMADyK1gTl2gJiCYUNlgBSDPQ1v8gGipEPKDIBRSFAa6oIsoQQvGM1xCqpESsglgA
+ seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
+
+
AAABAAEAMjIAAAEAIADIKAAAFgAAACgAAAAyAAAAZAAAAAEAIAAAAAAAECcAACMuAAAjLgAAAAAAAAAA
@@ -303,16 +321,10 @@
/////8AA////////wAD////////AAA==
-
-
- iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAiklE
- QVQ4T8WPQQqAIBBFvUOrXIhu3dcZOkqtu5R1ia4SdRDrxyyGGKmBIOGBDL6nmk9WCGE9yUpW0q9Ads7V
- GuCQ/kHAe79joAEO6dcLFmttpQEO6c+Bvpm2oZ0zwB4zVQBiF8cIsMdMDPCb+G2vA/wgP/z6C6WAhBgo
- fUFCDGi4BxIGShLpvy5jDoPes/0oNG3VAAAAAElFTkSuQmCC
-
-
-
+
+ 269, 17
+
+
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAgUlE
@@ -321,9 +333,6 @@
seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
-
- 269, 17
-
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
@@ -343,15 +352,6 @@
dDIA6OTNQHX/YXysBjAxMcUxMjKuBrGB9DIgPxqmARhwOUBD3sD4WA0QERGRBHLvCwoKygLpm6KiohIw
DVBNz5HY2L0AtHkOEE8F4okgvoyMjDSSJsIGsLOzuwKFfnBycppJS0vLAJ39BUQTbQA6RnYBMkY3YAVI
gES8Aqp9QAEDAwCq9oYvtggceQAAAABJRU5ErkJggg==
-
-
-
-
- iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAgUlE
- QVQ4T2OgGjjOLq4AZeIFWNWBBI9yib06zC3uABXCCkDyIHVYDTnMI2pzhEvs5VFucSeoEAo4wiNii08e
- DHAZQshwFICumCTNMADyK1gTl2gJiCYUNlgBSDPQ1v8gGipEPKDIBRSFAa6oIsoQQvGM1xCqpESsglgA
- seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index 75f6118..54e5bf1 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -19,7 +20,7 @@ namespace ntfysh_client
private bool disposedValue;
- public Dictionary subscribedTopics;
+ public readonly Dictionary SubscribedTopics = new Dictionary();
public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
public event NotificationReceiveHandler OnNotificationReceive;
@@ -27,22 +28,32 @@ namespace ntfysh_client
public NotificationListener()
{
httpClient = new HttpClient();
- subscribedTopics = new Dictionary();
httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
ServicePointManager.DefaultConnectionLimit = 100;
}
- public async Task SubscribeToTopic(string topicId)
+ public async Task SubscribeToTopic(string topicId, string serverUrl, string username, string password)
{
- HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, $"https://ntfy.sh/{HttpUtility.UrlEncode(topicId)}/json");
- using (var response = await httpClient.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead))
+ if (string.IsNullOrWhiteSpace(username)) username = null;
+ if (string.IsNullOrWhiteSpace(password)) password = null;
+
+ HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, $"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/json");
+
+ if (username != null && password != null)
{
- using (var body = await response.Content.ReadAsStreamAsync())
+ byte[] boundCredentialsBytes = Encoding.UTF8.GetBytes($"{username}:{password}");
+
+ msg.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(boundCredentialsBytes));
+ }
+
+ using (HttpResponseMessage response = await httpClient.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead))
+ {
+ using (Stream body = await response.Content.ReadAsStreamAsync())
{
using (StreamReader reader = new StreamReader(body))
{
- subscribedTopics.Add(topicId, reader);
+ SubscribedTopics.Add(topicId, new SubscribedTopic(topicId, serverUrl, username, password, reader));
try
{
@@ -71,9 +82,9 @@ namespace ntfysh_client
// If the topic is still registered, then that stream wasn't mean to be closed (maybe network failure?)
// Restart it
- if (subscribedTopics.ContainsKey(topicId))
+ if (SubscribedTopics.ContainsKey(topicId))
{
- SubscribeToTopic(topicId);
+ SubscribeToTopic(topicId, serverUrl, username, password);
}
}
}
@@ -85,12 +96,12 @@ namespace ntfysh_client
{
Debug.WriteLine($"Removing topic {topicId}");
- if (subscribedTopics.ContainsKey(topicId))
+ if (SubscribedTopics.ContainsKey(topicId))
{
// Not moronic to store it in a variable; this solves a race condition in SubscribeToTopic
- var topic = subscribedTopics[topicId];
- subscribedTopics.Remove(topicId);
- topic.Close();
+ var topic = SubscribedTopics[topicId];
+ SubscribedTopics.Remove(topicId);
+ topic.Stream.Close();
}
}
diff --git a/ntfysh_client/SubscribeDialog.Designer.cs b/ntfysh_client/SubscribeDialog.Designer.cs
index e7d369e..30d5a60 100644
--- a/ntfysh_client/SubscribeDialog.Designer.cs
+++ b/ntfysh_client/SubscribeDialog.Designer.cs
@@ -30,10 +30,16 @@ namespace ntfysh_client
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
- this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
+ this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.topicId = new System.Windows.Forms.TextBox();
+ this.serverUrl = new System.Windows.Forms.TextBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.username = new System.Windows.Forms.TextBox();
+ this.label3 = new System.Windows.Forms.Label();
+ this.password = new System.Windows.Forms.TextBox();
+ this.label4 = new System.Windows.Forms.Label();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
@@ -43,23 +49,11 @@ namespace ntfysh_client
this.panel1.Controls.Add(this.button2);
this.panel1.Controls.Add(this.button1);
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.panel1.Location = new System.Drawing.Point(0, 81);
+ this.panel1.Location = new System.Drawing.Point(0, 175);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(297, 44);
this.panel1.TabIndex = 0;
//
- // button1
- //
- this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.button1.Location = new System.Drawing.Point(212, 11);
- this.button1.Margin = new System.Windows.Forms.Padding(3, 10, 10, 10);
- this.button1.Name = "button1";
- this.button1.Size = new System.Drawing.Size(75, 23);
- this.button1.TabIndex = 2;
- this.button1.Text = "Subscribe";
- this.button1.UseVisualStyleBackColor = true;
- this.button1.Click += new System.EventHandler(this.button1_Click);
- //
// button2
//
this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
@@ -72,10 +66,22 @@ namespace ntfysh_client
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
+ // button1
+ //
+ this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.button1.Location = new System.Drawing.Point(212, 11);
+ this.button1.Margin = new System.Windows.Forms.Padding(3, 10, 10, 10);
+ this.button1.Name = "button1";
+ this.button1.Size = new System.Drawing.Size(75, 23);
+ this.button1.TabIndex = 2;
+ this.button1.Text = "Subscribe";
+ this.button1.UseVisualStyleBackColor = true;
+ this.button1.Click += new System.EventHandler(this.button1_Click);
+ //
// label1
//
this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(12, 23);
+ this.label1.Location = new System.Drawing.Point(12, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(51, 13);
this.label1.TabIndex = 1;
@@ -83,20 +89,85 @@ namespace ntfysh_client
//
// topicId
//
- this.topicId.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ this.topicId.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
- this.topicId.Location = new System.Drawing.Point(15, 39);
+ this.topicId.Location = new System.Drawing.Point(12, 25);
this.topicId.Name = "topicId";
this.topicId.Size = new System.Drawing.Size(273, 20);
this.topicId.TabIndex = 0;
this.topicId.KeyDown += new System.Windows.Forms.KeyEventHandler(this.topicId_KeyDown);
//
+ // serverUrl
+ //
+ this.serverUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.serverUrl.Location = new System.Drawing.Point(12, 64);
+ this.serverUrl.Name = "serverUrl";
+ this.serverUrl.Size = new System.Drawing.Size(273, 20);
+ this.serverUrl.TabIndex = 2;
+ this.serverUrl.Text = "https://ntfy.sh";
+ this.serverUrl.KeyDown += new System.Windows.Forms.KeyEventHandler(this.serverUrl_KeyDown);
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(10, 48);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(66, 13);
+ this.label2.TabIndex = 3;
+ this.label2.Text = "Server URL:";
+ //
+ // username
+ //
+ this.username.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.username.Location = new System.Drawing.Point(12, 103);
+ this.username.Name = "username";
+ this.username.Size = new System.Drawing.Size(273, 20);
+ this.username.TabIndex = 4;
+ this.username.KeyDown += new System.Windows.Forms.KeyEventHandler(this.username_KeyDown);
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(10, 87);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(58, 13);
+ this.label3.TabIndex = 5;
+ this.label3.Text = "Username:";
+ //
+ // password
+ //
+ this.password.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.password.Location = new System.Drawing.Point(12, 142);
+ this.password.Name = "password";
+ this.password.Size = new System.Drawing.Size(273, 20);
+ this.password.TabIndex = 6;
+ this.password.UseSystemPasswordChar = true;
+ this.password.KeyDown += new System.Windows.Forms.KeyEventHandler(this.password_KeyDown);
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(10, 126);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(56, 13);
+ this.label4.TabIndex = 7;
+ this.label4.Text = "Password:";
+ //
// SubscribeDialog
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
- this.ClientSize = new System.Drawing.Size(297, 125);
+ this.ClientSize = new System.Drawing.Size(297, 219);
+ this.Controls.Add(this.password);
+ this.Controls.Add(this.label4);
+ this.Controls.Add(this.username);
+ this.Controls.Add(this.label3);
+ this.Controls.Add(this.serverUrl);
+ this.Controls.Add(this.label2);
this.Controls.Add(this.topicId);
this.Controls.Add(this.label1);
this.Controls.Add(this.panel1);
@@ -114,6 +185,9 @@ namespace ntfysh_client
}
+ private System.Windows.Forms.TextBox serverUrl;
+ private System.Windows.Forms.Label label2;
+
#endregion
private System.Windows.Forms.Panel panel1;
@@ -121,5 +195,9 @@ namespace ntfysh_client
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox topicId;
+ private System.Windows.Forms.TextBox username;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TextBox password;
+ private System.Windows.Forms.Label label4;
}
}
\ No newline at end of file
diff --git a/ntfysh_client/SubscribeDialog.cs b/ntfysh_client/SubscribeDialog.cs
index 7e4ded3..861f3fe 100644
--- a/ntfysh_client/SubscribeDialog.cs
+++ b/ntfysh_client/SubscribeDialog.cs
@@ -1,11 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Forms;
namespace ntfysh_client
@@ -21,6 +14,21 @@ namespace ntfysh_client
{
return topicId.Text;
}
+
+ public string getServerUrl()
+ {
+ return serverUrl.Text;
+ }
+
+ public string getUsername()
+ {
+ return username.Text;
+ }
+
+ public string getPassword()
+ {
+ return password.Text;
+ }
private void button1_Click(object sender, EventArgs e)
{
@@ -32,6 +40,30 @@ namespace ntfysh_client
return;
}
+ if (serverUrl.Text.Length < 1)
+ {
+ MessageBox.Show("You must specify a server URL. The default is https://ntfy.sh", "Server URL not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ DialogResult = DialogResult.None;
+ serverUrl.Focus();
+ return;
+ }
+
+ if (username.Text.Length > 0 && password.Text.Length < 1)
+ {
+ MessageBox.Show("You must specify a password alongside the username", "Password not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ DialogResult = DialogResult.None;
+ password.Focus();
+ return;
+ }
+
+ if (password.Text.Length > 0 && username.Text.Length < 1)
+ {
+ MessageBox.Show("You must specify a username alongside the password", "Username not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ DialogResult = DialogResult.None;
+ username.Focus();
+ return;
+ }
+
DialogResult = DialogResult.OK;
}
@@ -48,5 +80,32 @@ namespace ntfysh_client
e.SuppressKeyPress = true;
}
}
+
+ private void serverUrl_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.KeyData == Keys.Enter)
+ {
+ button1.PerformClick();
+ e.SuppressKeyPress = true;
+ }
+ }
+
+ private void username_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.KeyData == Keys.Enter)
+ {
+ button1.PerformClick();
+ e.SuppressKeyPress = true;
+ }
+ }
+
+ private void password_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.KeyData == Keys.Enter)
+ {
+ button1.PerformClick();
+ e.SuppressKeyPress = true;
+ }
+ }
}
}
diff --git a/ntfysh_client/SubscribedTopic.cs b/ntfysh_client/SubscribedTopic.cs
new file mode 100644
index 0000000..5d51bef
--- /dev/null
+++ b/ntfysh_client/SubscribedTopic.cs
@@ -0,0 +1,23 @@
+using System.IO;
+
+namespace ntfysh_client
+{
+ public class SubscribedTopic
+ {
+ public SubscribedTopic(string topicId, string serverUrl, string username, string password, StreamReader stream)
+ {
+ TopicId = topicId;
+ ServerUrl = serverUrl;
+ Username = username;
+ Password = password;
+ Stream = stream;
+ }
+
+ public string TopicId { get; }
+ public string ServerUrl { get; }
+ public string Username { get; }
+ public string Password { get; }
+
+ public StreamReader Stream { get; }
+ }
+}
\ No newline at end of file
diff --git a/ntfysh_client/ntfysh_client.csproj b/ntfysh_client/ntfysh_client.csproj
index b6e42a2..ad03786 100644
--- a/ntfysh_client/ntfysh_client.csproj
+++ b/ntfysh_client/ntfysh_client.csproj
@@ -74,6 +74,7 @@
SubscribeDialog.cs
+
AboutBox.cs
From 6aa48fdd2f6b45668af9e99cbeb99d889ae4eb76 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 19:59:32 +0000
Subject: [PATCH 02/14] Start some really basic persistence support. Buggy
because of async missing
---
ntfysh_client/Form1.cs | 82 ++++++++++++++++++++++++++------
ntfysh_client/SubscribedTopic.cs | 2 +
2 files changed, 69 insertions(+), 15 deletions(-)
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index e3e441c..cd451c5 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -9,6 +9,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
+using Newtonsoft.Json;
namespace ntfysh_client
{
@@ -94,32 +95,83 @@ namespace ntfysh_client
private string GetTopicsFilePath()
{
- string binaryDirectory = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
- return Path.Combine(binaryDirectory, "topics.txt");
+ string binaryDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ return Path.Combine(binaryDirectory ?? throw new InvalidOperationException("Unable to determine path for topics file"), "topics.json");
+ }
+
+ private string GetLegacyTopicsFilePath()
+ {
+ string binaryDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ return Path.Combine(binaryDirectory ?? throw new InvalidOperationException("Unable to determine path for legacy topics file"), "topics.txt");
}
private void SaveTopicsToFile()
{
- using (StreamWriter writer = new StreamWriter(GetTopicsFilePath()))
- {
- foreach (string topic in notificationTopics.Items)
- {
- writer.WriteLine(topic);
- }
- }
+ string topicsSerialised = JsonConvert.SerializeObject(notificationListener.SubscribedTopics.Select(st => st.Value).ToList(), Formatting.Indented);
+
+ File.WriteAllText(GetTopicsFilePath(), topicsSerialised);
}
private void LoadTopics()
{
- if (!File.Exists(GetTopicsFilePath())) return;
- using (StreamReader reader = new StreamReader(GetTopicsFilePath()))
+ string legacyTopicsPath = GetLegacyTopicsFilePath();
+ string topicsFilePath = GetTopicsFilePath();
+
+ //If we have an old format topics file. Convert it to the new format!
+ if (File.Exists(legacyTopicsPath))
{
- while (!reader.EndOfStream)
+ //Read old format
+ List legacyTopics = new List();
+
+ using (StreamReader reader = new StreamReader(legacyTopicsPath))
{
- var topic = reader.ReadLine();
- notificationListener.SubscribeToTopic(topic, "https://ntfy.sh", null, null);
- notificationTopics.Items.Add(topic);
+ while (!reader.EndOfStream)
+ {
+ string legacyTopic = reader.ReadLine();
+ legacyTopics.Add(legacyTopic);
+ }
}
+
+ //Assemble new format
+ List newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null, null)).ToList();
+
+ string newFormatSerialised = JsonConvert.SerializeObject(newTopics, Formatting.Indented);
+
+ //Write new format
+ File.WriteAllText(topicsFilePath, newFormatSerialised);
+
+ //Delete old format
+ File.Delete(legacyTopicsPath);
+ }
+
+ //Check if we have any topics file on disk to load
+ if (!File.Exists(topicsFilePath)) return;
+
+ //We have a topics file. Load it!
+ string topicsSerialised = File.ReadAllText(topicsFilePath);
+
+ //Check if the file is empty
+ if (string.IsNullOrWhiteSpace(topicsSerialised))
+ {
+ //The file is empty. May as well remove it and consider it nonexistent
+ File.Delete(topicsFilePath);
+ return;
+ }
+
+ //Deserialise the topics
+ List topics = JsonConvert.DeserializeObject>(topicsSerialised);
+
+ if (topics == null)
+ {
+ //TODO Deserialise error!
+ return;
+ }
+
+ //Load them in
+ foreach (SubscribedTopic topic in topics)
+ {
+ notificationListener.SubscribeToTopic(topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
+ notificationTopics.Items.Add(topic.TopicId);
}
}
diff --git a/ntfysh_client/SubscribedTopic.cs b/ntfysh_client/SubscribedTopic.cs
index 5d51bef..184f768 100644
--- a/ntfysh_client/SubscribedTopic.cs
+++ b/ntfysh_client/SubscribedTopic.cs
@@ -1,4 +1,5 @@
using System.IO;
+using Newtonsoft.Json;
namespace ntfysh_client
{
@@ -18,6 +19,7 @@ namespace ntfysh_client
public string Username { get; }
public string Password { get; }
+ [JsonIgnore]
public StreamReader Stream { get; }
}
}
\ No newline at end of file
From 5a4dfc01b63a1cfcccc2d8a34835c7a4e1cc4aa6 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 20:14:45 +0000
Subject: [PATCH 03/14] Fix topic uniqueness issue
---
ntfysh_client/Form1.cs | 19 ++++++++++---------
ntfysh_client/NotificationListener.cs | 21 +++++++++++----------
ntfysh_client/SubscribeDialog.cs | 18 +++++++++++++++++-
3 files changed, 38 insertions(+), 20 deletions(-)
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index cd451c5..6c3b9e1 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -31,14 +31,14 @@ namespace ntfysh_client
private void subscribeNewTopic_Click(object sender, EventArgs e)
{
- using (var dialog = new SubscribeDialog())
+ using (var dialog = new SubscribeDialog(notificationTopics))
{
var result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
- notificationListener.SubscribeToTopic(dialog.getTopicId(), dialog.getServerUrl(), dialog.getUsername(), dialog.getPassword());
- notificationTopics.Items.Add(dialog.getTopicId());
+ notificationListener.SubscribeToTopic(dialog.getUniqueString(), dialog.getTopicId(), dialog.getServerUrl(), dialog.getUsername(), dialog.getPassword());
+ notificationTopics.Items.Add(dialog.getUniqueString());
this.SaveTopicsToFile();
}
}
@@ -48,9 +48,10 @@ namespace ntfysh_client
{
while (notificationTopics.SelectedIndex > -1)
{
- var topicId = notificationTopics.Items[notificationTopics.SelectedIndex];
- notificationListener.RemoveTopic((string)topicId);
- notificationTopics.Items.RemoveAt(notificationTopics.SelectedIndex);
+ string topicUniqueString = (string)notificationTopics.Items[notificationTopics.SelectedIndex];
+
+ notificationListener.RemoveTopicByUniqueString(topicUniqueString);
+ notificationTopics.Items.Remove(topicUniqueString);
}
this.SaveTopicsToFile();
@@ -107,7 +108,7 @@ namespace ntfysh_client
private void SaveTopicsToFile()
{
- string topicsSerialised = JsonConvert.SerializeObject(notificationListener.SubscribedTopics.Select(st => st.Value).ToList(), Formatting.Indented);
+ string topicsSerialised = JsonConvert.SerializeObject(notificationListener.SubscribedTopicsByUnique.Select(st => st.Value).ToList(), Formatting.Indented);
File.WriteAllText(GetTopicsFilePath(), topicsSerialised);
}
@@ -170,8 +171,8 @@ namespace ntfysh_client
//Load them in
foreach (SubscribedTopic topic in topics)
{
- notificationListener.SubscribeToTopic(topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
- notificationTopics.Items.Add(topic.TopicId);
+ notificationListener.SubscribeToTopic($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
+ notificationTopics.Items.Add($"{topic.TopicId}@{topic.ServerUrl}");
}
}
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index 54e5bf1..a7057ca 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -20,7 +20,7 @@ namespace ntfysh_client
private bool disposedValue;
- public readonly Dictionary SubscribedTopics = new Dictionary();
+ public readonly Dictionary SubscribedTopicsByUnique = new Dictionary();
public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
public event NotificationReceiveHandler OnNotificationReceive;
@@ -33,7 +33,7 @@ namespace ntfysh_client
ServicePointManager.DefaultConnectionLimit = 100;
}
- public async Task SubscribeToTopic(string topicId, string serverUrl, string username, string password)
+ public async Task SubscribeToTopic(string unique, string topicId, string serverUrl, string username, string password)
{
if (string.IsNullOrWhiteSpace(username)) username = null;
if (string.IsNullOrWhiteSpace(password)) password = null;
@@ -53,7 +53,7 @@ namespace ntfysh_client
{
using (StreamReader reader = new StreamReader(body))
{
- SubscribedTopics.Add(topicId, new SubscribedTopic(topicId, serverUrl, username, password, reader));
+ SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, reader));
try
{
@@ -82,9 +82,9 @@ namespace ntfysh_client
// If the topic is still registered, then that stream wasn't mean to be closed (maybe network failure?)
// Restart it
- if (SubscribedTopics.ContainsKey(topicId))
+ if (SubscribedTopicsByUnique.ContainsKey(unique))
{
- SubscribeToTopic(topicId, serverUrl, username, password);
+ SubscribeToTopic(unique, topicId, serverUrl, username, password);
}
}
}
@@ -92,16 +92,17 @@ namespace ntfysh_client
}
}
- public void RemoveTopic(string topicId)
+ public void RemoveTopicByUniqueString(string topicUniqueString)
{
- Debug.WriteLine($"Removing topic {topicId}");
+ Debug.WriteLine($"Removing topic {topicUniqueString}");
- if (SubscribedTopics.ContainsKey(topicId))
+ if (SubscribedTopicsByUnique.ContainsKey(topicUniqueString))
{
// Not moronic to store it in a variable; this solves a race condition in SubscribeToTopic
- var topic = SubscribedTopics[topicId];
- SubscribedTopics.Remove(topicId);
+ SubscribedTopic topic = SubscribedTopicsByUnique[topicUniqueString];
topic.Stream.Close();
+
+ SubscribedTopicsByUnique.Remove(topicUniqueString);
}
}
diff --git a/ntfysh_client/SubscribeDialog.cs b/ntfysh_client/SubscribeDialog.cs
index 861f3fe..f5c697e 100644
--- a/ntfysh_client/SubscribeDialog.cs
+++ b/ntfysh_client/SubscribeDialog.cs
@@ -5,8 +5,11 @@ namespace ntfysh_client
{
public partial class SubscribeDialog : Form
{
- public SubscribeDialog()
+ private readonly ListBox _notificationTopics;
+
+ public SubscribeDialog(ListBox notificationTopics)
{
+ _notificationTopics = notificationTopics;
InitializeComponent();
}
@@ -30,6 +33,11 @@ namespace ntfysh_client
return password.Text;
}
+ public string getUniqueString()
+ {
+ return $"{topicId.Text}@{serverUrl.Text}";
+ }
+
private void button1_Click(object sender, EventArgs e)
{
if (topicId.Text.Length < 1)
@@ -64,6 +72,14 @@ namespace ntfysh_client
return;
}
+ if (_notificationTopics.Items.Contains(getUniqueString()))
+ {
+ 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;
+ username.Focus();
+ return;
+ }
+
DialogResult = DialogResult.OK;
}
From ed5a43a0e8ac014f0df8dae8d4e040325c326b52 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 22:34:32 +0000
Subject: [PATCH 04/14] Complete refactor, reasync, rebuild listener
---
ntfysh_client/Form1.cs | 108 ++++-----
ntfysh_client/NotificationListener.cs | 229 +++++++++---------
ntfysh_client/NotificationReceiveEventArgs.cs | 16 ++
ntfysh_client/NtfyEvent.cs | 25 ++
ntfysh_client/Program.cs | 4 +-
ntfysh_client/SubscribeDialog.cs | 37 +--
ntfysh_client/SubscribedTopic.cs | 17 +-
ntfysh_client/ntfysh_client.csproj | 4 +
8 files changed, 234 insertions(+), 206 deletions(-)
create mode 100644 ntfysh_client/NotificationReceiveEventArgs.cs
create mode 100644 ntfysh_client/NtfyEvent.cs
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index 6c3b9e1..5666191 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -15,33 +15,35 @@ namespace ntfysh_client
{
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.OnNotificationReceive += OnNotificationReceive;
+ _notificationListener = notificationListener;
+ _notificationListener.OnNotificationReceive += OnNotificationReceive;
+
InitializeComponent();
}
- private void Form1_Load(object sender, EventArgs e)
- {
- this.LoadTopics();
- }
+ private void Form1_Load(object sender, EventArgs e) => LoadTopics();
private void subscribeNewTopic_Click(object sender, EventArgs e)
{
- using (var dialog = new SubscribeDialog(notificationTopics))
- {
- var result = dialog.ShowDialog();
+ using SubscribeDialog dialog = new SubscribeDialog(notificationTopics);
+ DialogResult result = dialog.ShowDialog();
- if (result == DialogResult.OK)
- {
- notificationListener.SubscribeToTopic(dialog.getUniqueString(), dialog.getTopicId(), dialog.getServerUrl(), dialog.getUsername(), dialog.getPassword());
- notificationTopics.Items.Add(dialog.getUniqueString());
- this.SaveTopicsToFile();
- }
- }
+ //Do not subscribe on cancelled dialog
+ if (result != DialogResult.OK) return;
+
+ //Subscribe
+ _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)
@@ -50,11 +52,11 @@ namespace ntfysh_client
{
string topicUniqueString = (string)notificationTopics.Items[notificationTopics.SelectedIndex];
- notificationListener.RemoveTopicByUniqueString(topicUniqueString);
+ _notificationListener.UnsubscribeFromTopic(topicUniqueString);
notificationTopics.Items.Remove(topicUniqueString);
}
- this.SaveTopicsToFile();
+ SaveTopicsToFile();
}
private void notificationTopics_SelectedValueChanged(object sender, EventArgs e)
@@ -64,51 +66,48 @@ namespace ntfysh_client
private void notificationTopics_Click(object sender, EventArgs e)
{
- var ev = (MouseEventArgs)e;
- var clickedItemIndex = notificationTopics.IndexFromPoint(new Point(ev.X, ev.Y));
+ MouseEventArgs ev = (MouseEventArgs)e;
+ int clickedItemIndex = notificationTopics.IndexFromPoint(new Point(ev.X, ev.Y));
- if (clickedItemIndex == -1)
- {
- notificationTopics.ClearSelected();
- }
+ if (clickedItemIndex == -1) notificationTopics.ClearSelected();
}
private void button1_Click(object sender, EventArgs e)
{
- this.Visible = false;
+ Visible = false;
}
private void notifyIcon_Click(object sender, EventArgs e)
{
- var mouseEv = (MouseEventArgs)e;
- if (mouseEv.Button == MouseButtons.Left)
- {
- this.Visible = !this.Visible;
- this.BringToFront();
- }
+ MouseEventArgs mouseEv = (MouseEventArgs)e;
+
+ if (mouseEv.Button != MouseButtons.Left) return;
+
+ Visible = !Visible;
+ BringToFront();
}
private void showControlWindowToolStripMenuItem_Click(object sender, EventArgs e)
{
- this.Visible = true;
- this.BringToFront();
+ Visible = true;
+ BringToFront();
}
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");
}
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");
}
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);
}
@@ -128,13 +127,13 @@ namespace ntfysh_client
{
while (!reader.EndOfStream)
{
- string legacyTopic = reader.ReadLine();
- legacyTopics.Add(legacyTopic);
+ string? legacyTopic = reader.ReadLine();
+ if (!string.IsNullOrWhiteSpace(legacyTopic)) legacyTopics.Add(legacyTopic);
}
}
//Assemble new format
- List newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null, null)).ToList();
+ List newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null, null, null)).ToList();
string newFormatSerialised = JsonConvert.SerializeObject(newTopics, Formatting.Indented);
@@ -160,9 +159,9 @@ namespace ntfysh_client
}
//Deserialise the topics
- List topics = JsonConvert.DeserializeObject>(topicsSerialised);
+ List? topics = JsonConvert.DeserializeObject>(topicsSerialised);
- if (topics == null)
+ if (topics is null)
{
//TODO Deserialise error!
return;
@@ -171,7 +170,7 @@ namespace ntfysh_client
//Load them in
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}");
}
}
@@ -185,24 +184,22 @@ namespace ntfysh_client
{
notifyIcon.Dispose();
}
-
- private bool trueExit = false;
+
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// Let it close
- if (trueExit) return;
+ if (_trueExit) return;
- if (e.CloseReason == CloseReason.UserClosing)
- {
- this.Visible = false;
- e.Cancel = true;
- }
+ if (e.CloseReason != CloseReason.UserClosing) return;
+
+ Visible = false;
+ e.Cancel = true;
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
- trueExit = true;
- this.Close();
+ _trueExit = true;
+ Close();
}
private void ntfyshWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
@@ -212,9 +209,8 @@ namespace ntfysh_client
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
- var d = new AboutBox();
+ using AboutBox d = new AboutBox();
d.ShowDialog();
- d.Dispose();
}
}
}
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index a7057ca..408c274 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -14,153 +15,147 @@ using System.Web;
namespace ntfysh_client
{
- class NotificationListener : IDisposable
+ public class NotificationListener : IDisposable
{
- private HttpClient httpClient;
-
- private bool disposedValue;
+ private readonly HttpClient _httpClient = new();
+ private bool _isDisposed;
- public readonly Dictionary SubscribedTopicsByUnique = new Dictionary();
+ public readonly Dictionary SubscribedTopicsByUnique = new();
public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
- public event NotificationReceiveHandler OnNotificationReceive;
+ public event NotificationReceiveHandler? OnNotificationReceive;
public NotificationListener()
{
- httpClient = new HttpClient();
-
- httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
+ _httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
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 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(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(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)
{
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))
- {
- using (Stream body = await response.Content.ReadAsStreamAsync())
- {
- using (StreamReader reader = new StreamReader(body))
- {
- SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, reader));
+ CancellationTokenSource listenCanceller = new();
+ Task listenTask = ListenToTopicAsync(message, listenCanceller.Token);
- try
- {
- // The loop will be broken when this stream is closed
- while (true)
- {
- var line = await reader.ReadLineAsync();
-
- Debug.WriteLine(line);
-
- NtfyEventObject nev = JsonConvert.DeserializeObject(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);
- }
- }
- }
- }
- }
+ SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
}
- public void RemoveTopicByUniqueString(string topicUniqueString)
+ public void UnsubscribeFromTopic(string topicUniqueString)
{
- Debug.WriteLine($"Removing topic {topicUniqueString}");
-
- 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();
+ if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
- 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()
{
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
+ if (_isDisposed) return;
+
+ _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; }
- }
}
diff --git a/ntfysh_client/NotificationReceiveEventArgs.cs b/ntfysh_client/NotificationReceiveEventArgs.cs
new file mode 100644
index 0000000..87d932e
--- /dev/null
+++ b/ntfysh_client/NotificationReceiveEventArgs.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ntfysh_client/NtfyEvent.cs b/ntfysh_client/NtfyEvent.cs
new file mode 100644
index 0000000..ef4ee82
--- /dev/null
+++ b/ntfysh_client/NtfyEvent.cs
@@ -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!;
+ }
+}
\ No newline at end of file
diff --git a/ntfysh_client/Program.cs b/ntfysh_client/Program.cs
index d9332fa..a980f95 100644
--- a/ntfysh_client/Program.cs
+++ b/ntfysh_client/Program.cs
@@ -8,6 +8,8 @@ namespace ntfysh_client
{
static class Program
{
+ private static readonly NotificationListener NotificationListener = new NotificationListener();
+
///
/// The main entry point for the application.
///
@@ -16,7 +18,7 @@ namespace ntfysh_client
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new Form1());
+ Application.Run(new Form1(NotificationListener));
}
}
}
diff --git a/ntfysh_client/SubscribeDialog.cs b/ntfysh_client/SubscribeDialog.cs
index f5c697e..e5233f6 100644
--- a/ntfysh_client/SubscribeDialog.cs
+++ b/ntfysh_client/SubscribeDialog.cs
@@ -6,6 +6,16 @@ namespace ntfysh_client
public partial class SubscribeDialog : Form
{
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)
{
@@ -13,31 +23,6 @@ namespace ntfysh_client
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)
{
if (topicId.Text.Length < 1)
@@ -72,7 +57,7 @@ namespace ntfysh_client
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);
DialogResult = DialogResult.None;
diff --git a/ntfysh_client/SubscribedTopic.cs b/ntfysh_client/SubscribedTopic.cs
index 184f768..f5b172b 100644
--- a/ntfysh_client/SubscribedTopic.cs
+++ b/ntfysh_client/SubscribedTopic.cs
@@ -1,25 +1,30 @@
-using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ntfysh_client
{
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;
ServerUrl = serverUrl;
Username = username;
Password = password;
- Stream = stream;
+ Runner = runner;
+ RunnerCanceller = runnerCanceller;
}
public string TopicId { get; }
public string ServerUrl { get; }
- public string Username { get; }
- public string Password { get; }
+ public string? Username { get; }
+ public string? Password { get; }
[JsonIgnore]
- public StreamReader Stream { get; }
+ public Task Runner { get; }
+
+ [JsonIgnore]
+ public CancellationTokenSource RunnerCanceller { get; }
}
}
\ No newline at end of file
diff --git a/ntfysh_client/ntfysh_client.csproj b/ntfysh_client/ntfysh_client.csproj
index ad03786..3708738 100644
--- a/ntfysh_client/ntfysh_client.csproj
+++ b/ntfysh_client/ntfysh_client.csproj
@@ -12,6 +12,8 @@
512
true
true
+ latest
+ enable
AnyCPU
@@ -66,6 +68,8 @@
Form1.cs
+
+
From fde9a03887af8f7fd5facffb80a9f0caead0bdf7 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 23:17:03 +0000
Subject: [PATCH 05/14] Upgrade to .NET Core 6 (Windows Only Target) and fix UI
thread deadlock bug
---
ntfysh_client/NotificationListener.cs | 16 +--
ntfysh_client/Program.cs | 1 +
ntfysh_client/Properties/AssemblyInfo.cs | 36 ------
.../Properties/Resources.Designer.cs | 63 ----------
ntfysh_client/Properties/Resources.resx | 117 ------------------
ntfysh_client/Properties/Settings.Designer.cs | 26 ----
ntfysh_client/Properties/Settings.settings | 7 --
ntfysh_client/ntfysh_client.csproj | 117 ++----------------
8 files changed, 18 insertions(+), 365 deletions(-)
delete mode 100644 ntfysh_client/Properties/AssemblyInfo.cs
delete mode 100644 ntfysh_client/Properties/Resources.Designer.cs
delete mode 100644 ntfysh_client/Properties/Resources.resx
delete mode 100644 ntfysh_client/Properties/Settings.Designer.cs
delete mode 100644 ntfysh_client/Properties/Settings.settings
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index 408c274..385e244 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -20,7 +20,7 @@ namespace ntfysh_client
private readonly HttpClient _httpClient = new();
private bool _isDisposed;
- public readonly Dictionary SubscribedTopicsByUnique = new();
+ public readonly Dictionary SubscribedTopicsByUnique = new();
public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
public event NotificationReceiveHandler? OnNotificationReceive;
@@ -37,8 +37,8 @@ namespace ntfysh_client
while (!cancellationToken.IsCancellationRequested)
{
- using HttpResponseMessage response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
- using Stream body = await response.Content.ReadAsStreamAsync();
+ using HttpResponseMessage response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ await using Stream body = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
try
{
@@ -48,7 +48,7 @@ namespace ntfysh_client
{
//Read as much as possible
byte[] buffer = new byte[8192];
- int readBytes = await body.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
+ int readBytes = await body.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
//Append it to our main buffer
mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, readBytes));
@@ -134,16 +134,16 @@ namespace ntfysh_client
#endif
//Topic isn't even subscribed, ignore
- if (!SubscribedTopicsByUnique.TryGetValue(topicUniqueString, out SubscribedTopic topic)) return;
+ if (!SubscribedTopicsByUnique.TryGetValue(topicUniqueString, out SubscribedTopic? topic)) return;
//Cancel and dispose the task runner
- topic.RunnerCanceller.Cancel();
+ topic?.RunnerCanceller.Cancel();
//Wait for the task runner to shut down
- while (!topic.Runner.IsCompleted) Thread.Sleep(100);
+ while (topic is not null && !topic.Runner.IsCompleted) Thread.Sleep(100);
//Dispose task
- topic.Runner.Dispose();
+ topic?.Runner.Dispose();
//Remove the old topic
SubscribedTopicsByUnique.Remove(topicUniqueString);
diff --git a/ntfysh_client/Program.cs b/ntfysh_client/Program.cs
index a980f95..6f47948 100644
--- a/ntfysh_client/Program.cs
+++ b/ntfysh_client/Program.cs
@@ -16,6 +16,7 @@ namespace ntfysh_client
[STAThread]
static void Main()
{
+ //Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(NotificationListener));
diff --git a/ntfysh_client/Properties/AssemblyInfo.cs b/ntfysh_client/Properties/AssemblyInfo.cs
deleted file mode 100644
index 07fb348..0000000
--- a/ntfysh_client/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("ntfy.sh")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ntfy.sh")]
-[assembly: AssemblyCopyright("Copyright © 2022")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("5a18d152-d620-43fe-b844-def30cfa50ef")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ntfysh_client/Properties/Resources.Designer.cs b/ntfysh_client/Properties/Resources.Designer.cs
deleted file mode 100644
index 8cf2c22..0000000
--- a/ntfysh_client/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace ntfysh_client.Properties {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ntfysh_client.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/ntfysh_client/Properties/Resources.resx b/ntfysh_client/Properties/Resources.resx
deleted file mode 100644
index af7dbeb..0000000
--- a/ntfysh_client/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/ntfysh_client/Properties/Settings.Designer.cs b/ntfysh_client/Properties/Settings.Designer.cs
deleted file mode 100644
index 67b3c60..0000000
--- a/ntfysh_client/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace ntfysh_client.Properties {
-
-
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
-
- private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default {
- get {
- return defaultInstance;
- }
- }
- }
-}
diff --git a/ntfysh_client/Properties/Settings.settings b/ntfysh_client/Properties/Settings.settings
deleted file mode 100644
index 3964565..0000000
--- a/ntfysh_client/Properties/Settings.settings
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/ntfysh_client/ntfysh_client.csproj b/ntfysh_client/ntfysh_client.csproj
index 3708738..dfe4310 100644
--- a/ntfysh_client/ntfysh_client.csproj
+++ b/ntfysh_client/ntfysh_client.csproj
@@ -1,119 +1,20 @@
-
-
-
+
+
- Debug
- AnyCPU
- {5A18D152-D620-43FE-B844-DEF30CFA50EF}
WinExe
- ntfysh_client
- ntfysh
- v4.7.2
- 512
- true
- true
+ net6.0-windows
+ true
latest
enable
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
NotificationHub.ico
+
-
- ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
- Form
-
-
- AboutBox.cs
-
-
- Form
-
-
- Form1.cs
-
-
-
-
-
-
-
- Form
-
-
- SubscribeDialog.cs
-
-
-
- AboutBox.cs
-
-
- Form1.cs
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
- Designer
-
-
- True
- Resources.resx
- True
-
-
- SubscribeDialog.cs
-
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
- True
- Settings.settings
- True
-
+
-
-
-
-
-
-
-
+
\ No newline at end of file
From 012eeaa65f217be22979b9298709cc4a0f594f28 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 23:25:07 +0000
Subject: [PATCH 06/14] Enable .NET Core system aware DPI mode Fix Exit button
in window menu Cleanup
---
ntfysh_client/Form1.Designer.cs | 52 ++++++++++++++++--------
ntfysh_client/Form1.cs | 6 +++
ntfysh_client/Form1.resx | 72 +++------------------------------
ntfysh_client/Program.cs | 2 +-
4 files changed, 48 insertions(+), 84 deletions(-)
diff --git a/ntfysh_client/Form1.Designer.cs b/ntfysh_client/Form1.Designer.cs
index 91caa7e..89f66c2 100644
--- a/ntfysh_client/Form1.Designer.cs
+++ b/ntfysh_client/Form1.Designer.cs
@@ -53,9 +53,10 @@ namespace ntfysh_client
// subscribeNewTopic
//
this.subscribeNewTopic.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.subscribeNewTopic.Location = new System.Drawing.Point(236, 50);
+ this.subscribeNewTopic.Location = new System.Drawing.Point(275, 58);
+ this.subscribeNewTopic.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.subscribeNewTopic.Name = "subscribeNewTopic";
- this.subscribeNewTopic.Size = new System.Drawing.Size(105, 23);
+ this.subscribeNewTopic.Size = new System.Drawing.Size(122, 27);
this.subscribeNewTopic.TabIndex = 2;
this.subscribeNewTopic.Text = "Add";
this.subscribeNewTopic.UseVisualStyleBackColor = true;
@@ -65,9 +66,10 @@ namespace ntfysh_client
//
this.removeSelectedTopics.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.removeSelectedTopics.Enabled = false;
- this.removeSelectedTopics.Location = new System.Drawing.Point(236, 79);
+ this.removeSelectedTopics.Location = new System.Drawing.Point(275, 91);
+ this.removeSelectedTopics.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.removeSelectedTopics.Name = "removeSelectedTopics";
- this.removeSelectedTopics.Size = new System.Drawing.Size(105, 23);
+ this.removeSelectedTopics.Size = new System.Drawing.Size(122, 27);
this.removeSelectedTopics.TabIndex = 0;
this.removeSelectedTopics.Text = "Remove selected";
this.removeSelectedTopics.UseVisualStyleBackColor = true;
@@ -75,12 +77,15 @@ namespace ntfysh_client
//
// notificationTopics
//
- this.notificationTopics.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+ this.notificationTopics.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
this.notificationTopics.FormattingEnabled = true;
- this.notificationTopics.Location = new System.Drawing.Point(12, 50);
+ this.notificationTopics.ItemHeight = 15;
+ this.notificationTopics.Location = new System.Drawing.Point(14, 58);
+ this.notificationTopics.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.notificationTopics.Name = "notificationTopics";
this.notificationTopics.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended;
- this.notificationTopics.Size = new System.Drawing.Size(145, 173);
+ this.notificationTopics.Size = new System.Drawing.Size(168, 199);
this.notificationTopics.TabIndex = 3;
this.notificationTopics.Click += new System.EventHandler(this.notificationTopics_Click);
this.notificationTopics.SelectedValueChanged += new System.EventHandler(this.notificationTopics_SelectedValueChanged);
@@ -95,7 +100,9 @@ namespace ntfysh_client
//
// trayContextMenu
//
- this.trayContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.showControlWindowToolStripMenuItem, this.exitToolStripMenuItem });
+ this.trayContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.showControlWindowToolStripMenuItem,
+ this.exitToolStripMenuItem});
this.trayContextMenu.Name = "trayContextMenu";
this.trayContextMenu.Size = new System.Drawing.Size(190, 48);
//
@@ -118,16 +125,20 @@ namespace ntfysh_client
// menuStrip1
//
this.menuStrip1.BackColor = System.Drawing.Color.White;
- this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileToolStripMenuItem, this.helpToolStripMenuItem });
+ this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.fileToolStripMenuItem,
+ this.helpToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
- this.menuStrip1.Size = new System.Drawing.Size(353, 24);
+ this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
+ this.menuStrip1.Size = new System.Drawing.Size(412, 24);
this.menuStrip1.TabIndex = 4;
this.menuStrip1.Text = "menuStrip1";
//
// fileToolStripMenuItem
//
- this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.exitToolStripMenuItem1 });
+ this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.exitToolStripMenuItem1});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
this.fileToolStripMenuItem.Text = "File";
@@ -136,12 +147,16 @@ namespace ntfysh_client
//
this.exitToolStripMenuItem1.Image = ((System.Drawing.Image)(resources.GetObject("exitToolStripMenuItem1.Image")));
this.exitToolStripMenuItem1.Name = "exitToolStripMenuItem1";
- this.exitToolStripMenuItem1.Size = new System.Drawing.Size(93, 22);
+ this.exitToolStripMenuItem1.Size = new System.Drawing.Size(180, 22);
this.exitToolStripMenuItem1.Text = "Exit";
+ this.exitToolStripMenuItem1.Click += new System.EventHandler(this.exitToolStripMenuItem1_Click);
//
// helpToolStripMenuItem
//
- this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.ntfyshWebsiteToolStripMenuItem, this.toolStripMenuItem1, this.aboutToolStripMenuItem });
+ this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.ntfyshWebsiteToolStripMenuItem,
+ this.toolStripMenuItem1,
+ this.aboutToolStripMenuItem});
this.helpToolStripMenuItem.Name = "helpToolStripMenuItem";
this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20);
this.helpToolStripMenuItem.Text = "Help";
@@ -170,18 +185,19 @@ namespace ntfysh_client
// label1
//
this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(12, 34);
+ this.label1.Location = new System.Drawing.Point(14, 39);
+ this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(145, 13);
+ this.label1.Size = new System.Drawing.Size(164, 15);
this.label1.TabIndex = 1;
this.label1.Text = "Subscribed notification topics";
//
// Form1
//
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
- this.ClientSize = new System.Drawing.Size(353, 236);
+ this.ClientSize = new System.Drawing.Size(412, 272);
this.Controls.Add(this.menuStrip1);
this.Controls.Add(this.notificationTopics);
this.Controls.Add(this.removeSelectedTopics);
@@ -191,6 +207,7 @@ namespace ntfysh_client
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.KeyPreview = true;
this.MainMenuStrip = this.menuStrip1;
+ this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "Form1";
@@ -204,6 +221,7 @@ namespace ntfysh_client
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
+
}
#endregion
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index 5666191..2d51bf1 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -212,5 +212,11 @@ namespace ntfysh_client
using AboutBox d = new AboutBox();
d.ShowDialog();
}
+
+ private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
+ {
+ _trueExit = true;
+ Close();
+ }
}
}
diff --git a/ntfysh_client/Form1.resx b/ntfysh_client/Form1.resx
index 7adc2ca..f988851 100644
--- a/ntfysh_client/Form1.resx
+++ b/ntfysh_client/Form1.resx
@@ -1,64 +1,4 @@
-
-
-
+
@@ -127,7 +67,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAiklE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAiklE
QVQ4T8WPQQqAIBBFvUOrXIhu3dcZOkqtu5R1ia4SdRDrxyyGGKmBIOGBDL6nmk9WCGE9yUpW0q9Ads7V
GuCQ/kHAe79joAEO6dcLFmttpQEO6c+Bvpm2oZ0zwB4zVQBiF8cIsMdMDPCb+G2vA/wgP/z6C6WAhBgo
fUFCDGi4BxIGShLpvy5jDoPes/0oNG3VAAAAAElFTkSuQmCC
@@ -136,7 +76,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAgUlE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAgUlE
QVQ4T2OgGjjOLq4AZeIFWNWBBI9yib06zC3uABXCCkDyIHVYDTnMI2pzhEvs5VFucSeoEAo4wiNii08e
DHAZQshwFICumCTNMADyK1gTl2gJiCYUNlgBSDPQ1v8gGipEPKDIBRSFAa6oIsoQQvGM1xCqpESsglgA
seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
@@ -327,7 +267,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAgUlE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAgUlE
QVQ4T2OgGjjOLq4AZeIFWNWBBI9yib06zC3uABXCCkDyIHVYDTnMI2pzhEvs5VFucSeoEAo4wiNii08e
DHAZQshwFICumCTNMADyK1gTl2gJiCYUNlgBSDPQ1v8gGipEPKDIBRSFAa6oIsoQQvGM1xCqpESsglgA
seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
@@ -336,7 +276,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABDElE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAABDElE
QVQ4T91RO3bCQAzUy3uhChdJnTVlCj5FcMsZsF1jmy4XycPrzl6fBLgEXMFp4srRiF0wcaBP5j0VI2m0
4zH9EwSq8gPP7ELPNFxtpxrbn9vVPkSsykOkqvHi2QxsWwAevhQTzG8eCb1yD7Glv2I5MlM+sLX0GqEq
6+jVPFl6QZz7lOgdJVnzuMq/sEepbsGln2YnR3AAm0IcIE71geJsTO9mcOWAOa3zicxxxGWApXMGabaH
@@ -347,7 +287,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAwUlE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAwUlE
QVQ4T2OgClBUVHwAxP9JxA+g2sEG/JeTk5MkBYP0QLVTwQAFBYXHIAFSMEgPVDvYBfulpKSEScEgPVDt
dDIA6OTNQHX/YXysBjAxMcUxMjKuBrGB9DIgPxqmARhwOUBD3sD4WA0QERGRBHLvCwoKygLpm6KiohIw
DVBNz5HY2L0AtHkOEE8F4okgvoyMjDSSJsIGsLOzuwKFfnBycppJS0vLAJ39BUQTbQA6RnYBMkY3YAVI
diff --git a/ntfysh_client/Program.cs b/ntfysh_client/Program.cs
index 6f47948..a8feec7 100644
--- a/ntfysh_client/Program.cs
+++ b/ntfysh_client/Program.cs
@@ -16,7 +16,7 @@ namespace ntfysh_client
[STAThread]
static void Main()
{
- //Application.SetHighDpiMode(HighDpiMode.SystemAware);
+ Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(NotificationListener));
From bba9051b4bab8d3bc6ad91644ed91fccc54e5856 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 23:27:30 +0000
Subject: [PATCH 07/14] Fix browser launch
---
ntfysh_client/Form1.cs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index 2d51bf1..664180c 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
+using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -204,7 +205,10 @@ namespace ntfysh_client
private void ntfyshWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
{
- System.Diagnostics.Process.Start("https://ntfy.sh/");
+ Process.Start(new ProcessStartInfo("https://ntfy.sh/")
+ {
+ UseShellExecute = true
+ });
}
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
From 76031ee868d8af29930a2bb953fd5e29f58beff0 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Tue, 6 Dec 2022 23:31:11 +0000
Subject: [PATCH 08/14] Squash that async bug for good
---
ntfysh_client/Form1.cs | 4 ++--
ntfysh_client/NotificationListener.cs | 28 ++++++++++++++++++---------
2 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/Form1.cs
index 664180c..05f07fc 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/Form1.cs
@@ -47,13 +47,13 @@ namespace ntfysh_client
SaveTopicsToFile();
}
- private void removeSelectedTopics_Click(object sender, EventArgs e)
+ private async void removeSelectedTopics_Click(object sender, EventArgs e)
{
while (notificationTopics.SelectedIndex > -1)
{
string topicUniqueString = (string)notificationTopics.Items[notificationTopics.SelectedIndex];
- _notificationListener.UnsubscribeFromTopic(topicUniqueString);
+ await _notificationListener.UnsubscribeFromTopicAsync(topicUniqueString);
notificationTopics.Items.Remove(topicUniqueString);
}
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index 385e244..5a08f70 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -37,8 +37,8 @@ namespace ntfysh_client
while (!cancellationToken.IsCancellationRequested)
{
- using HttpResponseMessage response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- await using Stream body = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ using HttpResponseMessage response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ await using Stream body = await response.Content.ReadAsStreamAsync(cancellationToken);
try
{
@@ -48,7 +48,7 @@ namespace ntfysh_client
{
//Read as much as possible
byte[] buffer = new byte[8192];
- int readBytes = await body.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ int readBytes = await body.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
//Append it to our main buffer
mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, readBytes));
@@ -125,25 +125,35 @@ namespace ntfysh_client
SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
}
- public void UnsubscribeFromTopic(string topicUniqueString)
+ public async Task UnsubscribeFromTopicAsync(string topicUniqueString)
{
if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
#if DEBUG
Debug.WriteLine($"Removing topic {topicUniqueString}");
#endif
+
+ // ReSharper disable once InlineOutVariableDeclaration - Needed to avoid nullable warning
+ SubscribedTopic topic;
//Topic isn't even subscribed, ignore
- if (!SubscribedTopicsByUnique.TryGetValue(topicUniqueString, out SubscribedTopic? topic)) return;
+ if (!SubscribedTopicsByUnique.TryGetValue(topicUniqueString, out topic!)) return;
//Cancel and dispose the task runner
- topic?.RunnerCanceller.Cancel();
+ topic.RunnerCanceller.Cancel();
//Wait for the task runner to shut down
- while (topic is not null && !topic.Runner.IsCompleted) Thread.Sleep(100);
-
+ try
+ {
+ await topic.Runner;
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+
//Dispose task
- topic?.Runner.Dispose();
+ topic.Runner.Dispose();
//Remove the old topic
SubscribedTopicsByUnique.Remove(topicUniqueString);
From 294747def51b937077398944729a28595d7cf586 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Wed, 7 Dec 2022 19:40:37 +0000
Subject: [PATCH 09/14] Adjust layout of main form to make long topic names
visible
---
ntfysh_client/Form1.Designer.cs | 22 +++++++++++-----------
ntfysh_client/Form1.resx | 10 +++++-----
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/ntfysh_client/Form1.Designer.cs b/ntfysh_client/Form1.Designer.cs
index 89f66c2..6fbabac 100644
--- a/ntfysh_client/Form1.Designer.cs
+++ b/ntfysh_client/Form1.Designer.cs
@@ -53,10 +53,10 @@ namespace ntfysh_client
// subscribeNewTopic
//
this.subscribeNewTopic.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.subscribeNewTopic.Location = new System.Drawing.Point(275, 58);
+ this.subscribeNewTopic.Location = new System.Drawing.Point(211, 251);
this.subscribeNewTopic.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.subscribeNewTopic.Name = "subscribeNewTopic";
- this.subscribeNewTopic.Size = new System.Drawing.Size(122, 27);
+ this.subscribeNewTopic.Size = new System.Drawing.Size(188, 27);
this.subscribeNewTopic.TabIndex = 2;
this.subscribeNewTopic.Text = "Add";
this.subscribeNewTopic.UseVisualStyleBackColor = true;
@@ -66,10 +66,10 @@ namespace ntfysh_client
//
this.removeSelectedTopics.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.removeSelectedTopics.Enabled = false;
- this.removeSelectedTopics.Location = new System.Drawing.Point(275, 91);
+ this.removeSelectedTopics.Location = new System.Drawing.Point(13, 251);
this.removeSelectedTopics.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.removeSelectedTopics.Name = "removeSelectedTopics";
- this.removeSelectedTopics.Size = new System.Drawing.Size(122, 27);
+ this.removeSelectedTopics.Size = new System.Drawing.Size(188, 27);
this.removeSelectedTopics.TabIndex = 0;
this.removeSelectedTopics.Text = "Remove selected";
this.removeSelectedTopics.UseVisualStyleBackColor = true;
@@ -81,11 +81,11 @@ namespace ntfysh_client
| System.Windows.Forms.AnchorStyles.Right)));
this.notificationTopics.FormattingEnabled = true;
this.notificationTopics.ItemHeight = 15;
- this.notificationTopics.Location = new System.Drawing.Point(14, 58);
+ this.notificationTopics.Location = new System.Drawing.Point(13, 46);
this.notificationTopics.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.notificationTopics.Name = "notificationTopics";
this.notificationTopics.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended;
- this.notificationTopics.Size = new System.Drawing.Size(168, 199);
+ this.notificationTopics.Size = new System.Drawing.Size(386, 199);
this.notificationTopics.TabIndex = 3;
this.notificationTopics.Click += new System.EventHandler(this.notificationTopics_Click);
this.notificationTopics.SelectedValueChanged += new System.EventHandler(this.notificationTopics_SelectedValueChanged);
@@ -147,7 +147,7 @@ namespace ntfysh_client
//
this.exitToolStripMenuItem1.Image = ((System.Drawing.Image)(resources.GetObject("exitToolStripMenuItem1.Image")));
this.exitToolStripMenuItem1.Name = "exitToolStripMenuItem1";
- this.exitToolStripMenuItem1.Size = new System.Drawing.Size(180, 22);
+ this.exitToolStripMenuItem1.Size = new System.Drawing.Size(93, 22);
this.exitToolStripMenuItem1.Text = "Exit";
this.exitToolStripMenuItem1.Click += new System.EventHandler(this.exitToolStripMenuItem1_Click);
//
@@ -185,19 +185,19 @@ namespace ntfysh_client
// label1
//
this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(14, 39);
+ this.label1.Location = new System.Drawing.Point(13, 27);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(164, 15);
+ this.label1.Size = new System.Drawing.Size(170, 15);
this.label1.TabIndex = 1;
- this.label1.Text = "Subscribed notification topics";
+ this.label1.Text = "Subscribed Notification Topics:";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
- this.ClientSize = new System.Drawing.Size(412, 272);
+ this.ClientSize = new System.Drawing.Size(412, 288);
this.Controls.Add(this.menuStrip1);
this.Controls.Add(this.notificationTopics);
this.Controls.Add(this.removeSelectedTopics);
diff --git a/ntfysh_client/Form1.resx b/ntfysh_client/Form1.resx
index f988851..dbf516e 100644
--- a/ntfysh_client/Form1.resx
+++ b/ntfysh_client/Form1.resx
@@ -67,7 +67,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAiklE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA6+AAAOvgHqQrHAAAAAiklE
QVQ4T8WPQQqAIBBFvUOrXIhu3dcZOkqtu5R1ia4SdRDrxyyGGKmBIOGBDL6nmk9WCGE9yUpW0q9Ads7V
GuCQ/kHAe79joAEO6dcLFmttpQEO6c+Bvpm2oZ0zwB4zVQBiF8cIsMdMDPCb+G2vA/wgP/z6C6WAhBgo
fUFCDGi4BxIGShLpvy5jDoPes/0oNG3VAAAAAElFTkSuQmCC
@@ -76,7 +76,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAgUlE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA6+AAAOvgHqQrHAAAAAgUlE
QVQ4T2OgGjjOLq4AZeIFWNWBBI9yib06zC3uABXCCkDyIHVYDTnMI2pzhEvs5VFucSeoEAo4wiNii08e
DHAZQshwFICumCTNMADyK1gTl2gJiCYUNlgBSDPQ1v8gGipEPKDIBRSFAa6oIsoQQvGM1xCqpESsglgA
seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
@@ -267,7 +267,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAgUlE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA6+AAAOvgHqQrHAAAAAgUlE
QVQ4T2OgGjjOLq4AZeIFWNWBBI9yib06zC3uABXCCkDyIHVYDTnMI2pzhEvs5VFucSeoEAo4wiNii08e
DHAZQshwFICumCTNMADyK1gTl2gJiCYUNlgBSDPQ1v8gGipEPKDIBRSFAa6oIsoQQvGM1xCqpESsglgA
seroBRgYAOoOWBJbfVcRAAAAAElFTkSuQmCC
@@ -276,7 +276,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAABDElE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA6+AAAOvgHqQrHAAAABDElE
QVQ4T91RO3bCQAzUy3uhChdJnTVlCj5FcMsZsF1jmy4XycPrzl6fBLgEXMFp4srRiF0wcaBP5j0VI2m0
4zH9EwSq8gPP7ELPNFxtpxrbn9vVPkSsykOkqvHi2QxsWwAevhQTzG8eCb1yD7Glv2I5MlM+sLX0GqEq
6+jVPFl6QZz7lOgdJVnzuMq/sEepbsGln2YnR3AAm0IcIE71geJsTO9mcOWAOa3zicxxxGWApXMGabaH
@@ -287,7 +287,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
- JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7AAAAOwAFq1okJAAAAwUlE
+ JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA6+AAAOvgHqQrHAAAAAwUlE
QVQ4T2OgClBUVHwAxP9JxA+g2sEG/JeTk5MkBYP0QLVTwQAFBYXHIAFSMEgPVDvYBfulpKSEScEgPVDt
dDIA6OTNQHX/YXysBjAxMcUxMjKuBrGB9DIgPxqmARhwOUBD3sD4WA0QERGRBHLvCwoKygLpm6KiohIw
DVBNz5HY2L0AtHkOEE8F4okgvoyMjDSSJsIGsLOzuwKFfnBycppJS0vLAJ39BUQTbQA6RnYBMkY3YAVI
From fe4ae354b2626d05d0aad777350cc2778bbe3d7a Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Wed, 7 Dec 2022 19:54:48 +0000
Subject: [PATCH 10/14] Rename main form
---
.../{Form1.Designer.cs => MainForm.Designer.cs} | 14 +++++++-------
ntfysh_client/{Form1.cs => MainForm.cs} | 14 +++++---------
ntfysh_client/{Form1.resx => MainForm.resx} | 0
ntfysh_client/Program.cs | 2 +-
ntfysh_client/ntfysh_client.csproj | 5 +----
5 files changed, 14 insertions(+), 21 deletions(-)
rename ntfysh_client/{Form1.Designer.cs => MainForm.Designer.cs} (98%)
rename ntfysh_client/{Form1.cs => MainForm.cs} (94%)
rename ntfysh_client/{Form1.resx => MainForm.resx} (100%)
diff --git a/ntfysh_client/Form1.Designer.cs b/ntfysh_client/MainForm.Designer.cs
similarity index 98%
rename from ntfysh_client/Form1.Designer.cs
rename to ntfysh_client/MainForm.Designer.cs
index 6fbabac..83e8285 100644
--- a/ntfysh_client/Form1.Designer.cs
+++ b/ntfysh_client/MainForm.Designer.cs
@@ -1,7 +1,7 @@
namespace ntfysh_client
{
- partial class Form1
+ partial class MainForm
{
///
/// Required designer variable.
@@ -30,7 +30,7 @@ namespace ntfysh_client
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.subscribeNewTopic = new System.Windows.Forms.Button();
this.removeSelectedTopics = new System.Windows.Forms.Button();
this.notificationTopics = new System.Windows.Forms.ListBox();
@@ -192,7 +192,7 @@ namespace ntfysh_client
this.label1.TabIndex = 1;
this.label1.Text = "Subscribed Notification Topics:";
//
- // Form1
+ // MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
@@ -210,12 +210,12 @@ namespace ntfysh_client
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.MaximizeBox = false;
this.MinimizeBox = false;
- this.Name = "Form1";
+ this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ntfy.sh";
- this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
- this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Form1_FormClosed);
- this.Load += new System.EventHandler(this.Form1_Load);
+ this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
+ this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed);
+ this.Load += new System.EventHandler(this.MainForm_Load);
this.trayContextMenu.ResumeLayout(false);
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
diff --git a/ntfysh_client/Form1.cs b/ntfysh_client/MainForm.cs
similarity index 94%
rename from ntfysh_client/Form1.cs
rename to ntfysh_client/MainForm.cs
index 05f07fc..296c664 100644
--- a/ntfysh_client/Form1.cs
+++ b/ntfysh_client/MainForm.cs
@@ -1,25 +1,21 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json;
namespace ntfysh_client
{
- public partial class Form1 : Form
+ public partial class MainForm : Form
{
private readonly NotificationListener _notificationListener;
private bool _trueExit;
- public Form1(NotificationListener notificationListener)
+ public MainForm(NotificationListener notificationListener)
{
_notificationListener = notificationListener;
_notificationListener.OnNotificationReceive += OnNotificationReceive;
@@ -27,7 +23,7 @@ namespace ntfysh_client
InitializeComponent();
}
- private void Form1_Load(object sender, EventArgs e) => LoadTopics();
+ private void MainForm_Load(object sender, EventArgs e) => LoadTopics();
private void subscribeNewTopic_Click(object sender, EventArgs e)
{
@@ -181,12 +177,12 @@ namespace ntfysh_client
notifyIcon.ShowBalloonTip(3000, e.Title, e.Message, ToolTipIcon.Info);
}
- private void Form1_FormClosed(object sender, FormClosedEventArgs e)
+ private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
notifyIcon.Dispose();
}
- private void Form1_FormClosing(object sender, FormClosingEventArgs e)
+ private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// Let it close
if (_trueExit) return;
diff --git a/ntfysh_client/Form1.resx b/ntfysh_client/MainForm.resx
similarity index 100%
rename from ntfysh_client/Form1.resx
rename to ntfysh_client/MainForm.resx
diff --git a/ntfysh_client/Program.cs b/ntfysh_client/Program.cs
index a8feec7..5841c89 100644
--- a/ntfysh_client/Program.cs
+++ b/ntfysh_client/Program.cs
@@ -19,7 +19,7 @@ namespace ntfysh_client
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new Form1(NotificationListener));
+ Application.Run(new MainForm(NotificationListener));
}
}
}
diff --git a/ntfysh_client/ntfysh_client.csproj b/ntfysh_client/ntfysh_client.csproj
index dfe4310..5b9a3a3 100644
--- a/ntfysh_client/ntfysh_client.csproj
+++ b/ntfysh_client/ntfysh_client.csproj
@@ -7,14 +7,11 @@
latest
enable
NotificationHub.ico
+ ntfysh_client.Program
-
-
-
-
\ No newline at end of file
From 1e1187e71ff99d3e70b33b3501c6b78ab2b6c24e Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Wed, 7 Dec 2022 21:36:03 +0000
Subject: [PATCH 11/14] Basic Websocket support working, without authentication
(auth broken)
---
ntfysh_client/MainForm.cs | 30 +++++-
ntfysh_client/NotificationListener.cs | 72 ++++++++++++-
ntfysh_client/SubscribeDialog.Designer.cs | 94 ++++++++++++-----
ntfysh_client/SubscribeDialog.cs | 122 +++++++++++++++++++++-
ntfysh_client/SubscribeDialog.resx | 62 +----------
5 files changed, 286 insertions(+), 94 deletions(-)
diff --git a/ntfysh_client/MainForm.cs b/ntfysh_client/MainForm.cs
index 296c664..acf5b99 100644
--- a/ntfysh_client/MainForm.cs
+++ b/ntfysh_client/MainForm.cs
@@ -34,8 +34,15 @@ namespace ntfysh_client
if (result != DialogResult.OK) return;
//Subscribe
- _notificationListener.SubscribeToTopicUsingLongHttpJson(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
-
+ if (dialog.UseWebsockets)
+ {
+ _notificationListener.SubscribeToTopicUsingWebsocket(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
+ }
+ else
+ {
+ _notificationListener.SubscribeToTopicUsingLongHttpJson(dialog.Unique, dialog.TopicId, dialog.ServerUrl, dialog.Username, dialog.Password);
+ }
+
//Add to the user visible list
notificationTopics.Items.Add(dialog.Unique);
@@ -167,7 +174,24 @@ namespace ntfysh_client
//Load them in
foreach (SubscribedTopic topic in topics)
{
- _notificationListener.SubscribeToTopicUsingLongHttpJson($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
+ string[] parts = topic.ServerUrl.Split("://", 2);
+
+ switch (parts[0].ToLower())
+ {
+ case "ws":
+ case "wss":
+ _notificationListener.SubscribeToTopicUsingWebsocket($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
+ break;
+
+ case "http":
+ case "https":
+ _notificationListener.SubscribeToTopicUsingLongHttpJson($"{topic.TopicId}@{topic.ServerUrl}", topic.TopicId, topic.ServerUrl, topic.Username, topic.Password);
+ break;
+
+ default:
+ continue;
+ }
+
notificationTopics.Items.Add($"{topic.TopicId}@{topic.ServerUrl}");
}
}
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index 5a08f70..9995599 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -31,7 +31,7 @@ namespace ntfysh_client
ServicePointManager.DefaultConnectionLimit = 100;
}
- private async Task ListenToTopicAsync(HttpRequestMessage message, CancellationToken cancellationToken)
+ private async Task ListenToTopicWithHttpLongJsonAsync(HttpRequestMessage message, CancellationToken cancellationToken)
{
if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
@@ -83,6 +83,59 @@ namespace ntfysh_client
}
}
}
+
+ private async Task ListenToTopicWithWebsocketAsync(Uri uri, NetworkCredential credentials, CancellationToken cancellationToken)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ using ClientWebSocket socket = new();
+ socket.Options.Credentials = credentials;
+
+ try
+ {
+ StringBuilder mainBuffer = new();
+
+ await socket.ConnectAsync(uri, cancellationToken);
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ //Read as much as possible
+ byte[] buffer = new byte[8192];
+ WebSocketReceiveResult? result = await socket.ReceiveAsync(new ArraySegment(buffer), cancellationToken);
+
+ //Append it to our main buffer
+ mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
+
+ List 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)
{
@@ -120,10 +173,25 @@ namespace ntfysh_client
}
CancellationTokenSource listenCanceller = new();
- Task listenTask = ListenToTopicAsync(message, listenCanceller.Token);
+ Task listenTask = ListenToTopicWithHttpLongJsonAsync(message, listenCanceller.Token);
SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
}
+
+ public void SubscribeToTopicUsingWebsocket(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(password)) password = null;
+
+ Uri targetUri = new($"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/ws");
+ CancellationTokenSource listenCanceller = new();
+ Task listenTask = ListenToTopicWithWebsocketAsync(targetUri, new NetworkCredential(username, password), listenCanceller.Token);
+ SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
+ }
public async Task UnsubscribeFromTopicAsync(string topicUniqueString)
{
diff --git a/ntfysh_client/SubscribeDialog.Designer.cs b/ntfysh_client/SubscribeDialog.Designer.cs
index 30d5a60..4977b26 100644
--- a/ntfysh_client/SubscribeDialog.Designer.cs
+++ b/ntfysh_client/SubscribeDialog.Designer.cs
@@ -40,6 +40,8 @@ namespace ntfysh_client
this.label3 = new System.Windows.Forms.Label();
this.password = new System.Windows.Forms.TextBox();
this.label4 = new System.Windows.Forms.Label();
+ this.label5 = new System.Windows.Forms.Label();
+ this.connectionType = new System.Windows.Forms.ComboBox();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
@@ -49,18 +51,19 @@ namespace ntfysh_client
this.panel1.Controls.Add(this.button2);
this.panel1.Controls.Add(this.button1);
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.panel1.Location = new System.Drawing.Point(0, 175);
+ this.panel1.Location = new System.Drawing.Point(0, 244);
+ this.panel1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.panel1.Name = "panel1";
- this.panel1.Size = new System.Drawing.Size(297, 44);
+ this.panel1.Size = new System.Drawing.Size(346, 51);
this.panel1.TabIndex = 0;
//
// button2
//
this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.button2.Location = new System.Drawing.Point(131, 11);
- this.button2.Margin = new System.Windows.Forms.Padding(10, 10, 3, 10);
+ this.button2.Location = new System.Drawing.Point(153, 13);
+ this.button2.Margin = new System.Windows.Forms.Padding(12, 12, 4, 12);
this.button2.Name = "button2";
- this.button2.Size = new System.Drawing.Size(75, 23);
+ this.button2.Size = new System.Drawing.Size(88, 27);
this.button2.TabIndex = 1;
this.button2.Text = "Cancel";
this.button2.UseVisualStyleBackColor = true;
@@ -69,10 +72,10 @@ namespace ntfysh_client
// button1
//
this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.button1.Location = new System.Drawing.Point(212, 11);
- this.button1.Margin = new System.Windows.Forms.Padding(3, 10, 10, 10);
+ this.button1.Location = new System.Drawing.Point(247, 13);
+ this.button1.Margin = new System.Windows.Forms.Padding(4, 12, 12, 12);
this.button1.Name = "button1";
- this.button1.Size = new System.Drawing.Size(75, 23);
+ this.button1.Size = new System.Drawing.Size(88, 27);
this.button1.TabIndex = 2;
this.button1.Text = "Subscribe";
this.button1.UseVisualStyleBackColor = true;
@@ -81,9 +84,10 @@ namespace ntfysh_client
// label1
//
this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(12, 9);
+ this.label1.Location = new System.Drawing.Point(14, 10);
+ this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(51, 13);
+ this.label1.Size = new System.Drawing.Size(52, 15);
this.label1.TabIndex = 1;
this.label1.Text = "Topic ID:";
//
@@ -91,9 +95,10 @@ namespace ntfysh_client
//
this.topicId.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
- this.topicId.Location = new System.Drawing.Point(12, 25);
+ this.topicId.Location = new System.Drawing.Point(14, 29);
+ this.topicId.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.topicId.Name = "topicId";
- this.topicId.Size = new System.Drawing.Size(273, 20);
+ this.topicId.Size = new System.Drawing.Size(318, 23);
this.topicId.TabIndex = 0;
this.topicId.KeyDown += new System.Windows.Forms.KeyEventHandler(this.topicId_KeyDown);
//
@@ -101,19 +106,21 @@ namespace ntfysh_client
//
this.serverUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
- this.serverUrl.Location = new System.Drawing.Point(12, 64);
+ this.serverUrl.Location = new System.Drawing.Point(14, 74);
+ this.serverUrl.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.serverUrl.Name = "serverUrl";
- this.serverUrl.Size = new System.Drawing.Size(273, 20);
+ this.serverUrl.Size = new System.Drawing.Size(318, 23);
this.serverUrl.TabIndex = 2;
- this.serverUrl.Text = "https://ntfy.sh";
+ this.serverUrl.Text = "wss://ntfy.sh";
this.serverUrl.KeyDown += new System.Windows.Forms.KeyEventHandler(this.serverUrl_KeyDown);
//
// label2
//
this.label2.AutoSize = true;
- this.label2.Location = new System.Drawing.Point(10, 48);
+ this.label2.Location = new System.Drawing.Point(12, 55);
+ this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Name = "label2";
- this.label2.Size = new System.Drawing.Size(66, 13);
+ this.label2.Size = new System.Drawing.Size(66, 15);
this.label2.TabIndex = 3;
this.label2.Text = "Server URL:";
//
@@ -121,18 +128,20 @@ namespace ntfysh_client
//
this.username.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
- this.username.Location = new System.Drawing.Point(12, 103);
+ this.username.Location = new System.Drawing.Point(14, 119);
+ this.username.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.username.Name = "username";
- this.username.Size = new System.Drawing.Size(273, 20);
+ this.username.Size = new System.Drawing.Size(318, 23);
this.username.TabIndex = 4;
this.username.KeyDown += new System.Windows.Forms.KeyEventHandler(this.username_KeyDown);
//
// label3
//
this.label3.AutoSize = true;
- this.label3.Location = new System.Drawing.Point(10, 87);
+ this.label3.Location = new System.Drawing.Point(12, 100);
+ this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label3.Name = "label3";
- this.label3.Size = new System.Drawing.Size(58, 13);
+ this.label3.Size = new System.Drawing.Size(63, 15);
this.label3.TabIndex = 5;
this.label3.Text = "Username:";
//
@@ -140,9 +149,10 @@ namespace ntfysh_client
//
this.password.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
- this.password.Location = new System.Drawing.Point(12, 142);
+ this.password.Location = new System.Drawing.Point(14, 164);
+ this.password.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.password.Name = "password";
- this.password.Size = new System.Drawing.Size(273, 20);
+ this.password.Size = new System.Drawing.Size(318, 23);
this.password.TabIndex = 6;
this.password.UseSystemPasswordChar = true;
this.password.KeyDown += new System.Windows.Forms.KeyEventHandler(this.password_KeyDown);
@@ -150,18 +160,44 @@ namespace ntfysh_client
// label4
//
this.label4.AutoSize = true;
- this.label4.Location = new System.Drawing.Point(10, 126);
+ this.label4.Location = new System.Drawing.Point(12, 145);
+ this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label4.Name = "label4";
- this.label4.Size = new System.Drawing.Size(56, 13);
+ this.label4.Size = new System.Drawing.Size(60, 15);
this.label4.TabIndex = 7;
this.label4.Text = "Password:";
//
+ // label5
+ //
+ this.label5.AutoSize = true;
+ this.label5.Location = new System.Drawing.Point(12, 190);
+ this.label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ this.label5.Name = "label5";
+ this.label5.Size = new System.Drawing.Size(99, 15);
+ this.label5.TabIndex = 9;
+ this.label5.Text = "Connection Type:";
+ //
+ // connectionType
+ //
+ this.connectionType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.connectionType.FormattingEnabled = true;
+ this.connectionType.Items.AddRange(new object[] {
+ "Websockets (Recommended)",
+ "Long HTTP JSON (Robust)"});
+ this.connectionType.Location = new System.Drawing.Point(14, 208);
+ this.connectionType.Name = "connectionType";
+ this.connectionType.Size = new System.Drawing.Size(318, 23);
+ this.connectionType.TabIndex = 10;
+ this.connectionType.TextChanged += new System.EventHandler(this.connectionType_TextChanged);
+ //
// SubscribeDialog
//
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
- this.ClientSize = new System.Drawing.Size(297, 219);
+ this.ClientSize = new System.Drawing.Size(346, 295);
+ this.Controls.Add(this.connectionType);
+ this.Controls.Add(this.label5);
this.Controls.Add(this.password);
this.Controls.Add(this.label4);
this.Controls.Add(this.username);
@@ -172,6 +208,7 @@ namespace ntfysh_client
this.Controls.Add(this.label1);
this.Controls.Add(this.panel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "SubscribeDialog";
@@ -179,6 +216,7 @@ namespace ntfysh_client
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Subscribe to new topic";
+ this.Load += new System.EventHandler(this.SubscribeDialog_Load);
this.panel1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
@@ -199,5 +237,7 @@ namespace ntfysh_client
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox password;
private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.Label label5;
+ private System.Windows.Forms.ComboBox connectionType;
}
}
\ No newline at end of file
diff --git a/ntfysh_client/SubscribeDialog.cs b/ntfysh_client/SubscribeDialog.cs
index e5233f6..bc890b7 100644
--- a/ntfysh_client/SubscribeDialog.cs
+++ b/ntfysh_client/SubscribeDialog.cs
@@ -17,12 +17,107 @@ namespace ntfysh_client
public string Unique => $"{topicId.Text}@{serverUrl.Text}";
+ public bool UseWebsockets
+ {
+ get
+ {
+ switch (connectionType.Text)
+ {
+ case "Websockets (Recommended)":
+ return true;
+
+ case "Long HTTP JSON (Robust)":
+ return false;
+
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+ }
+
public SubscribeDialog(ListBox notificationTopics)
{
_notificationTopics = notificationTopics;
InitializeComponent();
}
+ private void SubscribeDialog_Load(object sender, EventArgs e)
+ {
+ connectionType.SelectedIndex = 0;
+ }
+
+ private bool ReparseAddress()
+ {
+ //Separate schema and address
+ string[] parts = serverUrl.Text.Split("://", 2);
+
+ //Validate the basic formatting is correct
+ if (parts.Length != 2) return false;
+
+ //Take the schema aside for parsing
+ string schema = parts[0].ToLower();
+
+ //Ensure the schema is actually valid
+ switch (schema)
+ {
+ case "http":
+ case "https":
+ case "ws":
+ case "wss":
+ break;
+
+ default:
+ return false;
+ }
+
+ //Correct the schema based on connection type if required
+ if (UseWebsockets)
+ {
+ switch (schema)
+ {
+ case "http":
+ schema = "ws";
+ break;
+
+ case "https":
+ schema = "wss";
+ break;
+
+ case "ws":
+ case "wss":
+ break;
+ }
+ }
+ else
+ {
+ switch (schema)
+ {
+ case "ws":
+ schema = "http";
+ break;
+
+ case "wss":
+ schema = "https";
+ break;
+
+ case "http":
+ case "https":
+ break;
+ }
+ }
+
+ //Reconstruct the address
+ string finalAddress = schema + "://" + parts[1];
+
+ //Validate the address
+ if (!Uri.IsWellFormedUriString(finalAddress, UriKind.Absolute)) return false;
+
+ //Set the final address and OK it
+ serverUrl.Text = finalAddress;
+
+ return true;
+ }
+
private void button1_Click(object sender, EventArgs e)
{
if (topicId.Text.Length < 1)
@@ -35,7 +130,7 @@ namespace ntfysh_client
if (serverUrl.Text.Length < 1)
{
- MessageBox.Show("You must specify a server URL. The default is https://ntfy.sh", "Server URL not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ MessageBox.Show("You must specify a server URL. The default is wss://ntfy.sh", "Server URL not specified", MessageBoxButtons.OK, MessageBoxIcon.Error);
DialogResult = DialogResult.None;
serverUrl.Focus();
return;
@@ -65,6 +160,26 @@ namespace ntfysh_client
return;
}
+ try
+ {
+ if (!ReparseAddress())
+ {
+ MessageBox.Show($"The specified server URL is invalid. Accepted schemas are: http:// https:// ws:// wss://", "Invalid Server URL", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ DialogResult = DialogResult.None;
+ connectionType.Focus();
+ return;
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ MessageBox.Show($"The selected Connection Type '{connectionType.Text}' is invalid.", "Invalid Connection Type", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ DialogResult = DialogResult.None;
+ connectionType.Focus();
+ return;
+ }
+
+
+
DialogResult = DialogResult.OK;
}
@@ -108,5 +223,10 @@ namespace ntfysh_client
e.SuppressKeyPress = true;
}
}
+
+ private void connectionType_TextChanged(object sender, EventArgs e)
+ {
+ ReparseAddress();
+ }
}
}
diff --git a/ntfysh_client/SubscribeDialog.resx b/ntfysh_client/SubscribeDialog.resx
index 1af7de1..f298a7b 100644
--- a/ntfysh_client/SubscribeDialog.resx
+++ b/ntfysh_client/SubscribeDialog.resx
@@ -1,64 +1,4 @@
-
-
-
+
From ecbde9509f4b5429263dc72c14503efab6bd0024 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Wed, 7 Dec 2022 23:20:36 +0000
Subject: [PATCH 12/14] Implement error handling and notification
---
ntfysh_client/MainForm.cs | 26 +++-
ntfysh_client/NotificationListener.cs | 215 +++++++++++++++++++-------
ntfysh_client/SubscribedTopic.cs | 15 +-
3 files changed, 188 insertions(+), 68 deletions(-)
diff --git a/ntfysh_client/MainForm.cs b/ntfysh_client/MainForm.cs
index acf5b99..bd1bf9f 100644
--- a/ntfysh_client/MainForm.cs
+++ b/ntfysh_client/MainForm.cs
@@ -19,10 +19,29 @@ namespace ntfysh_client
{
_notificationListener = notificationListener;
_notificationListener.OnNotificationReceive += OnNotificationReceive;
+ _notificationListener.OnConnectionMultiAttemptFailure += OnConnectionMultiAttemptFailure;
+ _notificationListener.OnConnectionCredentialsFailure += OnConnectionCredentialsFailure;
InitializeComponent();
}
+ private void OnNotificationReceive(object sender, NotificationReceiveEventArgs e)
+ {
+ notifyIcon.ShowBalloonTip(3000, e.Title, e.Message, ToolTipIcon.Info);
+ }
+
+ private void OnConnectionMultiAttemptFailure(NotificationListener sender, SubscribedTopic topic)
+ {
+ MessageBox.Show($"Connecting to topic ID '{topic.TopicId}' on server '{topic.ServerUrl}' failed after multiple attempts.\n\nThis topic ID will be ignored and you will not receive notifications for it until you restart the application.", "Connection Failure", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+
+ private void OnConnectionCredentialsFailure(NotificationListener sender, SubscribedTopic topic)
+ {
+ string reason = string.IsNullOrWhiteSpace(topic.Username) ? "credentials are required but were not provided" : "the entered credentials are incorrect";
+
+ MessageBox.Show($"Connecting to topic ID '{topic.TopicId}' on server '{topic.ServerUrl}' failed because {reason}.\n\nThis topic ID will be ignored and you will not receive notifications for it until you correct the credentials.", "Connection Authentication Failure", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+
private void MainForm_Load(object sender, EventArgs e) => LoadTopics();
private void subscribeNewTopic_Click(object sender, EventArgs e)
@@ -137,7 +156,7 @@ namespace ntfysh_client
}
//Assemble new format
- List newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null, null, null)).ToList();
+ List newTopics = legacyTopics.Select(lt => new SubscribedTopic(lt, "https://ntfy.sh", null, null)).ToList();
string newFormatSerialised = JsonConvert.SerializeObject(newTopics, Formatting.Indented);
@@ -196,11 +215,6 @@ namespace ntfysh_client
}
}
- private void OnNotificationReceive(object sender, NotificationReceiveEventArgs e)
- {
- notifyIcon.ShowBalloonTip(3000, e.Title, e.Message, ToolTipIcon.Info);
- }
-
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
notifyIcon.Dispose();
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index 9995599..bd90dde 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -15,124 +15,230 @@ using System.Web;
namespace ntfysh_client
{
- public class NotificationListener : IDisposable
+ public class NotificationListener
{
- private readonly HttpClient _httpClient = new();
- private bool _isDisposed;
-
public readonly Dictionary SubscribedTopicsByUnique = new();
- public delegate void NotificationReceiveHandler(object sender, NotificationReceiveEventArgs e);
+ public delegate void NotificationReceiveHandler(NotificationListener sender, NotificationReceiveEventArgs e);
public event NotificationReceiveHandler? OnNotificationReceive;
+
+ public delegate void ConnectionErrorHandler(NotificationListener sender, SubscribedTopic topic);
+ public event ConnectionErrorHandler? OnConnectionMultiAttemptFailure;
+ public event ConnectionErrorHandler? OnConnectionCredentialsFailure;
public NotificationListener()
{
- _httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
ServicePointManager.DefaultConnectionLimit = 100;
}
- private async Task ListenToTopicWithHttpLongJsonAsync(HttpRequestMessage message, CancellationToken cancellationToken)
+ private async Task ListenToTopicWithHttpLongJsonAsync(HttpRequestMessage message, CancellationToken cancellationToken, SubscribedTopic topic)
{
- if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
+ int connectionAttempts = 0;
while (!cancellationToken.IsCancellationRequested)
{
- using HttpResponseMessage response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
- await using Stream body = await response.Content.ReadAsStreamAsync(cancellationToken);
-
+ //See if we have exceeded maximum attempts
+ if (connectionAttempts >= 10)
+ {
+ //10 connection failures (1 initial + 9 reattempts)! Do not retry
+ OnConnectionMultiAttemptFailure?.Invoke(this, topic);
+ return;
+ }
+
try
{
- StringBuilder mainBuffer = new();
+ //Establish connection
+ using HttpClient client = new();
+ client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite); //This will not prevent us from failing to connect, luckily
+ using HttpResponseMessage response = await client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ await using Stream body = await response.Content.ReadAsStreamAsync(cancellationToken);
+
+ //Ensure successful connect
+ response.EnsureSuccessStatusCode();
+
+ //Reset connection attempts after a successful connect
+ connectionAttempts = 0;
+
+ //Begin listening
+ 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 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)
+ catch (HttpRequestException hre)
{
+ if (hre.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
+ {
+ //Our credentials either aren't present when they need to be or are invalid
+
+ //Credential Failure! Do not retry
+ OnConnectionCredentialsFailure?.Invoke(this, topic);
+ return;
+ }
+
#if DEBUG
- Debug.WriteLine(ex);
+ Debug.WriteLine(hre);
#endif
- //Fall back to the outer loop to restart the listen, or cancel if requested
+ //We will not hit the finally block which will increment the connection failure counter and attempt a reconnect if applicable
+ }
+ catch (Exception e)
+ {
+ #if DEBUG
+ Debug.WriteLine(e);
+ #endif
+
+ //We will not hit the finally block which will increment the connection failure counter and attempt a reconnect if applicable
+ }
+ finally
+ {
+ //We land here if we fail to connect or our connection gets closed (and if we are canceeling, but that gets ignored)
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ //Not cancelling, legitimate connection failure or termination
+
+ if (connectionAttempts != 0)
+ {
+ //On our first reconnect attempt, try instantly. On consecutive, wait 3 seconds before each attempt
+ await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
+ }
+
+ //Increment attempts
+ connectionAttempts++;
+
+ //Proceed to reattempt
+ }
}
}
}
- private async Task ListenToTopicWithWebsocketAsync(Uri uri, NetworkCredential credentials, CancellationToken cancellationToken)
+ private async Task ListenToTopicWithWebsocketAsync(Uri uri, NetworkCredential credentials, CancellationToken cancellationToken, SubscribedTopic topic)
{
- if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
+ int connectionAttempts = 0;
while (!cancellationToken.IsCancellationRequested)
{
- using ClientWebSocket socket = new();
- socket.Options.Credentials = credentials;
-
+ //See if we have exceeded maximum attempts
+ if (connectionAttempts >= 10)
+ {
+ //10 connection failures (1 initial + 9 reattempts)! Do not retry
+ OnConnectionMultiAttemptFailure?.Invoke(this, topic);
+ return;
+ }
+
try
{
- StringBuilder mainBuffer = new();
+ //Establish connection
+ using ClientWebSocket socket = new();
+ socket.Options.Credentials = credentials;
await socket.ConnectAsync(uri, cancellationToken);
+ //Reset connection attempts after a successful connect
+ connectionAttempts = 0;
+
+ //Begin listening
+ StringBuilder mainBuffer = new();
+
while (!cancellationToken.IsCancellationRequested)
{
//Read as much as possible
byte[] buffer = new byte[8192];
WebSocketReceiveResult? result = await socket.ReceiveAsync(new ArraySegment(buffer), cancellationToken);
-
+
//Append it to our main buffer
mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
-
+
List 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)
+ catch (WebSocketException wse)
{
+ if (wse.WebSocketErrorCode is WebSocketError.NotAWebSocket)
+ {
+ //We haven't achieved a connection with a websocket. TODO Seems ntfy doesn't report unauthorised properly, and responds 200
+
+ //Credential Failure! Do not retry
+ OnConnectionCredentialsFailure?.Invoke(this, topic);
+ return;
+ }
+
#if DEBUG
- Debug.WriteLine(ex);
+ Debug.WriteLine(wse);
#endif
- //Fall back to the outer loop to restart the listen, or cancel if requested
+ //We will not hit the finally block which will increment the connection failure counter and attempt a reconnect if applicable
+ }
+ catch (Exception e)
+ {
+ #if DEBUG
+ Debug.WriteLine(e);
+ #endif
+
+ //We will not hit the finally block which will increment the connection failure counter and attempt a reconnect if applicable
+ }
+ finally
+ {
+ //We land here if we fail to connect or our connection gets closed (and if we are canceeling, but that gets ignored)
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ //Not cancelling, legitimate connection failure or termination
+
+ if (connectionAttempts != 0)
+ {
+ //On our first reconnect attempt, try instantly. On consecutive, wait 3 seconds before each attempt
+ await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
+ }
+
+ //Increment attempts
+ connectionAttempts++;
+
+ //Proceed to reattempt
+ }
}
}
}
@@ -156,8 +262,6 @@ namespace ntfysh_client
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;
@@ -172,31 +276,35 @@ namespace ntfysh_client
message.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(boundCredentialsBytes));
}
- CancellationTokenSource listenCanceller = new();
- Task listenTask = ListenToTopicWithHttpLongJsonAsync(message, listenCanceller.Token);
+ SubscribedTopic newTopic = new(topicId, serverUrl, username, password);
- SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
+ CancellationTokenSource listenCanceller = new();
+ Task listenTask = ListenToTopicWithHttpLongJsonAsync(message, listenCanceller.Token, newTopic);
+
+ newTopic.SetAssociatedRunner(listenTask, listenCanceller);
+
+ SubscribedTopicsByUnique.Add(unique, newTopic);
}
public void SubscribeToTopicUsingWebsocket(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(password)) password = null;
- Uri targetUri = new($"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/ws");
+ SubscribedTopic newTopic = new(topicId, serverUrl, username, password);
+
CancellationTokenSource listenCanceller = new();
- Task listenTask = ListenToTopicWithWebsocketAsync(targetUri, new NetworkCredential(username, password), listenCanceller.Token);
- SubscribedTopicsByUnique.Add(unique, new SubscribedTopic(topicId, serverUrl, username, password, listenTask, listenCanceller));
+ Task listenTask = ListenToTopicWithWebsocketAsync(new Uri($"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/ws"), new NetworkCredential(username, password), listenCanceller.Token, newTopic);
+
+ newTopic.SetAssociatedRunner(listenTask, listenCanceller);
+
+ SubscribedTopicsByUnique.Add(unique, newTopic);
}
public async Task UnsubscribeFromTopicAsync(string topicUniqueString)
{
- if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
-
#if DEBUG
Debug.WriteLine($"Removing topic {topicUniqueString}");
#endif
@@ -208,12 +316,12 @@ namespace ntfysh_client
if (!SubscribedTopicsByUnique.TryGetValue(topicUniqueString, out topic!)) return;
//Cancel and dispose the task runner
- topic.RunnerCanceller.Cancel();
+ topic.RunnerCanceller?.Cancel();
//Wait for the task runner to shut down
try
{
- await topic.Runner;
+ if (topic.Runner is not null) await topic.Runner;
}
catch (Exception)
{
@@ -221,19 +329,10 @@ namespace ntfysh_client
}
//Dispose task
- topic.Runner.Dispose();
+ topic.Runner?.Dispose();
//Remove the old topic
SubscribedTopicsByUnique.Remove(topicUniqueString);
}
-
- public void Dispose()
- {
- if (_isDisposed) return;
-
- _httpClient.Dispose();
-
- _isDisposed = true;
- }
}
}
diff --git a/ntfysh_client/SubscribedTopic.cs b/ntfysh_client/SubscribedTopic.cs
index f5b172b..b0593e1 100644
--- a/ntfysh_client/SubscribedTopic.cs
+++ b/ntfysh_client/SubscribedTopic.cs
@@ -1,4 +1,5 @@
-using System.Threading;
+using System;
+using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
@@ -6,12 +7,18 @@ namespace ntfysh_client
{
public class SubscribedTopic
{
- public SubscribedTopic(string topicId, string serverUrl, string? username, string? password, Task runner, CancellationTokenSource runnerCanceller)
+ public SubscribedTopic(string topicId, string serverUrl, string? username, string? password)
{
TopicId = topicId;
ServerUrl = serverUrl;
Username = username;
Password = password;
+ }
+
+ public void SetAssociatedRunner(Task runner, CancellationTokenSource runnerCanceller)
+ {
+ if (Runner is not null || RunnerCanceller is not null) throw new InvalidOperationException("Runner already associated");
+
Runner = runner;
RunnerCanceller = runnerCanceller;
}
@@ -22,9 +29,9 @@ namespace ntfysh_client
public string? Password { get; }
[JsonIgnore]
- public Task Runner { get; }
+ public Task? Runner { get; private set; }
[JsonIgnore]
- public CancellationTokenSource RunnerCanceller { get; }
+ public CancellationTokenSource? RunnerCanceller { get; private set; }
}
}
\ No newline at end of file
From 2b298d6e190a2cf346eec08d16b411cea176a661 Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Wed, 7 Dec 2022 23:28:09 +0000
Subject: [PATCH 13/14] Fix credentials on websocket
---
ntfysh_client/NotificationListener.cs | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/ntfysh_client/NotificationListener.cs b/ntfysh_client/NotificationListener.cs
index bd90dde..5e4d58c 100644
--- a/ntfysh_client/NotificationListener.cs
+++ b/ntfysh_client/NotificationListener.cs
@@ -140,7 +140,7 @@ namespace ntfysh_client
}
}
- private async Task ListenToTopicWithWebsocketAsync(Uri uri, NetworkCredential credentials, CancellationToken cancellationToken, SubscribedTopic topic)
+ private async Task ListenToTopicWithWebsocketAsync(Uri uri, string? credentials, CancellationToken cancellationToken, SubscribedTopic topic)
{
int connectionAttempts = 0;
@@ -158,8 +158,9 @@ namespace ntfysh_client
{
//Establish connection
using ClientWebSocket socket = new();
- socket.Options.Credentials = credentials;
-
+
+ if (!string.IsNullOrWhiteSpace(credentials)) socket.Options.SetRequestHeader("Authorization", "Basic " + credentials);
+
await socket.ConnectAsync(uri, cancellationToken);
//Reset connection attempts after a successful connect
@@ -269,7 +270,7 @@ namespace ntfysh_client
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, $"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/json");
- if (username != null && password != null)
+ if (username is not null && password is not null)
{
byte[] boundCredentialsBytes = Encoding.UTF8.GetBytes($"{username}:{password}");
@@ -294,9 +295,18 @@ namespace ntfysh_client
if (string.IsNullOrWhiteSpace(password)) password = null;
SubscribedTopic newTopic = new(topicId, serverUrl, username, password);
+
+ string? credentials = null;
+
+ if (username is not null && password is not null)
+ {
+ byte[] boundCredentialsBytes = Encoding.UTF8.GetBytes($"{username}:{password}");
+
+ credentials = Convert.ToBase64String(boundCredentialsBytes);
+ }
CancellationTokenSource listenCanceller = new();
- Task listenTask = ListenToTopicWithWebsocketAsync(new Uri($"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/ws"), new NetworkCredential(username, password), listenCanceller.Token, newTopic);
+ Task listenTask = ListenToTopicWithWebsocketAsync(new Uri($"{serverUrl}/{HttpUtility.UrlEncode(topicId)}/ws"), credentials, listenCanceller.Token, newTopic);
newTopic.SetAssociatedRunner(listenTask, listenCanceller);
From 2a8a7e3c0c17f4582cd58258831ba96bfffedf0e Mon Sep 17 00:00:00 2001
From: Alexander Horner <33007665+alexhorner@users.noreply.github.com>
Date: Wed, 7 Dec 2022 23:31:13 +0000
Subject: [PATCH 14/14] Adjust subscribe window tab index
---
ntfysh_client/SubscribeDialog.Designer.cs | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/ntfysh_client/SubscribeDialog.Designer.cs b/ntfysh_client/SubscribeDialog.Designer.cs
index 4977b26..9f1e66c 100644
--- a/ntfysh_client/SubscribeDialog.Designer.cs
+++ b/ntfysh_client/SubscribeDialog.Designer.cs
@@ -55,7 +55,7 @@ namespace ntfysh_client
this.panel1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(346, 51);
- this.panel1.TabIndex = 0;
+ this.panel1.TabIndex = 8;
//
// button2
//
@@ -64,7 +64,7 @@ namespace ntfysh_client
this.button2.Margin = new System.Windows.Forms.Padding(12, 12, 4, 12);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(88, 27);
- this.button2.TabIndex = 1;
+ this.button2.TabIndex = 7;
this.button2.Text = "Cancel";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
@@ -76,7 +76,7 @@ namespace ntfysh_client
this.button1.Margin = new System.Windows.Forms.Padding(4, 12, 12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 27);
- this.button1.TabIndex = 2;
+ this.button1.TabIndex = 6;
this.button1.Text = "Subscribe";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
@@ -99,7 +99,7 @@ namespace ntfysh_client
this.topicId.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.topicId.Name = "topicId";
this.topicId.Size = new System.Drawing.Size(318, 23);
- this.topicId.TabIndex = 0;
+ this.topicId.TabIndex = 1;
this.topicId.KeyDown += new System.Windows.Forms.KeyEventHandler(this.topicId_KeyDown);
//
// serverUrl
@@ -132,7 +132,7 @@ namespace ntfysh_client
this.username.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.username.Name = "username";
this.username.Size = new System.Drawing.Size(318, 23);
- this.username.TabIndex = 4;
+ this.username.TabIndex = 3;
this.username.KeyDown += new System.Windows.Forms.KeyEventHandler(this.username_KeyDown);
//
// label3
@@ -153,7 +153,7 @@ namespace ntfysh_client
this.password.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.password.Name = "password";
this.password.Size = new System.Drawing.Size(318, 23);
- this.password.TabIndex = 6;
+ this.password.TabIndex = 4;
this.password.UseSystemPasswordChar = true;
this.password.KeyDown += new System.Windows.Forms.KeyEventHandler(this.password_KeyDown);
//
@@ -187,7 +187,7 @@ namespace ntfysh_client
this.connectionType.Location = new System.Drawing.Point(14, 208);
this.connectionType.Name = "connectionType";
this.connectionType.Size = new System.Drawing.Size(318, 23);
- this.connectionType.TabIndex = 10;
+ this.connectionType.TabIndex = 5;
this.connectionType.TextChanged += new System.EventHandler(this.connectionType_TextChanged);
//
// SubscribeDialog