Skip to content

Commit

Permalink
Merge pull request #1874 from nextcloud/refactor/neon_framework/maint…
Browse files Browse the repository at this point in the history
…enance-mode-bloc
  • Loading branch information
provokateurin authored Apr 6, 2024
2 parents 08a969b + bc56334 commit db83d25
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 29 deletions.
22 changes: 22 additions & 0 deletions packages/neon_framework/lib/src/blocs/accounts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:meta/meta.dart';
import 'package:neon_framework/src/bloc/bloc.dart';
import 'package:neon_framework/src/blocs/apps.dart';
import 'package:neon_framework/src/blocs/capabilities.dart';
import 'package:neon_framework/src/blocs/maintenance_mode.dart';
import 'package:neon_framework/src/blocs/unified_search.dart';
import 'package:neon_framework/src/blocs/user_details.dart';
import 'package:neon_framework/src/blocs/user_status.dart';
Expand Down Expand Up @@ -138,6 +139,16 @@ abstract interface class AccountsBloc implements Disposable {
///
/// Use [activeWeatherStatusBloc] to get them for the [activeAccount].
WeatherStatusBloc getWeatherStatusBlocFor(Account account);

/// The MaintenanceModeBloc for the [activeAccount].
///
/// Convenience method for [getMaintenanceModeBlocFor] with the currently active account.
MaintenanceModeBloc get activeMaintenanceModeBloc;

/// The MaintenanceModeBloc for the specified [account].
///
/// Use [activeMaintenanceModeBloc] to get them for the [activeAccount].
MaintenanceModeBloc getMaintenanceModeBlocFor(Account account);
}

