Major overhaul #2
@@ -34,7 +34,14 @@ namespace ntfysh_client
 | 
				
			|||||||
            if (result != DialogResult.OK) return;
 | 
					            if (result != DialogResult.OK) return;
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            //Subscribe
 | 
					            //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
 | 
					            //Add to the user visible list
 | 
				
			||||||
            notificationTopics.Items.Add(dialog.Unique);
 | 
					            notificationTopics.Items.Add(dialog.Unique);
 | 
				
			||||||
@@ -167,7 +174,24 @@ namespace ntfysh_client
 | 
				
			|||||||
            //Load them in
 | 
					            //Load them in
 | 
				
			||||||
            foreach (SubscribedTopic topic in topics)
 | 
					            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}");
 | 
					                notificationTopics.Items.Add($"{topic.TopicId}@{topic.ServerUrl}");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ namespace ntfysh_client
 | 
				
			|||||||
            ServicePointManager.DefaultConnectionLimit = 100;
 | 
					            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));
 | 
					            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
@@ -84,6 +84,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<byte>(buffer), cancellationToken);
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        //Append it to our main buffer
 | 
				
			||||||
 | 
					                        mainBuffer.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        List<string> lines = mainBuffer.ToString().Split('\n').ToList();
 | 
				
			||||||
 | 
					                        //If we have not yet received a full line, meaning theres only 1 part, go back to reading
 | 
				
			||||||
 | 
					                        if (lines.Count <= 1) continue;
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        //We now have at least 1 line! Count how many full lines. There will always be a partial line at the end, even if that partial line is empty
 | 
				
			||||||
 | 
					                        //Separate the partial line from the full lines
 | 
				
			||||||
 | 
					                        int partialLineIndex = lines.Count - 1;
 | 
				
			||||||
 | 
					                        string partialLine = lines[partialLineIndex];
 | 
				
			||||||
 | 
					                        lines.RemoveAt(partialLineIndex);
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        //Process the full lines
 | 
				
			||||||
 | 
					                        foreach (string line in lines) ProcessMessage(line);
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        //Write back the partial line
 | 
				
			||||||
 | 
					                        mainBuffer.Clear();
 | 
				
			||||||
 | 
					                        mainBuffer.Append(partialLine);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    #if DEBUG
 | 
				
			||||||
 | 
					                        Debug.WriteLine(ex);
 | 
				
			||||||
 | 
					                    #endif
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    //Fall back to the outer loop to restart the listen, or cancel if requested
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void ProcessMessage(string message)
 | 
					        private void ProcessMessage(string message)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            #if DEBUG
 | 
					            #if DEBUG
 | 
				
			||||||
@@ -120,11 +173,26 @@ namespace ntfysh_client
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            CancellationTokenSource listenCanceller = new();
 | 
					            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));
 | 
					            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)
 | 
					        public async Task UnsubscribeFromTopicAsync(string topicUniqueString)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
					            if (_isDisposed) throw new ObjectDisposedException(nameof(NotificationListener));
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										94
									
								
								ntfysh_client/SubscribeDialog.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										94
									
								
								ntfysh_client/SubscribeDialog.Designer.cs
									
									
									
										generated
									
									
									
								
							@@ -40,6 +40,8 @@ namespace ntfysh_client
 | 
				
			|||||||
            this.label3 = new System.Windows.Forms.Label();
 | 
					            this.label3 = new System.Windows.Forms.Label();
 | 
				
			||||||
            this.password = new System.Windows.Forms.TextBox();
 | 
					            this.password = new System.Windows.Forms.TextBox();
 | 
				
			||||||
            this.label4 = new System.Windows.Forms.Label();
 | 
					            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.panel1.SuspendLayout();
 | 
				
			||||||
            this.SuspendLayout();
 | 
					            this.SuspendLayout();
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
@@ -49,18 +51,19 @@ namespace ntfysh_client
 | 
				
			|||||||
            this.panel1.Controls.Add(this.button2);
 | 
					            this.panel1.Controls.Add(this.button2);
 | 
				
			||||||
            this.panel1.Controls.Add(this.button1);
 | 
					            this.panel1.Controls.Add(this.button1);
 | 
				
			||||||
            this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
 | 
					            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.Name = "panel1";
 | 
				
			||||||
            this.panel1.Size = new System.Drawing.Size(297, 44);
 | 
					            this.panel1.Size = new System.Drawing.Size(346, 51);
 | 
				
			||||||
            this.panel1.TabIndex = 0;
 | 
					            this.panel1.TabIndex = 0;
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            // button2
 | 
					            // button2
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
 | 
					            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.Location = new System.Drawing.Point(153, 13);
 | 
				
			||||||
            this.button2.Margin = new System.Windows.Forms.Padding(10, 10, 3, 10);
 | 
					            this.button2.Margin = new System.Windows.Forms.Padding(12, 12, 4, 12);
 | 
				
			||||||
            this.button2.Name = "button2";
 | 
					            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.TabIndex = 1;
 | 
				
			||||||
            this.button2.Text = "Cancel";
 | 
					            this.button2.Text = "Cancel";
 | 
				
			||||||
            this.button2.UseVisualStyleBackColor = true;
 | 
					            this.button2.UseVisualStyleBackColor = true;
 | 
				
			||||||
