using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; using System.Threading; using System.Threading.Tasks; using Pingerino.Services.Interfaces; namespace Pingerino.Services { public class PingService : IPingService { private readonly ILoggingService _logger; private readonly List _pingRoundTripTimes = new List(); private readonly List _jitterValues = new List(); private readonly List _packetLossValues = new List(); private readonly object _lockObject = new object(); private Timer _pingTimer; private string _currentIpAddress; private bool _disposed; private CancellationTokenSource _cancellationTokenSource; public event EventHandler PingCompleted; public event EventHandler StatisticsUpdated; public bool IsRunning { get; private set; } public PingService(ILoggingService logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async System.Threading.Tasks.Task StartPingingAsync(string ipAddress, int intervalMs, CancellationToken cancellationToken = default) { if (IsRunning) { await StopPingingAsync(); } _currentIpAddress = ipAddress; _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _pingTimer = new Timer(async _ => await PingCallback(), null, 0, intervalMs); IsRunning = true; _logger.LogInformation($"Started pinging {ipAddress} with interval {intervalMs}ms"); } public async System.Threading.Tasks.Task StopPingingAsync() { if (!IsRunning) return; _pingTimer?.Dispose(); _cancellationTokenSource?.Cancel(); IsRunning = false; _logger.LogInformation("Stopped pinging"); await System.Threading.Tasks.Task.CompletedTask; } public async System.Threading.Tasks.Task PingOnceAsync(string ipAddress, CancellationToken cancellationToken = default) { try { if (!NetworkInterface.GetIsNetworkAvailable()) { return new PingResult { IpAddress = ipAddress, IsSuccess = false, Status = "No network connection available", ErrorMessage = "Network unavailable" }; } using (var ping = new Ping()) { var reply = await ping.SendPingAsync(ipAddress); var result = new PingResult { IpAddress = ipAddress, IsSuccess = reply.Status == IPStatus.Success, RoundTripTime = reply.RoundtripTime, Status = reply.Status.ToString() }; if (result.IsSuccess) { UpdateStatistics(reply.RoundtripTime); } return result; } } catch (Exception ex) { _logger.LogError($"Error pinging {ipAddress}", ex); return new PingResult { IpAddress = ipAddress, IsSuccess = false, Status = "Error", ErrorMessage = ex.Message }; } } public void ClearStatistics() { lock (_lockObject) { _pingRoundTripTimes.Clear(); _jitterValues.Clear(); _packetLossValues.Clear(); } _logger.LogInformation("Statistics cleared"); } public NetworkStatistics GetCurrentStatistics() { lock (_lockObject) { var stats = new NetworkStatistics(); if (_pingRoundTripTimes.Count > 0) { stats.MaxPing = _pingRoundTripTimes.Max(); stats.MinPing = _pingRoundTripTimes.Min(); stats.AveragePing = _pingRoundTripTimes.Average(); stats.TotalPings = _pingRoundTripTimes.Count; stats.SuccessfulPings = _pingRoundTripTimes.Count; } if (_jitterValues.Count > 0) { stats.MaxJitter = _jitterValues.Max(); stats.MinJitter = _jitterValues.Min(); stats.AverageJitter = _jitterValues.Average(); stats.Jitter = stats.AverageJitter; } if (_packetLossValues.Count > 0) { stats.MaxPacketLoss = _packetLossValues.Max(); stats.MinPacketLoss = _packetLossValues.Min(); stats.AveragePacketLoss = _packetLossValues.Average(); stats.PacketLoss = stats.AveragePacketLoss; } return stats; } } private async System.Threading.Tasks.Task PingCallback() { if (_cancellationTokenSource?.Token.IsCancellationRequested == true) return; try { var result = await PingOnceAsync(_currentIpAddress, _cancellationTokenSource?.Token ?? CancellationToken.None); PingCompleted?.Invoke(this, new PingResultEventArgs(result, DateTime.Now)); // Calculate and update network statistics periodically await CalculateNetworkStatisticsAsync(); } catch (Exception ex) { _logger.LogError("Error in ping callback", ex); } } private void UpdateStatistics(long roundTripTime) { lock (_lockObject) { _pingRoundTripTimes.Add(roundTripTime); // Calculate jitter if we have previous measurements if (_pingRoundTripTimes.Count > 1) { var previousTime = _pingRoundTripTimes[_pingRoundTripTimes.Count - 2]; var jitter = Math.Abs(roundTripTime - previousTime); _jitterValues.Add(jitter); } } } private async System.Threading.Tasks.Task CalculateNetworkStatisticsAsync() { try { // Calculate packet loss over the last 10 pings var packetLoss = await CalculatePacketLossAsync(_currentIpAddress, 10); lock (_lockObject) { _packetLossValues.Add(packetLoss); } var stats = GetCurrentStatistics(); StatisticsUpdated?.Invoke(this, new NetworkStatisticsEventArgs(stats)); } catch (Exception ex) { _logger.LogError("Error calculating network statistics", ex); } } private async System.Threading.Tasks.Task CalculatePacketLossAsync(string ipAddress, int count) { int failedPings = 0; var tasks = new List>(); for (int i = 0; i < count; i++) { tasks.Add(TestSinglePingAsync(ipAddress)); } var results = await System.Threading.Tasks.Task.WhenAll(tasks); failedPings = results.Count(r => !r); return (double)failedPings / count * 100; } private async System.Threading.Tasks.Task TestSinglePingAsync(string ipAddress) { try { using (var ping = new Ping()) { var reply = await ping.SendPingAsync(ipAddress); return reply.Status == IPStatus.Success; } } catch { return false; } } public void Dispose() { if (_disposed) return; _pingTimer?.Dispose(); _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _disposed = true; } } }