Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gui): Smooth startup Part II #373

Merged
merged 12 commits into from
Oct 31, 2023
3 changes: 1 addition & 2 deletions gui/packages/ubuntupro/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"agentStateCannotStart": "Ubuntu Pro background agent cannot be started.",
"agentStateUnknownEnv": "Ubuntu Pro background agent state cannot be verified. Check your environment settings.",
"agentStateQuerying": "Checking Ubuntu Pro background agent's state.",
"agentStateUnreachable": "Ubuntu Pro background agent is unreachable.",
"agentRetryButton": "Click to restart it",
"agentStateUnreachable": "Attempting to recover Ubuntu Pro background agent.",

"proHeading": "The most comprehensive subscription\nfor open-source software security\nnow available on WSL",
"subscribeNow": "Subscribe Now",
Expand Down
2 changes: 2 additions & 0 deletions gui/packages/ubuntupro/lib/launch_agent.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import 'core/environment.dart';
Future<bool> launchAgent(String agentRelativePath) async {
final agentPath = p.join(msixRootDir().path, agentRelativePath);
try {
// Attempts to kill a possibly stuck agent. Failure is desirable in this case.
await Process.run('taskkill.exe', ['/f', '/im', p.basename(agentPath)]);
await Process.start(
agentPath,
['-vv'],
Expand Down
10 changes: 9 additions & 1 deletion gui/packages/ubuntupro/lib/pages/startup/agent_monitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,15 @@ class AgentStartupMonitor {
/// Thus, we delete the existing `addr` file and retry launching the agent.
Future<void> reset() async {
if (_addrFilePath != null) {
await File(_addrFilePath!).delete();
try {
await File(_addrFilePath!).delete();
} on PathNotFoundException {
// TODO: Log
// ignore: avoid_print
print(
'Port file expected but not found. Likely a race with the agent at this point, not an issue.',
);
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions gui/packages/ubuntupro/lib/pages/startup/startup_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class StartupModel extends ChangeNotifier {
ViewState get view => _view;

StreamSubscription<AgentState>? _subs;
static const _retryLimit = 5;
int _retries = 0;

/// Starts the monitor and subscribes to its events. Returns a future that
/// completes when the agent monitor startup routine completes.
Expand All @@ -67,6 +69,12 @@ class StartupModel extends ChangeNotifier {
_view == ViewState.retry,
"resetAgent only if it's possible to retry",
);
if (_retries >= _retryLimit) {
_view = ViewState.crash;
notifyListeners();
return;
}
++_retries;
await monitor.reset();
await _subs?.cancel();
return init();
Expand Down
17 changes: 6 additions & 11 deletions gui/packages/ubuntupro/lib/pages/startup/startup_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,30 +50,25 @@ class _StartupAnimatedChildState extends State<StartupAnimatedChild> {
super.initState();
final model = context.read<StartupModel>();
model.init();
model.addListener(() {
model.addListener(() async {
if (model.view == ViewState.ok) {
Navigator.of(context).pushReplacementNamed(widget.nextRoute);
await Navigator.of(context).pushReplacementNamed(widget.nextRoute);
}
if (model.view == ViewState.retry) {
await model.resetAgent();
}
});
}

Widget buildChild(ViewState view, String message) {
switch (view) {
case ViewState.inProgress:
case ViewState.retry:
return StartupInProgressWidget(message);

case ViewState.ok:
return const SizedBox.shrink();

case ViewState.retry:
return StartupRetryWidget(
message: message,
retry: OutlinedButton(
onPressed: context.read<StartupModel>().resetAgent,
child: Text(AppLocalizations.of(context).agentRetryButton),
),
);

case ViewState.crash:
return StartupErrorWidget(message);
}
Expand Down
24 changes: 0 additions & 24 deletions gui/packages/ubuntupro/lib/pages/startup/startup_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class StartupInProgressWidget extends StatelessWidget {
message: message,
bottom: const LinearProgressIndicator(),
),
showTitleBar: false,
);
}
}
Expand All @@ -78,26 +77,3 @@ class StartupErrorWidget extends StatelessWidget {
);
}
}

/// Displays an error icon followed by the [errorMessage] and a button allowing
/// users to manually request a reset/retry operation.
class StartupRetryWidget extends StatelessWidget {
const StartupRetryWidget({
super.key,
required this.message,
required this.retry,
});
final String message;
final Widget retry;

@override
Widget build(BuildContext context) {
return Pro4WindowsPage(
body: StatusColumn(
top: const Icon(Icons.error_outline, size: 64),
message: message,
bottom: retry,
),
);
}
}
27 changes: 0 additions & 27 deletions gui/packages/ubuntupro/test/startup/startup_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,6 @@ void main() {
expect(find.text(lastText), findsOneWidget);
});

testWidgets('button for retry', (tester) async {
final monitor = MockAgentStartupMonitor();
when(monitor.start()).thenAnswer(
(_) => Stream.fromIterable(
[
AgentState.querying,
AgentState.starting,
AgentState.invalid,
AgentState.unreachable,
],
),
);
final model = StartupModel(monitor);
await tester.pumpWidget(buildApp(model));

await model.init();
await tester.pumpAndSettle();

// no success
expect(find.text(lastText), findsNothing);
// show error icon
expect(find.byIcon(Icons.error_outline), findsOneWidget);
// and a retry button
expect(find.byType(OutlinedButton), findsOneWidget);
});

testWidgets('terminal error no button', (tester) async {
final monitor = MockAgentStartupMonitor();
when(monitor.start()).thenAnswer(
Expand Down Expand Up @@ -134,7 +108,6 @@ void main() {
);

await tester.pumpWidget(app);
await tester.pumpAndSettle();

final context = tester.element(find.byType(StartupAnimatedChild));
final model = Provider.of<StartupModel>(context, listen: false);
Expand Down
32 changes: 0 additions & 32 deletions gui/packages/ubuntupro/test/startup/startup_widgets_test.dart

This file was deleted.

Loading