@@ -69,10 +72,10 @@ namespace ntfysh_client
 | 
				
			|||||||
            // button1
 | 
					            // button1
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
 | 
					            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.Location = new System.Drawing.Point(247, 13);
 | 
				
			||||||
            this.button1.Margin = new System.Windows.Forms.Padding(3, 10, 10, 10);
 | 
					            this.button1.Margin = new System.Windows.Forms.Padding(4, 12, 12, 12);
 | 
				
			||||||
            this.button1.Name = "button1";
 | 
					            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.TabIndex = 2;
 | 
				
			||||||
            this.button1.Text = "Subscribe";
 | 
					            this.button1.Text = "Subscribe";
 | 
				
			||||||
            this.button1.UseVisualStyleBackColor = true;
 | 
					            this.button1.UseVisualStyleBackColor = true;
 | 
				
			||||||
@@ -81,9 +84,10 @@ namespace ntfysh_client
 | 
				
			|||||||
            // label1
 | 
					            // label1
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            this.label1.AutoSize = true;
 | 
					            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.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.TabIndex = 1;
 | 
				
			||||||
            this.label1.Text = "Topic ID:";
 | 
					            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) 
 | 
					            this.topicId.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
				
			||||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
					            | 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.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.TabIndex = 0;
 | 
				
			||||||
            this.topicId.KeyDown += new System.Windows.Forms.KeyEventHandler(this.topicId_KeyDown);
 | 
					            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) 
 | 
					            this.serverUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
				
			||||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
					            | 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.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.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);
 | 
					            this.serverUrl.KeyDown += new System.Windows.Forms.KeyEventHandler(this.serverUrl_KeyDown);
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            // label2
 | 
					            // label2
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            this.label2.AutoSize = true;
 | 
					            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.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.TabIndex = 3;
 | 
				
			||||||
            this.label2.Text = "Server URL:";
 | 
					            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) 
 | 
					            this.username.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
				
			||||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
					            | 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.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.TabIndex = 4;
 | 
				
			||||||
            this.username.KeyDown += new System.Windows.Forms.KeyEventHandler(this.username_KeyDown);
 | 
					            this.username.KeyDown += new System.Windows.Forms.KeyEventHandler(this.username_KeyDown);
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            // label3
 | 
					            // label3
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            this.label3.AutoSize = true;
 | 
					            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.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.TabIndex = 5;
 | 
				
			||||||
            this.label3.Text = "Username:";
 | 
					            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) 
 | 
					            this.password.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
 | 
				
			||||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
					            | 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.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.TabIndex = 6;
 | 
				
			||||||
            this.password.UseSystemPasswordChar = true;
 | 
					            this.password.UseSystemPasswordChar = true;
 | 
				
			||||||
            this.password.KeyDown += new System.Windows.Forms.KeyEventHandler(this.password_KeyDown);
 | 
					            this.password.KeyDown += new System.Windows.Forms.KeyEventHandler(this.password_KeyDown);
 | 
				
			||||||
