diff --git a/stmx.tests/Commands/NetworkCommandTest.cs b/stmx.tests/Commands/NetworkCommandTest.cs new file mode 100644 index 0000000..5dc3a15 --- /dev/null +++ b/stmx.tests/Commands/NetworkCommandTest.cs @@ -0,0 +1,213 @@ +using Moq; +using stmx.Commands; +using stmx.Services; +using stmx.Utils; + +namespace stmx.Tests; + +public class NetworkCommandTests +{ + private Mock _mockStats; + private Mock _mockIcons; + private NetworkCommand _cmd; + + [SetUp] + public void SetUp() + { + _mockStats = new Mock(); + _mockIcons = new Mock(); + + _mockStats.SetupGet(s => s.Options).Returns(new SystemStatsServiceOptions + { + DefaultNetworkDirection = NetworkDirection.Both, + DefaultNetworkUnit = NetworkUnits.Auto, + DefaultNetworkDelay = 1 + }); + _mockIcons.SetupGet(i => i.Options).Returns(new IconServiceOptions + { + NetworkDownload = "DOWN_ICON", + NetworkUpload = "UP_ICON" + }); + + _cmd = new NetworkCommand(_mockStats.Object, _mockIcons.Object); + } + + private void SetupNetworkSpeed(double upload, double download, NetworkUnits unit = NetworkUnits.KiloBits) + { + _mockStats.Setup(s => s.GetNetworkSpeed(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(( + new NetworkSpeed(upload, unit), + new NetworkSpeed(download, unit) + )); + } + + [Test] + public async Task TestNetworkCommand_PrintsBothDirections() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Both, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("20.30k 10.50k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsDownloadOnly() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Download, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("20.30k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsUploadOnly() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Upload, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("10.50k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsDefaultDownloadIcon() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: true, downloadIconValue: "", + direction: NetworkDirection.Download, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("DOWN_ICON 20.30k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsDefaultUploadIcon() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: true, uploadIconValue: "", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Upload, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("UP_ICON 10.50k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsCustomDownloadIcon() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: true, downloadIconValue: "↓", + direction: NetworkDirection.Download, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("↓ 20.30k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsCustomUploadIcon() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: true, uploadIconValue: "↑", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Upload, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("↑ 10.50k")); + } + + [Test] + public async Task TestNetworkCommand_PrintsBothWithIcons() + { + SetupNetworkSpeed(10.5, 20.3); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: true, uploadIconValue: "↑", + showDownloadIcon: true, downloadIconValue: "↓", + direction: NetworkDirection.Both, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("↓ 20.30k ↑ 10.50k")); + } + + [Test] + public async Task TestNetworkCommand_FormatsToTwoDecimalPlaces() + { + SetupNetworkSpeed(10.1234, 20.5678); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Both, + network: "wlo1", delay: 1, unit: NetworkUnits.KiloBits + ); + + Assert.That(consoleOut.ToString(), Is.EqualTo("20.57k 10.12k")); + } + + [Test] + public async Task TestNetworkCommand_UsesCorrectNetworkInterface() + { + SetupNetworkSpeed(0, 0); + var consoleOut = new StringWriter(); + Console.SetOut(consoleOut); + + await _cmd.ExecuteAsync( + showUploadIcon: false, uploadIconValue: "", + showDownloadIcon: false, downloadIconValue: "", + direction: NetworkDirection.Both, + network: "eth0", delay: 1, unit: NetworkUnits.KiloBits + ); + + _mockStats.Verify(s => s.GetNetworkSpeed(It.IsAny(), "eth0", It.IsAny()), Times.Once); + } +} diff --git a/stmx.tests/Services/LinuxSystemStatsServiceTest.cs b/stmx.tests/Services/LinuxSystemStatsServiceTest.cs index c48069b..29b80f9 100644 --- a/stmx.tests/Services/LinuxSystemStatsServiceTest.cs +++ b/stmx.tests/Services/LinuxSystemStatsServiceTest.cs @@ -265,5 +265,142 @@ public async Task GetCpuUsagePercent_ReturnsNull_OnIOException() Assert.That(result, Is.Null); } + + [Test] + public void ReadNetworkSpeedFromSysFile_ParsesCorrectly() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/wlo1")).Returns(true); + _fileReaderMock.SetupSequence(f => f.ReadAllText(It.IsAny())) + .Returns("1000") // tx_bytes + .Returns("2000"); // rx_bytes + + var (upload, download) = _service.ReadNetworkSpeedFromSysFile("wlo1"); + + Assert.That(upload, Is.EqualTo(1000 * 8)); + Assert.That(download, Is.EqualTo(2000 * 8)); + } + + [Test] + public void ReadNetworkSpeedFromSysFile_ThrowsIOException_WhenInterfaceNotFound() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/eth99")).Returns(false); + + Assert.Throws(() => _service.ReadNetworkSpeedFromSysFile("eth99")); + } + + [Test] + public async Task GetNetworkSpeed_ComputesCorrectBitsPerSecond() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/wlo1")).Returns(true); + + // First sample: 1000 tx, 2000 rx bytes + // Second sample: 4000 tx, 7000 rx bytes + // Over 1 second delay: upload = (4000-1000)*8 = 24000 bps, download = (7000-2000)*8 = 40000 bps + _fileReaderMock.SetupSequence(f => f.ReadAllText(It.IsAny())) + .Returns("1000") + .Returns("2000") + .Returns("4000") + .Returns("7000"); + + var (upload, download) = await _service.GetNetworkSpeed(NetworkUnits.Bits, "wlo1", 1); + + Assert.That(upload.Value, Is.EqualTo(24000)); + Assert.That(download.Value, Is.EqualTo(40000)); + Assert.That(upload.Unit, Is.EqualTo(NetworkUnits.Bits)); + Assert.That(download.Unit, Is.EqualTo(NetworkUnits.Bits)); + } + + [Test] + public async Task GetNetworkSpeed_ConvertsToKiloBits() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/wlo1")).Returns(true); + + // upload = (4000-1000)*8 = 24000 bps = ~23.4375 kbps + // download = (7000-2000)*8 = 40000 bps = ~39.0625 kbps + _fileReaderMock.SetupSequence(f => f.ReadAllText(It.IsAny())) + .Returns("1000") + .Returns("2000") + .Returns("4000") + .Returns("7000"); + + var (upload, download) = await _service.GetNetworkSpeed(NetworkUnits.KiloBits, "wlo1", 1); + + Assert.That(upload.Unit, Is.EqualTo(NetworkUnits.KiloBits)); + Assert.That(download.Unit, Is.EqualTo(NetworkUnits.KiloBits)); + Assert.That(upload.Value, Is.EqualTo(24000.0 / 1024).Within(0.001)); + Assert.That(download.Value, Is.EqualTo(40000.0 / 1024).Within(0.001)); + } + + [Test] + public async Task GetNetworkSpeed_Auto_ReturnsKiloBitsAtMinimum() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/wlo1")).Returns(true); + + // Very low speed — should still return KiloBits not Bits + _fileReaderMock.SetupSequence(f => f.ReadAllText(It.IsAny())) + .Returns("1000") + .Returns("2000") + .Returns("1001") + .Returns("2001"); + + var (upload, download) = await _service.GetNetworkSpeed(NetworkUnits.Auto, "wlo1", 1); + + Assert.That(upload.Unit, Is.EqualTo(NetworkUnits.KiloBits)); + Assert.That(download.Unit, Is.EqualTo(NetworkUnits.KiloBits)); + } + + [Test] + public async Task GetNetworkSpeed_Auto_SelectsMegaBits_WhenSpeedIsHigh() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/wlo1")).Returns(true); + + // delta = 200_000 bytes = 1_600_000 bits > 1_048_576 → MegaBits + _fileReaderMock.SetupSequence(f => f.ReadAllText(It.IsAny())) + .Returns("0") + .Returns("0") + .Returns("200000") + .Returns("200000"); + + var (upload, download) = await _service.GetNetworkSpeed(NetworkUnits.Auto, "wlo1", 1); + + Assert.That(upload.Unit, Is.EqualTo(NetworkUnits.MegaBits)); + Assert.That(download.Unit, Is.EqualTo(NetworkUnits.MegaBits)); + } + + [Test] + public async Task GetNetworkSpeed_HandlesCounterReset_Gracefully() + { + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/wlo1")).Returns(true); + + // Counter went backwards (interface reset) + _fileReaderMock.SetupSequence(f => f.ReadAllText(It.IsAny())) + .Returns("5000") + .Returns("5000") + .Returns("100") + .Returns("100"); + + var (upload, download) = await _service.GetNetworkSpeed(NetworkUnits.Bits, "wlo1", 1); + + // Should not be negative + Assert.That(upload.Value, Is.GreaterThanOrEqualTo(0)); + Assert.That(download.Value, Is.GreaterThanOrEqualTo(0)); + } + + [Test] + public void ReadNetworkSpeedFromSysFile_UsesDefaultInterface_WhenPassedDefault() + { + string mockRoute = "Iface\tDestination\tGateway\n" + + "eth0\t00000000\t0101A8C0\n"; + + _fileSystemMock.Setup(fs => fs.DirectoryExists("/sys/class/net/eth0")).Returns(true); + _fileReaderMock.Setup(f => f.ReadAllText("/proc/net/route")).Returns(mockRoute); + _fileReaderMock.Setup(f => f.ReadAllText("/sys/class/net/eth0/statistics/tx_bytes")).Returns("1000"); + _fileReaderMock.Setup(f => f.ReadAllText("/sys/class/net/eth0/statistics/rx_bytes")).Returns("2000"); + + var (upload, download) = _service.ReadNetworkSpeedFromSysFile("default"); + + Assert.That(upload, Is.EqualTo(1000 * 8)); + Assert.That(download, Is.EqualTo(2000 * 8)); + } } diff --git a/stmx/Commands/NetworkCommand.cs b/stmx/Commands/NetworkCommand.cs new file mode 100644 index 0000000..112669b --- /dev/null +++ b/stmx/Commands/NetworkCommand.cs @@ -0,0 +1,124 @@ +using System.CommandLine; +using System.Text; + +using stmx.Services; +using stmx.Utils; + +namespace stmx.Commands; + +class NetworkCommand : Command +{ + private readonly ISystemStatsService _systemStats; + private readonly IIconService _icons; + + public NetworkCommand(ISystemStatsService systemStats, IIconService icons) : base("network", "get current network usage") + { + _systemStats = systemStats ?? throw new ArgumentNullException(nameof(systemStats)); + _icons = icons ?? throw new ArgumentNullException(nameof(icons)); + + var showDownloadIconOption = new Option("--show-download-icon", ["-di"]); + showDownloadIconOption.Description = "show download icon (optionally provide custom icon)"; + showDownloadIconOption.Arity = ArgumentArity.ZeroOrOne; + Add(showDownloadIconOption); + + var showUploadIconOption = new Option("--show-upload-icon", ["-ui"]); + showUploadIconOption.Description = "show upload icon (optionally provide custom icon)"; + // this allows users to pass one or more values + showUploadIconOption.Arity = ArgumentArity.ZeroOrOne; + Add(showUploadIconOption); + + var directionOption = new Option("--direction", "-d"); + directionOption.Description = "Show upload or download"; + directionOption.DefaultValueFactory = _ => _systemStats.Options.DefaultNetworkDirection; + Add(directionOption); + + // option to specify network interface + var networkOption = new Option("--network", "-n"); + networkOption.Description = "Select the network interface"; + networkOption.Required = true; + networkOption.DefaultValueFactory = _ => "default"; + Add(networkOption); + + // option to specify sample delay + var delayOption = new Option("--time-delay", "-t"); + delayOption.Description = "Set the delay for sampling network speeds in seconds"; + delayOption.DefaultValueFactory = _ => _systemStats.Options.DefaultNetworkDelay; + delayOption.Validators.Add(result => + { + var value = result.GetValueOrDefault(); + if (value <= 0) + result.AddError("Delay must be greater than 0"); + }); + Add(delayOption); + + var unitOption = new Option("--unit", "-u"); + unitOption.Description = "Select the network unit for display"; + unitOption.DefaultValueFactory = _ => _systemStats.Options.DefaultNetworkUnit; + Add(unitOption); + + SetAction(async (parseResult, cancellationToken) => + { + var uploadIconValue = parseResult.GetValue(showUploadIconOption); + var downloadIconValue = parseResult.GetValue(showDownloadIconOption); + var direction = parseResult.GetValue(directionOption); + var network = parseResult.GetValue(networkOption); + var delay = parseResult.GetValue(delayOption); + var unit = parseResult.GetValue(unitOption); + await ExecuteAsync( + parseResult.GetResult(showUploadIconOption) is not null, + uploadIconValue!, + parseResult.GetResult(showDownloadIconOption) is not null, + downloadIconValue!, + direction!, + network!, + delay!, + unit! + ); + }); + } + + public async Task ExecuteAsync( + bool showUploadIcon, + string uploadIconValue, + bool showDownloadIcon, + string downloadIconValue, + NetworkDirection direction, + string network, + int delay, + NetworkUnits unit) + { + + + var dataSpeed = await _systemStats.GetNetworkSpeed(unit, network, delay); + var commandOutputSb = new StringBuilder(); + + if (direction is NetworkDirection.Download or NetworkDirection.Both) + { + string downloadIcon = GetDirectionIcon(showDownloadIcon, downloadIconValue, NetworkDirection.Download); + commandOutputSb.Append($"{downloadIcon}{dataSpeed.Download.Value:F2}{dataSpeed.Download.UnitToString()}"); + } + if (direction is NetworkDirection.Both) + commandOutputSb.Append(" "); + if (direction is NetworkDirection.Upload or NetworkDirection.Both) + { + string uploadIcon = GetDirectionIcon(showUploadIcon, uploadIconValue, NetworkDirection.Upload); + commandOutputSb.Append($"{uploadIcon}{dataSpeed.Upload.Value:F2}{dataSpeed.Upload.UnitToString()}"); + } + + System.Console.Write(commandOutputSb.ToString()); + } + + private string GetDirectionIcon(bool showIcon, string iconValue, NetworkDirection direction) { + if (showIcon) + { + var defaultIcon = direction == NetworkDirection.Upload + ? _icons.Options.NetworkUpload + : _icons.Options.NetworkDownload; + + return string.IsNullOrEmpty(iconValue) + ? $"{defaultIcon} " + : $"{iconValue} "; + } + return ""; + } +} diff --git a/stmx/Program.cs b/stmx/Program.cs index ea8ea52..a3788de 100644 --- a/stmx/Program.cs +++ b/stmx/Program.cs @@ -15,6 +15,7 @@ static async Task Main(string[] args) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // TODO: introduce a switch to load based on OS services.AddTransient(); diff --git a/stmx/Services/IIconService.cs b/stmx/Services/IIconService.cs index bbcef2b..110209f 100644 --- a/stmx/Services/IIconService.cs +++ b/stmx/Services/IIconService.cs @@ -4,5 +4,6 @@ public interface IIconService { Task GetBatteryCapacityIcon(int batteryCapacity); Task GetBatteryStatusIcon(int batteryStatus); + Task GetDirectionIcon(bool download); IconServiceOptions Options { get; } } diff --git a/stmx/Services/ISystemStatsService.cs b/stmx/Services/ISystemStatsService.cs index a6f39be..f55923c 100644 --- a/stmx/Services/ISystemStatsService.cs +++ b/stmx/Services/ISystemStatsService.cs @@ -8,6 +8,8 @@ public interface ISystemStatsService Task GetBatteryStatus(); Task GetMemoryUsageNumber(MemoryUnits unit); Task GetMemoryUsagePercent(); + Task<(NetworkSpeed Upload, NetworkSpeed Download)> GetNetworkSpeed( + NetworkUnits unit, string network, int delaySecs); Task GetCpuUsagePercent(); SystemStatsServiceOptions Options { get; } } diff --git a/stmx/Services/IconService.cs b/stmx/Services/IconService.cs index 9f29989..3288d7a 100644 --- a/stmx/Services/IconService.cs +++ b/stmx/Services/IconService.cs @@ -1,3 +1,5 @@ +using stmx.Utils; + namespace stmx.Services; class IconService : IIconService @@ -13,4 +15,11 @@ public Task GetBatteryCapacityIcon(int batteryCapacity) public Task GetBatteryStatusIcon(int batteryStatus) { return Task.FromResult(Options.BatteryStatusIcons[batteryStatus]); } + + public Task GetDirectionIcon(bool download) + { + return Task.FromResult( + (download) ? Options.NetworkDownload : Options.NetworkUpload + ); + } } diff --git a/stmx/Services/IconServiceOptions.cs b/stmx/Services/IconServiceOptions.cs index d893f61..a94576d 100644 --- a/stmx/Services/IconServiceOptions.cs +++ b/stmx/Services/IconServiceOptions.cs @@ -13,4 +13,6 @@ public class IconServiceOptions public string MemoryIcon { get; set; } = ""; public string CpuIcon { get; set; } = ""; public string PercentIcon { get; set; } = ""; + public string NetworkDownload { get; set; } = "󰜮"; + public string NetworkUpload { get; set; } = "󰜷"; } diff --git a/stmx/Services/LinuxSystemStatsService.cs b/stmx/Services/LinuxSystemStatsService.cs index 38ef0ce..023f190 100644 --- a/stmx/Services/LinuxSystemStatsService.cs +++ b/stmx/Services/LinuxSystemStatsService.cs @@ -119,6 +119,68 @@ public MemoryUsageData ReadMemoryUsageFromProcFile() return new MemoryUsageData(totalMemory, availableMemory, MemoryUnits.KiloBytes); } + public async Task<(NetworkSpeed Upload, NetworkSpeed Download)> GetNetworkSpeed( + NetworkUnits unit, string network, int delaySecs) + { + // take two samples + var dataOld = ReadNetworkSpeedFromSysFile(network); + await Task.Delay(delaySecs * 1000); + var dataNew = ReadNetworkSpeedFromSysFile(network); + + // compute speed in bits per second + // If the counter was reset to 0 for second sample + // set the diff to 0 using Math.Max() to avoid negative value + var uploadSpeed = new NetworkSpeed( + Math.Max(0, dataNew.Upload - dataOld.Upload) / delaySecs, + NetworkUnits.Bits + ); + var downloadSpeed = new NetworkSpeed( + Math.Max(0, dataNew.Download - dataOld.Download) / delaySecs, + NetworkUnits.Bits + ); + + // auto or explicit conversion + if (unit == NetworkUnits.Auto) + { + return (uploadSpeed.ConvertAuto(), downloadSpeed.ConvertAuto()); + } + else + { + return (uploadSpeed.ConvertTo(unit), downloadSpeed.ConvertTo(unit)); + } + } + + // Returns current values of tx and rx counters + public (long Upload, long Download) ReadNetworkSpeedFromSysFile(string network_interface = "wlo1") + { + if (network_interface == "default") + network_interface = GetDefaultNetworkInterface(); + + string basePath = $"/sys/class/net/{network_interface}"; + if (!_fileSystem.DirectoryExists(basePath)) + throw new IOException($"No entry found for network {network_interface}"); + + var upload = long.Parse(_fileReader.ReadAllText( + $"/sys/class/net/{network_interface}/statistics/tx_bytes")) * 8; + var download = long.Parse(_fileReader.ReadAllText( + $"/sys/class/net/{network_interface}/statistics/rx_bytes")) * 8; + + return (upload, download); + } + + public string GetDefaultNetworkInterface() + { + var lines = _fileReader.ReadAllText("/proc/net/route").Split('\n', StringSplitOptions.RemoveEmptyEntries); + // skip header line, find route with destination 00000000 (default) + foreach (var line in lines.Skip(1)) + { + var fields = line.Split('\t'); + if (fields.Length > 1 && fields[1] == "00000000") + return fields[0]; + } + throw new IOException("No default network interface found"); + } + public async Task GetCpuUsagePercent() { int delayMs = 100; @@ -249,3 +311,74 @@ private static double UnitToBytesFactor(MemoryUnits unit) }; } } + +public class NetworkSpeed +{ + public double Value { set; get; } + public NetworkUnits Unit { set; get; } + + public NetworkSpeed(double value, NetworkUnits unit) + { + Value = value; + Unit = unit; + } + + public NetworkSpeed ConvertTo(NetworkUnits targetUnit) + { + return new NetworkSpeed( + ConvertValue(Value, Unit, targetUnit), + targetUnit + ); + } + + // Assumes that upload and download are in bits + public NetworkSpeed ConvertAuto() + { + NetworkUnits targetUnit = Value switch + { + >= 1_099_511_627_776 => NetworkUnits.TeraBits, + >= 1_073_741_824 => NetworkUnits.GigaBits, + >= 1_048_576 => NetworkUnits.MegaBits, + _ => NetworkUnits.KiloBits + // not including bits per second with Auto option + }; + + return ConvertTo(targetUnit); + } + + private static double ConvertValue(double value, NetworkUnits from, NetworkUnits to) + { + double bits = value * UnitToBitsFactor(from); + double result = bits / UnitToBitsFactor(to); + + return result; + } + + private static double UnitToBitsFactor(NetworkUnits unit) + { + return unit switch + { + NetworkUnits.Bits => 1, + NetworkUnits.KiloBits => 1_024, + NetworkUnits.MegaBits => 1_048_576, + NetworkUnits.GigaBits => 1_073_741_824, + NetworkUnits.TeraBits => 1_099_511_627_776, + + _ => throw new ArgumentOutOfRangeException(nameof(unit)) + }; + } + + public string UnitToString() + { + return Unit switch + { + NetworkUnits.Bits => "b", + NetworkUnits.KiloBits => "k", + NetworkUnits.MegaBits => "m", + NetworkUnits.GigaBits => "g", + NetworkUnits.TeraBits => "t", + + _ => throw new ArgumentOutOfRangeException(nameof(Unit)) + }; + } +} diff --git a/stmx/Services/SystemStatsServiceOptions.cs b/stmx/Services/SystemStatsServiceOptions.cs index 03750cb..4f8d205 100644 --- a/stmx/Services/SystemStatsServiceOptions.cs +++ b/stmx/Services/SystemStatsServiceOptions.cs @@ -8,5 +8,8 @@ public class SystemStatsServiceOptions public bool DefaultShowBatteryChargingIcon { get; set; } = false; public bool DefaultShowBatteryPercent { get; set; } = false; - public MemoryUnits DefaultMemoryUnit {get; set; } = MemoryUnits.Percent; + public MemoryUnits DefaultMemoryUnit { get; set; } = MemoryUnits.Percent; + public NetworkUnits DefaultNetworkUnit { get; set; } = NetworkUnits.Auto; + public int DefaultNetworkDelay { get; set; } = 3; + public NetworkDirection DefaultNetworkDirection { get; set; } = NetworkDirection.Both; } diff --git a/stmx/Utils/Enums.cs b/stmx/Utils/Enums.cs index 724c40a..0b518f3 100644 --- a/stmx/Utils/Enums.cs +++ b/stmx/Utils/Enums.cs @@ -13,3 +13,20 @@ public enum MemoryUnits TeraBytes = 8, TibiBytes = 9 } + +public enum NetworkDirection +{ + Both = 0, + Upload = 1, + Download = 2 +} + +public enum NetworkUnits +{ + Auto = 0, + Bits = 1, + KiloBits = 2, + MegaBits = 3, + GigaBits = 4, + TeraBits = 5 +}