mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
dev: add recovery screen when sqlite db doesn't open (#3179)
* dev: add recovery screen when sqlite db doesn't open * Update cw_core/lib/db/sqlite_debug.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/db/sqlite_debug.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Database? db;
|
||||
|
||||
@@ -22,7 +24,25 @@ Future<void> _addColumnIfNotExists(
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> sqliteDebugMarkerFile() async {
|
||||
final appDir = await getAppDir();
|
||||
final dbDebugMarker = p.join(appDir.path, ".sqlite_db_debug");
|
||||
return File(dbDebugMarker);
|
||||
}
|
||||
|
||||
Future<void> initDb({String? pathOverride}) async {
|
||||
final dbDebugMarker = await sqliteDebugMarkerFile();
|
||||
try {
|
||||
if (dbDebugMarker.existsSync()) {
|
||||
throw Exception("Debug marker is present");
|
||||
}
|
||||
await _initDb(pathOverride: pathOverride);
|
||||
} catch (e, s) {
|
||||
await handleSqliteError(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initDb({String? pathOverride}) async {
|
||||
if (Platform.isLinux || Platform.isWindows) {
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<void> handleSqliteError(final dynamic error, final StackTrace stackTrace) async {
|
||||
try {
|
||||
final f = await sqliteDebugMarkerFile();
|
||||
f.createSync();
|
||||
} catch (_) {}
|
||||
runApp(
|
||||
SqliteErrorHandlerApp(
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
await Future.delayed(Duration(hours: 24));
|
||||
}
|
||||
|
||||
class SqliteErrorHandlerApp extends StatelessWidget {
|
||||
const SqliteErrorHandlerApp({
|
||||
super.key,
|
||||
required this.error,
|
||||
required this.stackTrace,
|
||||
});
|
||||
|
||||
final dynamic error;
|
||||
final StackTrace? stackTrace;
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Cake Wallet Crash',
|
||||
themeMode: ThemeMode.light,
|
||||
home: SqliteErrorHandler(
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SqliteErrorHandler extends StatefulWidget {
|
||||
SqliteErrorHandler({
|
||||
super.key,
|
||||
required this.error,
|
||||
required this.stackTrace,
|
||||
});
|
||||
|
||||
final dynamic error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
@override
|
||||
State<SqliteErrorHandler> createState() => _SqliteErrorHandlerState();
|
||||
}
|
||||
|
||||
class _SqliteErrorHandlerState extends State<SqliteErrorHandler> {
|
||||
String log = "";
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Sqlite Recovery Screen"),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildNotice(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _text(final dynamic element) {
|
||||
return SelectableText(
|
||||
element.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
void _dbgRemoveDebugMarker() async {
|
||||
final f = await sqliteDebugMarkerFile();
|
||||
f.delete();
|
||||
}
|
||||
|
||||
void _corruptTheDb() async {
|
||||
final dbFile = File("${(await getAppDir()).path}/cake.db");
|
||||
await writeBytesToFile(dbFile);
|
||||
}
|
||||
|
||||
void _dumpDb() async {
|
||||
try {
|
||||
final dbFile = File("${(await getAppDir()).path}/cake.db");
|
||||
final jsonText = JsonEncoder.withIndent(" ").convert(await dumpCustomDb(dbFile.path));
|
||||
setState(() {
|
||||
log = jsonText;
|
||||
});
|
||||
} catch (e, s) {
|
||||
setState(() {
|
||||
log = """
|
||||
e: $e
|
||||
|
||||
s: $s
|
||||
""";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _removeDb() async {
|
||||
try {
|
||||
File("${(await getAppDir()).path}/cake.db.old").deleteSync();
|
||||
} catch (_) {}
|
||||
final dbFile = File("${(await getAppDir()).path}/cake.db");
|
||||
dbFile.renameSync("${(await getAppDir()).path}/cake.db.old");
|
||||
}
|
||||
|
||||
List<Widget> _debugOptions() {
|
||||
return [
|
||||
_debugButton("Remove debug marker", _dbgRemoveDebugMarker),
|
||||
if (kDebugMode || kProfileMode) _debugButton("[DO NOT PRESS] Corrupt the DB", _corruptTheDb),
|
||||
_debugButton("Remove DB", _removeDb),
|
||||
_debugButton("Dump DB", _dumpDb),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _debugButton(String text, VoidCallback callback) {
|
||||
return ElevatedButton(
|
||||
onPressed: callback,
|
||||
child: Text(text),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildNotice() {
|
||||
return [
|
||||
_text("""Critical error occured in SQLite
|
||||
This is very rare, usually caused by the device killing the app before it finishes writing file.
|
||||
Here you can recover the database or recreate it to regain access to your wallets.
|
||||
During this process some of the metadata may get lost.
|
||||
Please contact support@cakewallet.com if you need any help."""),
|
||||
Divider(),
|
||||
..._debugOptions(),
|
||||
if (log.isNotEmpty) ...[
|
||||
Divider(),
|
||||
SelectableText(
|
||||
log,
|
||||
style: TextStyle(
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
],
|
||||
Divider(),
|
||||
_text(widget.error),
|
||||
Divider(),
|
||||
_text(widget.stackTrace),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> writeBytesToFile(File file) async {
|
||||
const int seek = 5;
|
||||
const int count = 50000;
|
||||
final raf = await file.open(mode: FileMode.writeOnlyAppend);
|
||||
try {
|
||||
await raf.setPosition(seek);
|
||||
final zeros = Uint8List.fromList(List.generate(count, (_) => 0x42));
|
||||
await raf.writeFrom(zeros);
|
||||
} finally {
|
||||
await raf.close();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
|
||||
@@ -14,11 +16,13 @@ import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class OtherSettingsPage extends BasePage {
|
||||
OtherSettingsPage(this._otherSettingsViewModel) {
|
||||
@@ -211,6 +215,14 @@ class OtherSettingsPage extends BasePage {
|
||||
),
|
||||
);
|
||||
}),
|
||||
ListItemRegularRow(
|
||||
keyValue: '[dev] fake corrupt sqlite db',
|
||||
label: '[dev] fake corrupt sqlite db',
|
||||
onTap: () async {
|
||||
final dbDebugMarker = await sqliteDebugMarkerFile();
|
||||
dbDebugMarker.create();
|
||||
}
|
||||
),
|
||||
]
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user