/// Implementation of [AccountsBloc].
Expand Down Expand Up @@ -213,6 +224,7 @@ class _AccountsBloc extends Bloc implements AccountsBloc {
final userStatusBlocs = AccountCache<UserStatusBloc>();
final unifiedSearchBlocs = AccountCache<UnifiedSearchBloc>();
final weatherStatusBlocs = AccountCache<WeatherStatusBloc>();
final maintenanceModeBlocs = AccountCache<MaintenanceModeBloc>();

@override
void dispose() {
Expand All @@ -224,6 +236,7 @@ class _AccountsBloc extends Bloc implements AccountsBloc {
userStatusBlocs.dispose();
unifiedSearchBlocs.dispose();
weatherStatusBlocs.dispose();
maintenanceModeBlocs.dispose();
accountsOptions.dispose();
}

Expand Down Expand Up @@ -371,6 +384,15 @@ class _AccountsBloc extends Bloc implements AccountsBloc {
capabilities: getCapabilitiesBlocFor(account).capabilities,
account: account,
);

@override
MaintenanceModeBloc get activeMaintenanceModeBloc => getMaintenanceModeBlocFor(aa);

@override
MaintenanceModeBloc getMaintenanceModeBlocFor(Account account) =>
maintenanceModeBlocs[account] ??= MaintenanceModeBloc(
account: account,
);
}

/// Gets a list of logged in accounts from storage.
Expand Down
59 changes: 59 additions & 0 deletions packages/neon_framework/lib/src/blocs/maintenance_mode.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'dart:async';

import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/models.dart';
import 'package:nextcloud/core.dart';

/// A Bloc checking if the server is in maintenance mode.
sealed class MaintenanceModeBloc implements InteractiveBloc {
factory MaintenanceModeBloc({
required Account account,
}) = _MaintenanceModeBloc;

/// Emits an event if the server is in maintenance mode.
Stream<void> get onMaintenanceMode;
}

class _MaintenanceModeBloc extends InteractiveBloc implements MaintenanceModeBloc {
_MaintenanceModeBloc({
required this.account,
}) {
unawaited(refresh());
}

final Account account;
final controller = StreamController<void>();

@override
final log = Logger('MaintenanceModeBloc');

@override
void dispose() {
unawaited(controller.close());
super.dispose();
}

@override
late final onMaintenanceMode = controller.stream;

@override
Future<void> refresh() async {
try {
final status = await account.client.core.getStatus();

if (status.body.maintenance) {
controller.add(null);
}
} on http.ClientException catch (error, stackTrace) {
log.warning(
'Error getting the server status.',
error,
stackTrace,
);

addError(error);
}
}
}
46 changes: 17 additions & 29 deletions packages/neon_framework/lib/src/pages/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import 'dart:async';

import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:neon_framework/l10n/localizations.dart';
import 'package:neon_framework/src/bloc/result.dart';
Expand All @@ -16,13 +14,10 @@ import 'package:neon_framework/src/utils/global_popups.dart';
import 'package:neon_framework/src/utils/provider.dart';
import 'package:neon_framework/src/widgets/app_bar.dart';
import 'package:neon_framework/src/widgets/drawer.dart';
import 'package:neon_framework/src/widgets/error.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:neon_framework/widgets.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';

final _log = Logger('HomePage');

/// The home page of Neon.
@internal
class HomePage extends StatefulWidget {
Expand All @@ -43,13 +38,16 @@ class _HomePageState extends State<HomePage> {
late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc;
late StreamSubscription<BuiltMap<String, VersionCheck>> _versionCheckSubscription;
late StreamSubscription<Object> maintenanceModeErrorsSubscription;
late StreamSubscription<void> maintenanceModeSubscription;

@override
void initState() {
super.initState();
_globalOptions = NeonProvider.of<global_options.GlobalOptions>(context);
_accountsBloc = NeonProvider.of<AccountsBloc>(context);
_appsBloc = _accountsBloc.getAppsBlocFor(widget.account);
final maintenanceModeBloc = _accountsBloc.getMaintenanceModeBlocFor(widget.account);

_versionCheckSubscription = _appsBloc.appVersionChecks.listen((values) {
if (!mounted) {
Expand All @@ -70,39 +68,29 @@ class _HomePageState extends State<HomePage> {
unawaited(showErrorDialog(context: context, message: message));
});

GlobalPopups().register(context);
maintenanceModeErrorsSubscription = maintenanceModeBloc.errors.listen((error) {
NeonError.showSnackbar(context, error);
});

unawaited(_checkMaintenanceMode());
maintenanceModeSubscription = maintenanceModeBloc.onMaintenanceMode.listen((_) async {
await showErrorDialog(
context: context,
message: NeonLocalizations.of(context).errorServerInMaintenanceMode,
);
});

GlobalPopups().register(context);
}

@override
void dispose() {
unawaited(_versionCheckSubscription.cancel());
unawaited(maintenanceModeErrorsSubscription.cancel());
unawaited(maintenanceModeSubscription.cancel());
GlobalPopups().dispose();
super.dispose();
}

Future<void> _checkMaintenanceMode() async {
try {
final status = await widget.account.client.core.getStatus();

if (status.body.maintenance && mounted) {
final message = NeonLocalizations.of(context).errorServerInMaintenanceMode;
await showErrorDialog(context: context, message: message);
}
} on http.ClientException catch (error, stackTrace) {
_log.warning(
'Error getting the server status.',
error,
stackTrace,
);

if (mounted) {
NeonError.showSnackbar(context, error);
}
}
}

@override
Widget build(BuildContext context) {
const drawer = NeonDrawer();
Expand Down
57 changes: 57 additions & 0 deletions packages/neon_framework/test/maintenance_mode_bloc_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'dart:convert';

import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:mocktail/mocktail.dart';
import 'package:neon_framework/src/blocs/maintenance_mode.dart';
import 'package:neon_framework/src/models/account.dart';
import 'package:neon_framework/testing.dart';

Account mockMaintenanceModeAccount() {
return mockServer({
RegExp(r'/status\.php'): {
'get': (match, queryParameters) {
return Response(
json.encode({
'installed': false,
'maintenance': true,
'needsDbUpgrade': false,
'version': '',
'versionstring': '',
'edition': '',
'productname': '',
'extendedSupport': false,
}),
200,
);
},
},
});
}

void main() {
late Account account;
late MaintenanceModeBloc bloc;

setUpAll(() {
final storage = MockNeonStorage();
when(() => storage.requestCache).thenReturn(null);
});

setUp(() {
account = mockMaintenanceModeAccount();
bloc = MaintenanceModeBloc(
account: account,
);
});

tearDown(() {
bloc.dispose();
});

test('refresh', () async {
expect(bloc.onMaintenanceMode, emits(null));
await Future<void>.delayed(const Duration(milliseconds: 1));
await bloc.refresh();
});
}

0 comments on commit db83d25

Please sign in to comment.