@@ -150,18 +160,44 @@ namespace ntfysh_client
 | 
				
			|||||||
            // label4
 | 
					            // label4
 | 
				
			||||||
            // 
 | 
					            // 
 | 
				
			||||||
            this.label4.AutoSize = true;
 | 
					            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.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.TabIndex = 7;
 | 
				
			||||||
            this.label4.Text = "Password:";
 | 
					            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
 | 
					            // 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.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
				
			||||||
            this.BackColor = System.Drawing.Color.White;
 | 
					            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.password);
 | 
				
			||||||
            this.Controls.Add(this.label4);
 | 
					            this.Controls.Add(this.label4);
 | 
				
			||||||
            this.Controls.Add(this.username);
 | 
					            this.Controls.Add(this.username);
 | 
				
			||||||
@@ -172,6 +208,7 @@ namespace ntfysh_client
 | 
				
			|||||||
            this.Controls.Add(this.label1);
 | 
					            this.Controls.Add(this.label1);
 | 
				
			||||||
            this.Controls.Add(this.panel1);
 | 
					            this.Controls.Add(this.panel1);
 | 
				
			||||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
 | 
					            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
 | 
				
			||||||
 | 
					            this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
 | 
				
			||||||
            this.MaximizeBox = false;
 | 
					            this.MaximizeBox = false;
 | 
				
			||||||
            this.MinimizeBox = false;
 | 
					            this.MinimizeBox = false;
 | 
				
			||||||
            this.Name = "SubscribeDialog";
 | 
					            this.Name = "SubscribeDialog";
 | 
				
			||||||
@@ -179,6 +216,7 @@ namespace ntfysh_client
 | 
				
			|||||||
            this.ShowInTaskbar = false;
 | 
					            this.ShowInTaskbar = false;
 | 
				
			||||||
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
 | 
					            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
 | 
				
			||||||
            this.Text = "Subscribe to new topic";
 | 
					            this.Text = "Subscribe to new topic";
 | 
				
			||||||
 | 
					            this.Load += new System.EventHandler(this.SubscribeDialog_Load);
 | 
				
			||||||
            this.panel1.ResumeLayout(false);
 | 
					            this.panel1.ResumeLayout(false);
 | 
				
			||||||
            this.ResumeLayout(false);
 | 
					            this.ResumeLayout(false);
 | 
				
			||||||
            this.PerformLayout();
 | 
					            this.PerformLayout();
 | 
				
			||||||
@@ -199,5 +237,7 @@ namespace ntfysh_client
 | 
				
			|||||||
        private System.Windows.Forms.Label label3;
 | 
					        private System.Windows.Forms.Label label3;
 | 
				
			||||||
        private System.Windows.Forms.TextBox password;
 | 
					        private System.Windows.Forms.TextBox password;
 | 
				
			||||||
        private System.Windows.Forms.Label label4;
 | 
					        private System.Windows.Forms.Label label4;
 | 
				
			||||||
 | 
					        private System.Windows.Forms.Label label5;
 | 
				
			||||||
 | 
					        private System.Windows.Forms.ComboBox connectionType;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -17,12 +17,107 @@ namespace ntfysh_client
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public string Unique => $"{topicId.Text}@{serverUrl.Text}";
 | 
					        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)
 | 
					        public SubscribeDialog(ListBox notificationTopics)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _notificationTopics = notificationTopics;
 | 
					            _notificationTopics = notificationTopics;
 | 
				
			||||||
            InitializeComponent();
 | 
					            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)
 | 
					        private void button1_Click(object sender, EventArgs e)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (topicId.Text.Length < 1)
 | 
					            if (topicId.Text.Length < 1)
 | 
				
			||||||
