Files
AiQ_GUI/Network.cs
2025-09-02 15:32:24 +01:00

183 lines
7.1 KiB
C#

using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
namespace AiQ_GUI
{
public class Network
{
public static HttpClient? SingleHTTPClient;
public static HttpClient Client => SingleHTTPClient ?? throw new InvalidOperationException("Client not initialized.");
public static void Initialize(string username, string password)
{
HttpClientHandler handler = new HttpClientHandler
{
MaxConnectionsPerServer = 25,
Credentials = new NetworkCredential(username, password)
};
SingleHTTPClient?.Dispose(); // Dispose old client if needed
SingleHTTPClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(20)
};
}
// Handles get and post to the camera API
public static async Task<string> SendHttpRequest(string url, HttpMethod method, int? Timeout, string? jsonData = null, string[]? Headers = null)
{
try
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
if (jsonData != null) // Fills in the body of the request if jsonData is provided
request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");
if (Headers != null) // Fills in the headers of the request if Headers are provided
{
foreach (string Header in Headers)
{
string[] parts = Header.Split(':');
if (parts.Length == 2)
request.Headers.Add(parts[0].Trim(), parts[1].Trim());
else
throw new ArgumentException($"Invalid header format: {Header}");
}
}
int timeoutMs = (Timeout ?? 10) * 1000; // Convert from seconds to ms
using CancellationTokenSource cts = new CancellationTokenSource(timeoutMs);
using HttpResponseMessage response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cts.Token); // Use token here too if you're chaining timeouts
}
catch (TaskCanceledException ex)
{
return $"HTTP error calling {url}: {ex.Message}";
}
catch (HttpRequestException ex)
{
return $"HTTP error calling {url}: {ex.Message}";
}
catch (Exception ex)
{
return $"Unexpected error calling {url}: {ex.Message}";
}
}
public async static Task<IList<string>> SearchForCams()
{
const int sendPort = 6666;
const int receivePort = 6667;
IList<string> FoundCams = [];
byte[] discoveryPacket = [0x50, 0x4f, 0x4c, 0x4c, 0xaf, 0xb0, 0xb3, 0xb3, 0xb6, 0x01, 0xa8, 0xc0, 0x0b, 0x1a, 0x00, 0x00];
async Task SendAndListen(IPAddress localIp)
{
using (UdpClient sender = new(new IPEndPoint(localIp, sendPort)))
{
sender.EnableBroadcast = true;
sender.Connect(new IPEndPoint(IPAddress.Broadcast, sendPort));
sender.Send(discoveryPacket, discoveryPacket.Length);
}
using UdpClient receiver = new(receivePort); // Listen for replies on fixed port
receiver.Client.ReceiveTimeout = 750;
DateTime timeout = DateTime.Now.AddMilliseconds(750);
try
{
while (DateTime.Now < timeout)
{
if (receiver.Available > 0)
{
UdpReceiveResult result = await receiver.ReceiveAsync();
byte[] recvBuffer = result.Buffer;
if (recvBuffer.Length >= 52) // Safety check
{
byte[] ipBytes = recvBuffer.Skip(recvBuffer.Length - 52).Take(4).Reverse().ToArray();
string ipToAdd = string.Join(".", ipBytes);
if (!FoundCams.Contains(ipToAdd))
FoundCams.Add(ipToAdd);
}
}
await Task.Delay(50); // brief wait to allow data in
}
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
// No data received in time — normal case
}
}
// Get first IPv4 interface (non-loopback)
foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
try
{
await SendAndListen(ip);
}
catch { }
}
return FoundCams;
}
// Ping to make sure devices are connected to the network, be aware it isn't consistant across subnets.
public async static Task<bool> PingIP(string ipAddress)
{
if (RegexCache.RegexIPPattern().IsMatch(ipAddress))
{
try
{
Ping myPing = new();
PingReply reply = await myPing.SendPingAsync(ipAddress, 1000); // Timeout is 1s
return reply.Status == IPStatus.Success;
}
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {ex.Message}"); }
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}"); }
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}"); }
}
return false;
}
// Start a thread that pings each IP in the hardware accessories menu
public static async Task PingAndUpdateUI(string IP, Label label, string ItemName, ToolTip TT, Button[]? buttonsToEnable = null)
{
bool isAvailable = await PingIP(IP);
label.Invoke(() =>
{
if (isAvailable)
{
label.Text = "✔";
label.ForeColor = Color.ForestGreen;
TT.SetToolTip(label, ItemName + " Available");
}
else
{
label.Text = "❌";
label.ForeColor = Color.Red;
TT.SetToolTip(label, "No ping from " + ItemName);
}
if (buttonsToEnable != null)
{
foreach (Button button in buttonsToEnable)
button.Enabled = isAvailable;
}
});
}
}
}