Skip to content

Commit

Permalink
feat: launch recovery mode on bootstrap failure
Browse files Browse the repository at this point in the history
  • Loading branch information
JagandeepBrar committed Sep 23, 2022
1 parent a9d551d commit c062b3d
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 66 deletions.
2 changes: 1 addition & 1 deletion lib/database/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class LunaConfig {
LunaSeaDatabase.ENABLED_PROFILE.update(LunaProfile.list[0]);
}
} catch (error, stack) {
await LunaDatabase().bootstrap(databaseCorruption: true);
await LunaDatabase().bootstrap();
LunaLogger().error(
'Failed to import configuration, resetting to default',
error,
Expand Down
23 changes: 3 additions & 20 deletions lib/database/database.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import 'package:lunasea/database/box.dart';
import 'package:lunasea/database/models/profile.dart';
import 'package:lunasea/database/table.dart';
import 'package:lunasea/database/tables/bios.dart';
import 'package:lunasea/database/tables/lunasea.dart';
import 'package:lunasea/system/filesystem/filesystem.dart';
import 'package:lunasea/system/logger.dart';
import 'package:lunasea/system/platform.dart';
import 'package:lunasea/vendor.dart';

Expand All @@ -24,20 +22,8 @@ class LunaDatabase {
}

Future<void> open() async {
try {
await LunaBox.open();
if (LunaBox.profiles.isEmpty) await bootstrap();
} catch (error, stack) {
await nuke();
await LunaBox.open();
await bootstrap(databaseCorruption: true);

LunaLogger().error(
'Database corruption detected',
error,
stack,
);
}
await LunaBox.open();
if (LunaBox.profiles.isEmpty) await bootstrap();
}

Future<void> nuke() async {
Expand All @@ -52,15 +38,12 @@ class LunaDatabase {
}
}

Future<void> bootstrap({
bool databaseCorruption = false,
}) async {
Future<void> bootstrap() async {
const defaultProfile = LunaProfile.DEFAULT_PROFILE;
await clear();

LunaBox.profiles.update(defaultProfile, LunaProfile());
LunaSeaDatabase.ENABLED_PROFILE.update(defaultProfile);
BIOSDatabase.DATABASE_CORRUPTION.update(databaseCorruption);
}

Future<void> clear() async {
Expand Down
1 change: 0 additions & 1 deletion lib/database/tables/bios.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:lunasea/database/table.dart';

enum BIOSDatabase<T> with LunaTableMixin<T> {
DATABASE_CORRUPTION<bool>(false),
SENTRY_LOGGING<bool>(true),
FIRST_BOOT<bool>(true);

Expand Down
41 changes: 25 additions & 16 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,46 @@ import 'package:lunasea/system/cache/memory/memory_store.dart';
import 'package:lunasea/system/in_app_purchase/in_app_purchase.dart';
import 'package:lunasea/system/localization.dart';
import 'package:lunasea/system/network/network.dart';
import 'package:lunasea/system/recovery_mode/main.dart';
import 'package:lunasea/system/sentry.dart';
import 'package:lunasea/system/window_manager/window_manager.dart';
import 'package:lunasea/system/platform.dart';

/// LunaSea Entry Point: Initialize & Run Application
/// LunaSea Entry Point: Bootstrap & Run Application
///
/// Runs app in guarded zone to attempt to capture fatal (crashing) errors
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runZonedGuarded(
() async {
//LunaSea initialization
await LunaSentry().initialize();
await LunaDatabase().initialize();
LunaLogger().initialize();
if (LunaFirebase.isSupported) await LunaFirebase().initialize();
LunaTheme().initialize();
if (LunaWindowManager.isSupported) await LunaWindowManager().initialize();
if (LunaNetwork.isSupported) LunaNetwork().initialize();
if (LunaImageCache.isSupported) LunaImageCache().initialize();
LunaRouter().initialize();
if (LunaInAppPurchase.isSupported) LunaInAppPurchase().initialize();
await LunaLocalization().initialize();
await LunaMemoryStore().initialize();
// Run application
return runApp(const LunaBIOS());
try {
await bootstrap();
runApp(const LunaBIOS());
} catch (error) {
runApp(const LunaRecoveryMode());
}
},
(error, stack) => LunaLogger().critical(error, stack),
);
}

/// Bootstrap the core
///
Future<void> bootstrap() async {
await LunaSentry().initialize();
await LunaDatabase().initialize();
LunaLogger().initialize();
if (LunaFirebase.isSupported) await LunaFirebase().initialize();
LunaTheme().initialize();
if (LunaWindowManager.isSupported) await LunaWindowManager().initialize();
if (LunaNetwork.isSupported) LunaNetwork().initialize();
if (LunaImageCache.isSupported) LunaImageCache().initialize();
LunaRouter().initialize();
if (LunaInAppPurchase.isSupported) LunaInAppPurchase().initialize();
await LunaLocalization().initialize();
await LunaMemoryStore().initialize();
}

class LunaBIOS extends StatelessWidget {
const LunaBIOS({
Key? key,
Expand Down
2 changes: 0 additions & 2 deletions lib/router/router.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:lunasea/system/logger.dart';
import 'package:lunasea/system/sentry.dart';
import 'package:lunasea/widgets/pages/error_route.dart';
Expand All @@ -10,7 +9,6 @@ class LunaRouter {

void initialize() {
router = GoRouter(
debugLogDiagnostics: kDebugMode,
errorBuilder: (_, state) => ErrorRoutePage(exception: state.error),
initialLocation: LunaRoutes.initialLocation,
observers: [LunaSentry().navigatorObserver],
Expand Down
6 changes: 0 additions & 6 deletions lib/system/bios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:lunasea/system/build.dart';
import 'package:lunasea/system/localization.dart';
import 'package:lunasea/system/quick_actions/quick_actions.dart';
import 'package:lunasea/widgets/sheets/changelog/sheet.dart';
import 'package:lunasea/widgets/sheets/database_corruption/sheet.dart';

class LunaOS {
Future<void> boot(BuildContext context) async {
Expand All @@ -33,11 +32,6 @@ class LunaOS {
final isLatest = LunaBuild().isLatestBuildVersion();
final firstBoot = BIOSDatabase.FIRST_BOOT.read();

if (BIOSDatabase.DATABASE_CORRUPTION.read()) {
DatabaseCorruptionSheet().show();
BIOSDatabase.DATABASE_CORRUPTION.update(false);
}

if (!firstBoot && !isLatest.item1) {
ChangelogSheet().show();
}
Expand Down
105 changes: 105 additions & 0 deletions lib/system/recovery_mode/action_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';

abstract class RecoveryActionTile extends StatelessWidget {
final String title;
final String description;
final bool showConfirmDialog;

const RecoveryActionTile({
required this.title,
required this.description,
this.showConfirmDialog = false,
super.key,
});

@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
subtitle: Text(description),
onTap: () async => _action(context),
);
}

Future<void> _action(BuildContext context) async {
// ignore: invalid_use_of_visible_for_testing_member
Hive.resetAdapters();
await Hive.close();

try {
if (showConfirmDialog) {
final execute = await confirm(context);
if (execute) {
await action(context);
success(context);
}
} else {
await action(context);
success(context);
}
} catch (error, stack) {
failure(context, error, stack);
}
}

Future<void> action(BuildContext context);

void success(BuildContext context) {
showDialog(
context: context,
builder: (context) => const SimpleDialog(
title: Text('Success'),
children: [
Text('Action Completed Successfully'),
],
contentPadding: EdgeInsets.only(
left: 24.0,
right: 24.0,
top: 12.0,
bottom: 24.0,
),
),
);
}

void failure(BuildContext context, dynamic error, StackTrace stack) {
showDialog(
context: context,
builder: (context) => SimpleDialog(
title: const Text('Error'),
children: [
Text(error.toString()),
Text(stack.toString()),
],
contentPadding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 12.0,
),
),
);
}

Future<bool> confirm(BuildContext context) async {
bool result = false;

await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Are You Sure?'),
content: const Text('Are you sure you want to do this action?'),
actions: [
TextButton(
onPressed: () {
result = true;
Navigator.of(context).pop();
},
child: const Text('Confirm'),
)
],
),
);

return result;
}
}
17 changes: 17 additions & 0 deletions lib/system/recovery_mode/actions/bootstrap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:lunasea/main.dart';
import 'package:lunasea/system/recovery_mode/action_tile.dart';

class BootstrapTile extends RecoveryActionTile {
const BootstrapTile({
super.key,
}) : super(
title: 'Bootstrap LunaSea',
description: 'Run the bootstrap process and show any errors',
);

@override
Future<void> action(BuildContext context) async {
await bootstrap();
}
}
18 changes: 18 additions & 0 deletions lib/system/recovery_mode/actions/clear_database.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:lunasea/database/database.dart';
import 'package:lunasea/system/recovery_mode/action_tile.dart';

class ClearDatabaseTile extends RecoveryActionTile {
const ClearDatabaseTile({
super.key,
}) : super(
title: 'Clear Database',
description: 'Clear all configured settings and modules',
showConfirmDialog: true,
);

@override
Future<void> action(BuildContext context) async {
await LunaDatabase().nuke();
}
}
65 changes: 65 additions & 0 deletions lib/system/recovery_mode/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:lunasea/system/recovery_mode/actions/clear_database.dart';
import 'package:lunasea/system/recovery_mode/actions/bootstrap.dart';

class LunaRecoveryMode extends StatelessWidget {
const LunaRecoveryMode({
super.key,
});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: _home(),
darkTheme: ThemeData(brightness: Brightness.dark),
themeMode: ThemeMode.dark,
);
}

Widget _home() {
return Scaffold(
appBar: _appBar(),
body: _body(),
);
}

PreferredSizeWidget _appBar() {
return AppBar(
title: const Text('Recovery Mode'),
);
}

Widget _motd() {
return const Padding(
child: Center(
child: Text(
'To exit please fully close and restart the application.',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
padding: EdgeInsets.all(12.0),
);
}

Widget _body() {
const actions = [
BootstrapTile(),
ClearDatabaseTile(),
];

return ListView.separated(
itemCount: actions.length + 1,
itemBuilder: (context, idx) {
if (idx == 0) return _motd();
return actions[idx - 1];
},
separatorBuilder: (context, idx) {
if (idx == 0) return const SizedBox();
return const Divider();
},
);
}
}
20 changes: 0 additions & 20 deletions lib/widgets/sheets/database_corruption/sheet.dart

This file was deleted.

0 comments on commit c062b3d

Please sign in to comment.