@@ -35,7 +130,7 @@ namespace ntfysh_client
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (serverUrl.Text.Length < 1)
 | 
					            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;
 | 
					                DialogResult = DialogResult.None;
 | 
				
			||||||
                serverUrl.Focus();
 | 
					                serverUrl.Focus();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@@ -65,6 +160,26 @@ namespace ntfysh_client
 | 
				
			|||||||
                return;
 | 
					                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;
 | 
					            DialogResult = DialogResult.OK;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,5 +223,10 @@ namespace ntfysh_client
 | 
				
			|||||||
                e.SuppressKeyPress = true;
 | 
					                e.SuppressKeyPress = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void connectionType_TextChanged(object sender, EventArgs e)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ReparseAddress();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,64 +1,4 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<root>
 | 
				
			||||||
<root>
 | 
					 | 
				
			||||||
  <!-- 
 | 
					 | 
				
			||||||
    Microsoft ResX Schema 
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    Version 2.0
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    The primary goals of this format is to allow a simple XML format 
 | 
					 | 
				
			||||||
    that is mostly human readable. The generation and parsing of the 
 | 
					 | 
				
			||||||
    various data types are done through the TypeConverter classes 
 | 
					 | 
				
			||||||
    associated with the data types.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    Example:
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    ... ado.net/XML headers & schema ...
 | 
					 | 
				
			||||||
    <resheader name="resmimetype">text/microsoft-resx</resheader>
 | 
					 | 
				
			||||||
    <resheader name="version">2.0</resheader>
 | 
					 | 
				
			||||||
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
 | 
					 | 
				
			||||||
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
 | 
					 | 
				
			||||||
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
 | 
					 | 
				
			||||||
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
 | 
					 | 
				
			||||||
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
 | 
					 | 
				
			||||||
        <value>[base64 mime encoded serialized .NET Framework object]</value>
 | 
					 | 
				
			||||||
    </data>
 | 
					 | 
				
			||||||
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
 | 
					 | 
				
			||||||
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
 | 
					 | 
				
			||||||
        <comment>This is a comment</comment>
 | 
					 | 
				
			||||||
    </data>
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
    There are any number of "resheader" rows that contain simple 
 | 
					 | 
				
			||||||
    name/value pairs.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    Each data row contains a name, and value. The row also contains a 
 | 
					 | 
				
			||||||
    type or mimetype. Type corresponds to a .NET class that support 
 | 
					 | 
				
			||||||
    text/value conversion through the TypeConverter architecture. 
 | 
					 | 
				
			||||||
    Classes that don't support this are serialized and stored with the 
 | 
					 | 
				
			||||||
    mimetype set.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    The mimetype is used for serialized objects, and tells the 
 | 
					 | 
				
			||||||
    ResXResourceReader how to depersist the object. This is currently not 
 | 
					 | 
				
			||||||
    extensible. For a given mimetype the value must be set accordingly:
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    Note - application/x-microsoft.net.object.binary.base64 is the format 
 | 
					 | 
				
			||||||
    that the ResXResourceWriter will generate, however the reader can 
 | 
					 | 
				
			||||||
    read any of the formats listed below.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    mimetype: application/x-microsoft.net.object.binary.base64
 | 
					 | 
				
			||||||
    value   : The object must be serialized with 
 | 
					 | 
				
			||||||
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
 | 
					 | 
				
			||||||
            : and then encoded with base64 encoding.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    mimetype: application/x-microsoft.net.object.soap.base64
 | 
					 | 
				
			||||||
    value   : The object must be serialized with 
 | 
					 | 
				
			||||||
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
 | 
					 | 
				
			||||||
            : and then encoded with base64 encoding.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    mimetype: application/x-microsoft.net.object.bytearray.base64
 | 
					 | 
				
			||||||
    value   : The object must be serialized into a byte array 
 | 
					 | 
				
			||||||
            : using a System.ComponentModel.TypeConverter
 | 
					 | 
				
			||||||
            : and then encoded with base64 encoding.
 | 
					 | 
				
			||||||
    -->
 | 
					 | 
				
			||||||
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
 | 
					  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
 | 
				
			||||||
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
 | 
					    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
 | 
				
			||||||
    <xsd:element name="root" msdata:IsDataSet="true">
 | 
					    <xsd:element name="root" msdata:IsDataSet="true">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user