02a8ba9c18
Stopping a test shuts down the control socket, so iperf_run_client() returns -1 with "unable to send control message ... Broken pipe". That is the expected consequence of the stop, not a fault, but the bridge read the negative return as a failure — so pressing STOP left the UI showing "Error (exit 1)" above a broken-pipe message. The bridge now consults its own g_stop flag before classifying the outcome, and the distinction travels up as Iperf3Result.stopped so the status line can say "Stopped". A stop is reported with exit code 0: the run ended the way the user asked it to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
3.1 KiB
Dart
110 lines
3.1 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:iperf3gui/data/services/iperf3_service.dart';
|
|
import 'package:iperf3gui/domain/models/iperf3_result.dart';
|
|
import 'package:iperf3gui/domain/models/test_configuration.dart';
|
|
|
|
/// State of the test run.
|
|
enum TestRunState { idle, running, completed, error }
|
|
|
|
/// ViewModel for managing test run state.
|
|
///
|
|
/// Subscribes to [Iperf3Service.outputLines] so the transcript grows while the
|
|
/// test runs rather than appearing all at once at the end.
|
|
class TestRunViewModel extends ChangeNotifier {
|
|
final Iperf3Service iperf3Service;
|
|
|
|
TestRunViewModel({required this.iperf3Service}) {
|
|
_outputSubscription = iperf3Service.outputLines.listen(_onOutputLine);
|
|
}
|
|
|
|
StreamSubscription<String>? _outputSubscription;
|
|
|
|
TestRunState _state = TestRunState.idle;
|
|
TestRunState get state => _state;
|
|
|
|
Iperf3Result? _result;
|
|
Iperf3Result? get result => _result;
|
|
|
|
TestConfiguration? _currentConfig;
|
|
TestConfiguration? get currentConfig => _currentConfig;
|
|
|
|
final StringBuffer _liveOutput = StringBuffer();
|
|
|
|
/// Output accumulated so far. Grows line by line during a run, and holds the
|
|
/// finished transcript once the test completes.
|
|
String get output => _liveOutput.toString();
|
|
|
|
/// Whether a test is currently in flight.
|
|
bool get isRunning => _state == TestRunState.running;
|
|
|
|
/// Whether the most recent finished run ended because the user stopped it.
|
|
bool get wasStopped => _result?.stopped ?? false;
|
|
|
|
/// Start a test with the given configuration.
|
|
Future<void> runTest(TestConfiguration config) async {
|
|
if (_state == TestRunState.running) return;
|
|
|
|
if (!config.isValid) {
|
|
_liveOutput
|
|
..clear()
|
|
..writeln('Invalid configuration: check the server address and port.');
|
|
_state = TestRunState.error;
|
|
_result = null;
|
|
notifyListeners();
|
|
return;
|
|
}
|
|
|
|
_state = TestRunState.running;
|
|
_currentConfig = config;
|
|
_result = null;
|
|
_liveOutput.clear();
|
|
notifyListeners();
|
|
|
|
try {
|
|
final result = await iperf3Service.run(config);
|
|
_result = result;
|
|
// Prefer the streamed transcript — it is what the user watched build up.
|
|
// Fall back to the service's copy if nothing streamed through.
|
|
if (_liveOutput.isEmpty && result.rawOutput.isNotEmpty) {
|
|
_liveOutput.write(result.rawOutput);
|
|
}
|
|
_state =
|
|
result.exitCode == 0 ? TestRunState.completed : TestRunState.error;
|
|
} catch (e) {
|
|
_liveOutput.writeln('Test failed: $e');
|
|
_state = TestRunState.error;
|
|
} finally {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Request that a running test stop early.
|
|
Future<void> stopTest() async {
|
|
if (_state != TestRunState.running) return;
|
|
await iperf3Service.stop();
|
|
}
|
|
|
|
/// Reset to idle state.
|
|
void reset() {
|
|
_state = TestRunState.idle;
|
|
_result = null;
|
|
_currentConfig = null;
|
|
_liveOutput.clear();
|
|
notifyListeners();
|
|
}
|
|
|
|
void _onOutputLine(String line) {
|
|
_liveOutput.write(line.endsWith('\n') ? line : '$line\n');
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_outputSubscription?.cancel();
|
|
iperf3Service.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|