Skip to content
This repository was archived by the owner on Apr 3, 2025. It is now read-only.

Commit c062b3d

Browse files
committed
feat: launch recovery mode on bootstrap failure
1 parent a9d551d commit c062b3d

File tree

11 files changed

+234
-66
lines changed

11 files changed

+234
-66
lines changed

lib/database/config.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class LunaConfig {
2121
LunaSeaDatabase.ENABLED_PROFILE.update(LunaProfile.list[0]);
2222
}
2323
} catch (error, stack) {
24-
await LunaDatabase().bootstrap(databaseCorruption: true);
24+
await LunaDatabase().bootstrap();
2525
LunaLogger().error(
2626
'Failed to import configuration, resetting to default',
2727
error,

lib/database/database.dart

+3-20
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import 'package:lunasea/database/box.dart';
22
import 'package:lunasea/database/models/profile.dart';
33
import 'package:lunasea/database/table.dart';
4-
import 'package:lunasea/database/tables/bios.dart';
54
import 'package:lunasea/database/tables/lunasea.dart';
65
import 'package:lunasea/system/filesystem/filesystem.dart';
7-
import 'package:lunasea/system/logger.dart';
86
import 'package:lunasea/system/platform.dart';
97
import 'package:lunasea/vendor.dart';
108

@@ -24,20 +22,8 @@ class LunaDatabase {
2422
}
2523

2624
Future<void> open() async {
27-
try {
28-
await LunaBox.open();
29-
if (LunaBox.profiles.isEmpty) await bootstrap();
30-
} catch (error, stack) {
31-
await nuke();
32-
await LunaBox.open();
33-
await bootstrap(databaseCorruption: true);
34-
35-
LunaLogger().error(
36-
'Database corruption detected',
37-
error,
38-
stack,
39-
);
40-
}
25+
await LunaBox.open();
26+
if (LunaBox.profiles.isEmpty) await bootstrap();
4127
}
4228

4329
Future<void> nuke() async {
@@ -52,15 +38,12 @@ class LunaDatabase {
5238
}
5339
}
5440

55-
Future<void> bootstrap({
56-
bool databaseCorruption = false,
57-
}) async {
41+
Future<void> bootstrap() async {
5842
const defaultProfile = LunaProfile.DEFAULT_PROFILE;
5943
await clear();
6044

6145
LunaBox.profiles.update(defaultProfile, LunaProfile());
6246
LunaSeaDatabase.ENABLED_PROFILE.update(defaultProfile);
63-
BIOSDatabase.DATABASE_CORRUPTION.update(databaseCorruption);
6447
}
6548

6649
Future<void> clear() async {

lib/database/tables/bios.dart

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:lunasea/database/table.dart';
22

33
enum BIOSDatabase<T> with LunaTableMixin<T> {
4-
DATABASE_CORRUPTION<bool>(false),
54
SENTRY_LOGGING<bool>(true),
65
FIRST_BOOT<bool>(true);
76

lib/main.dart

+25-16
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,46 @@ import 'package:lunasea/system/cache/memory/memory_store.dart';
1111
import 'package:lunasea/system/in_app_purchase/in_app_purchase.dart';
1212
import 'package:lunasea/system/localization.dart';
1313
import 'package:lunasea/system/network/network.dart';
14+
import 'package:lunasea/system/recovery_mode/main.dart';
1415
import 'package:lunasea/system/sentry.dart';
1516
import 'package:lunasea/system/window_manager/window_manager.dart';
1617
import 'package:lunasea/system/platform.dart';
1718

18-
/// LunaSea Entry Point: Initialize & Run Application
19+
/// LunaSea Entry Point: Bootstrap & Run Application
1920
///
2021
/// Runs app in guarded zone to attempt to capture fatal (crashing) errors
2122
Future<void> main() async {
2223
WidgetsFlutterBinding.ensureInitialized();
2324
runZonedGuarded(
2425
() async {
25-
//LunaSea initialization
26-
await LunaSentry().initialize();
27-
await LunaDatabase().initialize();
28-
LunaLogger().initialize();
29-
if (LunaFirebase.isSupported) await LunaFirebase().initialize();
30-
LunaTheme().initialize();
31-
if (LunaWindowManager.isSupported) await LunaWindowManager().initialize();
32-
if (LunaNetwork.isSupported) LunaNetwork().initialize();
33-
if (LunaImageCache.isSupported) LunaImageCache().initialize();
34-
LunaRouter().initialize();
35-
if (LunaInAppPurchase.isSupported) LunaInAppPurchase().initialize();
36-
await LunaLocalization().initialize();
37-
await LunaMemoryStore().initialize();
38-
// Run application
39-
return runApp(const LunaBIOS());
26+
try {
27+
await bootstrap();
28+
runApp(const LunaBIOS());
29+
} catch (error) {
30+
runApp(const LunaRecoveryMode());
31+
}
4032
},
4133
(error, stack) => LunaLogger().critical(error, stack),
4234
);
4335
}
4436

37+
/// Bootstrap the core
38+
///
39+
Future<void> bootstrap() async {
40+
await LunaSentry().initialize();
41+
await LunaDatabase().initialize();
42+
LunaLogger().initialize();
43+
if (LunaFirebase.isSupported) await LunaFirebase().initialize();
44+
LunaTheme().initialize();
45+
if (LunaWindowManager.isSupported) await LunaWindowManager().initialize();
46+
if (LunaNetwork.isSupported) LunaNetwork().initialize();
47+
if (LunaImageCache.isSupported) LunaImageCache().initialize();
48+
LunaRouter().initialize();
49+
if (LunaInAppPurchase.isSupported) LunaInAppPurchase().initialize();
50+
await LunaLocalization().initialize();
51+
await LunaMemoryStore().initialize();
52+
}
53+
4554
class LunaBIOS extends StatelessWidget {
4655
const LunaBIOS({
4756
Key? key,

lib/router/router.dart

-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'package:flutter/foundation.dart';
21
import 'package:lunasea/system/logger.dart';
32
import 'package:lunasea/system/sentry.dart';
43
import 'package:lunasea/widgets/pages/error_route.dart';
@@ -10,7 +9,6 @@ class LunaRouter {
109

1110
void initialize() {
1211
router = GoRouter(
13-
debugLogDiagnostics: kDebugMode,
1412
errorBuilder: (_, state) => ErrorRoutePage(exception: state.error),
1513
initialLocation: LunaRoutes.initialLocation,
1614
observers: [LunaSentry().navigatorObserver],

lib/system/bios.dart

-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'package:lunasea/system/build.dart';
66
import 'package:lunasea/system/localization.dart';
77
import 'package:lunasea/system/quick_actions/quick_actions.dart';
88
import 'package:lunasea/widgets/sheets/changelog/sheet.dart';
9-
import 'package:lunasea/widgets/sheets/database_corruption/sheet.dart';
109

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

36-
if (BIOSDatabase.DATABASE_CORRUPTION.read()) {
37-
DatabaseCorruptionSheet().show();
38-
BIOSDatabase.DATABASE_CORRUPTION.update(false);
39-
}
40-
4135
if (!firstBoot && !isLatest.item1) {
4236
ChangelogSheet().show();
4337
}
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:hive_flutter/hive_flutter.dart';
3+
4+
abstract class RecoveryActionTile extends StatelessWidget {
5+
final String title;
6+
final String description;
7+
final bool showConfirmDialog;
8+
9+
const RecoveryActionTile({
10+
required this.title,
11+
required this.description,
12+
this.showConfirmDialog = false,
13+
super.key,
14+
});
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return ListTile(
19+
title: Text(title),
20+
subtitle: Text(description),
21+
onTap: () async => _action(context),
22+
);
23+
}
24+
25+
Future<void> _action(BuildContext context) async {
26+
// ignore: invalid_use_of_visible_for_testing_member
27+
Hive.resetAdapters();
28+
await Hive.close();
29+
30+
try {
31+
if (showConfirmDialog) {
32+
final execute = await confirm(context);
33+
if (execute) {
34+
await action(context);
35+
success(context);
36+
}
37+
} else {
38+
await action(context);
39+
success(context);
40+
}
41+
} catch (error, stack) {
42+
failure(context, error, stack);
43+
}
44+
}
45+
46+
Future<void> action(BuildContext context);
47+
48+
void success(BuildContext context) {
49+
showDialog(
50+
context: context,
51+
builder: (context) => const SimpleDialog(
52+
title: Text('Success'),
53+
children: [
54+
Text('Action Completed Successfully'),
55+
],
56+
contentPadding: EdgeInsets.only(
57+
left: 24.0,
58+
right: 24.0,
59+
top: 12.0,
60+
bottom: 24.0,
61+
),
62+
),
63+
);
64+
}
65+
66+
void failure(BuildContext context, dynamic error, StackTrace stack) {
67+
showDialog(
68+
context: context,
69+
builder: (context) => SimpleDialog(
70+
title: const Text('Error'),
71+
children: [
72+
Text(error.toString()),
73+
Text(stack.toString()),
74+
],
75+
contentPadding: const EdgeInsets.symmetric(
76+
horizontal: 24.0,
77+
vertical: 12.0,
78+
),
79+
),
80+
);
81+
}
82+
83+
Future<bool> confirm(BuildContext context) async {
84+
bool result = false;
85+
86+
await showDialog(
87+
context: context,
88+
builder: (context) => AlertDialog(
89+
title: const Text('Are You Sure?'),
90+
content: const Text('Are you sure you want to do this action?'),
91+
actions: [
92+
TextButton(
93+
onPressed: () {
94+
result = true;
95+
Navigator.of(context).pop();
96+
},
97+
child: const Text('Confirm'),
98+
)
99+
],
100+
),
101+
);
102+
103+
return result;
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:lunasea/main.dart';
3+
import 'package:lunasea/system/recovery_mode/action_tile.dart';
4+
5+
class BootstrapTile extends RecoveryActionTile {
6+
const BootstrapTile({
7+
super.key,
8+
}) : super(
9+
title: 'Bootstrap LunaSea',
10+
description: 'Run the bootstrap process and show any errors',
11+
);
12+
13+
@override
14+
Future<void> action(BuildContext context) async {
15+
await bootstrap();
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:lunasea/database/database.dart';
3+
import 'package:lunasea/system/recovery_mode/action_tile.dart';
4+
5+
class ClearDatabaseTile extends RecoveryActionTile {
6+
const ClearDatabaseTile({
7+
super.key,
8+
}) : super(
9+
title: 'Clear Database',
10+
description: 'Clear all configured settings and modules',
11+
showConfirmDialog: true,
12+
);
13+
14+
@override
15+
Future<void> action(BuildContext context) async {
16+
await LunaDatabase().nuke();
17+
}
18+
}

lib/system/recovery_mode/main.dart

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:lunasea/system/recovery_mode/actions/clear_database.dart';
3+
import 'package:lunasea/system/recovery_mode/actions/bootstrap.dart';
4+
5+
class LunaRecoveryMode extends StatelessWidget {
6+
const LunaRecoveryMode({
7+
super.key,
8+
});
9+
10+
@override
11+
Widget build(BuildContext context) {
12+
return MaterialApp(
13+
home: _home(),
14+
darkTheme: ThemeData(brightness: Brightness.dark),
15+
themeMode: ThemeMode.dark,
16+
);
17+
}
18+
19+
Widget _home() {
20+
return Scaffold(
21+
appBar: _appBar(),
22+
body: _body(),
23+
);
24+
}
25+
26+
PreferredSizeWidget _appBar() {
27+
return AppBar(
28+
title: const Text('Recovery Mode'),
29+
);
30+
}
31+
32+
Widget _motd() {
33+
return const Padding(
34+
child: Center(
35+
child: Text(
36+
'To exit please fully close and restart the application.',
37+
style: TextStyle(
38+
color: Colors.red,
39+
fontWeight: FontWeight.w600,
40+
),
41+
),
42+
),
43+
padding: EdgeInsets.all(12.0),
44+
);
45+
}
46+
47+
Widget _body() {
48+
const actions = [
49+
BootstrapTile(),
50+
ClearDatabaseTile(),
51+
];
52+
53+
return ListView.separated(
54+
itemCount: actions.length + 1,
55+
itemBuilder: (context, idx) {
56+
if (idx == 0) return _motd();
57+
return actions[idx - 1];
58+
},
59+
separatorBuilder: (context, idx) {
60+
if (idx == 0) return const SizedBox();
61+
return const Divider();
62+
},
63+
);
64+
}
65+
}

lib/widgets/sheets/database_corruption/sheet.dart

-20
This file was deleted.

0 commit comments

Comments
 (0)