import 'package:iperf3gui/domain/models/iperf3_event.dart'; /// Renders parsed iperf3 events as the kind of line iperf3 itself would print. /// /// The app consumes `--json-stream`, so the familiar text layout has to be /// rebuilt here. Keeping it in one pure function makes it testable and leaves /// the structured events untouched for anything else that wants them. String? formatIperf3Event(Iperf3Event event) { return switch (event) { Iperf3Start e => _formatStart(e), Iperf3Interval e => _formatInterval(e), Iperf3End e => _formatEnd(e), Iperf3Error e => 'iperf3 error: ${e.message}', Iperf3Stopped _ => 'Stopped by user.', Iperf3LogLine e => e.text.isEmpty ? null : e.text, }; } String _formatStart(Iperf3Start e) { final parts = []; if (e.targetHost != null) { final port = e.targetPort != null ? ', port ${e.targetPort}' : ''; parts.add('Connecting to host ${e.targetHost}$port'); } final detail = [ if (e.protocol != null) e.protocol!, if (e.streamCount != null) '${e.streamCount} stream${e.streamCount == 1 ? '' : 's'}', if (e.durationSeconds != null) '${e.durationSeconds}s', ]; if (detail.isNotEmpty) parts.add(detail.join(' · ')); return parts.isEmpty ? '' : parts.join('\n'); } String _formatInterval(Iperf3Interval e) { final window = '${_seconds(e.start)}-${_seconds(e.end)} sec'; final line = StringBuffer('[SUM] $window ' '${formatBytes(e.bytes)} ${formatBitrate(e.bitsPerSecond)}'); if (e.retransmits != null) { line.write(' ${e.retransmits} retr'); } if (e.omitted) line.write(' (omitted)'); return line.toString(); } String _formatEnd(Iperf3End e) { final lines = ['- ' * 20]; final sent = e.sent; final received = e.received; if (sent != null) { lines.add('[SUM] 0.00-${_seconds(sent.seconds)} sec ' '${formatBytes(sent.bytes)} ${formatBitrate(sent.bitsPerSecond)}' '${sent.retransmits != null ? ' ${sent.retransmits} retr' : ''}' ' sender'); } if (received != null) { lines.add('[SUM] 0.00-${_seconds(received.seconds)} sec ' '${formatBytes(received.bytes)} ' '${formatBitrate(received.bitsPerSecond)} receiver'); } if (e.hostCpuPercent != null) { lines.add('CPU: local ${e.hostCpuPercent!.toStringAsFixed(1)}%' '${e.remoteCpuPercent != null ? ', remote ${e.remoteCpuPercent!.toStringAsFixed(1)}%' : ''}'); } return lines.join('\n'); } String _seconds(double value) => value.toStringAsFixed(2); /// Byte totals the way iperf3 prints them — `93.8 MBytes`. String formatBytes(int bytes) { const units = ['Bytes', 'KBytes', 'MBytes', 'GBytes', 'TBytes']; var value = bytes.toDouble(); var unit = 0; while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit++; } final precision = value >= 100 ? 0 : (value >= 10 ? 1 : 2); return '${value.toStringAsFixed(precision)} ${units[unit]}'; } /// Bitrates the way iperf3 prints them — `786 Mbits/sec`. Decimal units, since /// network rates are powers of ten while transfer totals are powers of two. String formatBitrate(double bitsPerSecond) { const units = ['bits/sec', 'Kbits/sec', 'Mbits/sec', 'Gbits/sec']; var value = bitsPerSecond; var unit = 0; while (value >= 1000 && unit < units.length - 1) { value /= 1000; unit++; } final precision = value >= 100 ? 0 : (value >= 10 ? 1 : 2); return '${value.toStringAsFixed(precision)} ${units[unit]}'; }