dev
dart 177 lines 4.26 KB
Raw
1 import 'dart:convert';
2 import 'dart:io';
3 import 'dart:typed_data';
4
5 import 'package:cw_core/db/sqlite.dart';
6 import 'package:cw_core/root_dir.dart';
7 import 'package:flutter/foundation.dart';
8 import 'package:flutter/material.dart';
9
10 Future<void> handleSqliteError(final dynamic error, final StackTrace stackTrace) async {
11 try {
12 final f = await sqliteDebugMarkerFile();
13 f.createSync();
14 } catch (_) {}
15 runApp(
16 SqliteErrorHandlerApp(
17 error: error,
18 stackTrace: stackTrace,
19 ),
20 );
21 await Future.delayed(Duration(hours: 24));
22 }
23
24 class SqliteErrorHandlerApp extends StatelessWidget {
25 const SqliteErrorHandlerApp({
26 super.key,
27 required this.error,
28 required this.stackTrace,
29 });
30
31 final dynamic error;
32 final StackTrace? stackTrace;
33 @override
34 Widget build(final BuildContext context) {
35 return MaterialApp(
36 debugShowCheckedModeBanner: false,
37 title: 'Cake Wallet Crash',
38 themeMode: ThemeMode.light,
39 home: SqliteErrorHandler(
40 error: error,
41 stackTrace: stackTrace,
42 ),
43 );
44 }
45 }
46
47 class SqliteErrorHandler extends StatefulWidget {
48 SqliteErrorHandler({
49 super.key,
50 required this.error,
51 required this.stackTrace,
52 });
53
54 final dynamic error;
55 final StackTrace? stackTrace;
56
57 @override
58 State<SqliteErrorHandler> createState() => _SqliteErrorHandlerState();
59 }
60
61 class _SqliteErrorHandlerState extends State<SqliteErrorHandler> {
62 String log = "";
63
64 @override
65 Widget build(final BuildContext context) {
66 return Scaffold(
67 appBar: AppBar(
68 title: Text("Sqlite Recovery Screen"),
69 ),
70 body: SingleChildScrollView(
71 child: Padding(
72 padding: const EdgeInsets.all(8.0),
73 child: Column(
74 crossAxisAlignment: CrossAxisAlignment.start,
75 children: _buildNotice(),
76 ),
77 ),
78 ),
79 );
80 }
81
82 Widget _text(final dynamic element) {
83 return SelectableText(
84 element.toString(),
85 );
86 }
87
88 void _dbgRemoveDebugMarker() async {
89 final f = await sqliteDebugMarkerFile();
90 f.delete();
91 }
92
93 void _corruptTheDb() async {
94 final dbFile = File("${(await getAppDir()).path}/cake.db");
95 await writeBytesToFile(dbFile);
96 }
97
98 void _dumpDb() async {
99 try {
100 final dbFile = File("${(await getAppDir()).path}/cake.db");
101 final jsonText = JsonEncoder.withIndent(" ").convert(await dumpCustomDb(dbFile.path));
102 setState(() {
103 log = jsonText;
104 });
105 } catch (e, s) {
106 setState(() {
107 log = """
108 e: $e
109
110 s: $s
111 """;
112 });
113 }
114 }
115
116 void _removeDb() async {
117 try {
118 File("${(await getAppDir()).path}/cake.db.old").deleteSync();
119 } catch (_) {}
120 final dbFile = File("${(await getAppDir()).path}/cake.db");
121 dbFile.renameSync("${(await getAppDir()).path}/cake.db.old");
122 }
123
124 List<Widget> _debugOptions() {
125 return [
126 _debugButton("Remove debug marker", _dbgRemoveDebugMarker),
127 if (kDebugMode || kProfileMode) _debugButton("[DO NOT PRESS] Corrupt the DB", _corruptTheDb),
128 _debugButton("Remove DB", _removeDb),
129 _debugButton("Dump DB", _dumpDb),
130 ];
131 }
132
133 Widget _debugButton(String text, VoidCallback callback) {
134 return ElevatedButton(
135 onPressed: callback,
136 child: Text(text),
137 );
138 }
139
140 List<Widget> _buildNotice() {
141 return [
142 _text("""Critical error occured in SQLite
143 This is very rare, usually caused by the device killing the app before it finishes writing file.
144 Here you can recover the database or recreate it to regain access to your wallets.
145 During this process some of the metadata may get lost.
146 Please contact support@cakewallet.com if you need any help."""),
147 Divider(),
148 ..._debugOptions(),
149 if (log.isNotEmpty) ...[
150 Divider(),
151 SelectableText(
152 log,
153 style: TextStyle(
154 fontSize: 8,
155 ),
156 ),
157 ],
158 Divider(),
159 _text(widget.error),
160 Divider(),
161 _text(widget.stackTrace),
162 ];
163 }
164 }
165
166 Future<void> writeBytesToFile(File file) async {
167 const int seek = 5;
168 const int count = 50000;
169 final raf = await file.open(mode: FileMode.writeOnlyAppend);
170 try {
171 await raf.setPosition(seek);
172 final zeros = Uint8List.fromList(List.generate(count, (_) => 0x42));
173 await raf.writeFrom(zeros);
174 } finally {
175 await raf.close();
176 }
177 }