Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
case BackupFrequencyType.afterClosingAWallet:
// ignore this case here
break;
case BackupFrequencyType.afterChanges:
ref.read(autoSWBServiceProvider);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
BackupFrequencyType.everyTenMinutes,
BackupFrequencyType.everyAppStart,
BackupFrequencyType.afterClosingAWallet,
BackupFrequencyType.afterChanges,
];

String passwordFeedback =
Expand Down Expand Up @@ -572,6 +573,9 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
case BackupFrequencyType.afterClosingAWallet:
message = "After closing a cryptocurrency wallet";
break;
case BackupFrequencyType.afterChanges:
message = "After editing a note or contact";
break;
}

return DropdownMenuItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class BackupFrequencyTypeSelectSheet extends ConsumerWidget {
return "Every app start";
case BackupFrequencyType.afterClosingAWallet:
return "After closing a cryptocurrency wallet";
case BackupFrequencyType.afterChanges:
return "After editing a note or contact";
}
}

Expand Down
14 changes: 11 additions & 3 deletions lib/pages/wallet_view/transaction_views/edit_note_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../models/isar/models/transaction_note.dart';
import '../../../providers/global/auto_swb_service_provider.dart';
import '../../../providers/providers.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/constants.dart';
Expand Down Expand Up @@ -89,7 +90,7 @@ class _EditNoteViewState extends ConsumerState<EditNoteView> {
const Duration(milliseconds: 75),
);
}
if (mounted) {
if (context.mounted) {
Navigator.of(context).pop();
}
},
Expand Down Expand Up @@ -198,7 +199,11 @@ class _EditNoteViewState extends ConsumerState<EditNoteView> {
),
);

if (mounted) {
ref
.read(autoSWBServiceProvider)
.requestBackupAfterChange();

if (context.mounted) {
Navigator.of(context).pop();
}
},
Expand All @@ -217,7 +222,10 @@ class _EditNoteViewState extends ConsumerState<EditNoteView> {
value: _noteController.text,
),
);
if (mounted) {

ref.read(autoSWBServiceProvider).requestBackupAfterChange();

if (context.mounted) {
Navigator.of(context).pop();
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
BackupFrequencyType.everyTenMinutes,
BackupFrequencyType.everyAppStart,
BackupFrequencyType.afterClosingAWallet,
BackupFrequencyType.afterChanges,
];

Future<void> _enableAutoBackup() async {
Expand Down Expand Up @@ -616,6 +617,9 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
case BackupFrequencyType.afterClosingAWallet:
message = "After closing a cryptocurrency wallet";
break;
case BackupFrequencyType.afterChanges:
message = "After editing a note or contact";
break;
}

return DropdownMenuItem(
Expand Down
11 changes: 9 additions & 2 deletions lib/providers/global/address_book_service_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
*/

import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../services/address_book_service.dart';
import 'auto_swb_service_provider.dart';

final addressBookServiceProvider =
ChangeNotifierProvider<AddressBookService>((ref) => AddressBookService());
final addressBookServiceProvider = ChangeNotifierProvider<AddressBookService>(
(ref) => AddressBookService(
requestAutoBackup: ref
.read(autoSWBServiceProvider)
.requestBackupAfterChange,
),
);
17 changes: 12 additions & 5 deletions lib/providers/global/auto_swb_service_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
*/

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'secure_store_provider.dart';

import '../../services/auto_swb_service.dart';
import '../../utilities/enums/backup_frequency_type.dart';
import 'prefs_provider.dart';
import 'secure_store_provider.dart';

final autoSWBServiceProvider = ChangeNotifierProvider<AutoSWBService>(
(ref) => AutoSWBService(
final autoSWBServiceProvider = ChangeNotifierProvider<AutoSWBService>((ref) {
final prefs = ref.read(prefsChangeNotifierProvider);
return AutoSWBService(
secureStorageInterface: ref.read(secureStoreProvider),
),
);
shouldBackupAfterChange: () =>
prefs.isAutoBackupEnabled &&
prefs.backupFrequencyType == BackupFrequencyType.afterChanges,
);
});
8 changes: 8 additions & 0 deletions lib/services/address_book_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';

import '../db/isar/main_db.dart';
import '../models/isar/models/contact_entry.dart';

class AddressBookService extends ChangeNotifier {
AddressBookService({this.requestAutoBackup});

final void Function()? requestAutoBackup;

ContactEntry getContactById(String id) {
final ContactEntry? contactEntry = MainDB.instance.getContactEntry(id: id);
if (contactEntry == null) {
Expand Down Expand Up @@ -66,6 +71,7 @@ class AddressBookService extends ChangeNotifier {
} else {
await MainDB.instance.putContactEntry(contactEntry: contact);
notifyListeners();
requestAutoBackup?.call();
return true;
}
}
Expand All @@ -75,12 +81,14 @@ class AddressBookService extends ChangeNotifier {
// over write the contact with edited version
await MainDB.instance.putContactEntry(contactEntry: editedContact);
notifyListeners();
requestAutoBackup?.call();
return true;
}

/// Remove address book contact entry from db if it exists
Future<void> removeContact(String id) async {
await MainDB.instance.deleteContactEntry(id: id);
notifyListeners();
requestAutoBackup?.call();
}
}
167 changes: 97 additions & 70 deletions lib/services/auto_swb_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ enum AutoSWBStatus { idle, backingUp, error }

class AutoSWBService extends ChangeNotifier {
Timer? _timer;
Timer? _debounceTimer;
bool _backupPending = false;
bool _isDisposed = false;

AutoSWBStatus _status = AutoSWBStatus.idle;
AutoSWBStatus get status => _status;
Expand All @@ -33,95 +36,115 @@ class AutoSWBService extends ChangeNotifier {
bool get isActivePeriodicTimer => _isActiveTimer;

final SecureStorageInterface secureStorageInterface;
final bool Function() shouldBackupAfterChange;
@visibleForTesting
final Future<void> Function()? backupRunner;
final Duration debounceDuration;

AutoSWBService({
required this.secureStorageInterface,
required this.shouldBackupAfterChange,
this.backupRunner,
this.debounceDuration = const Duration(seconds: 5),
});

void requestBackupAfterChange() {
if (_isDisposed || !shouldBackupAfterChange()) return;

Logging.instance.d("AutoSWBService.requestBackupAfterChange() triggered");
requestBackup();
}

AutoSWBService({required this.secureStorageInterface});
void requestBackup({Duration? debounceDuration}) {
if (_isDisposed) return;
_debounceTimer?.cancel();
_debounceTimer = Timer(debounceDuration ?? this.debounceDuration, () {
_debounceTimer = null;
Logging.instance.d(
"AutoSWBService.requestBackup() debounce fired, running doBackup()",
);
unawaited(doBackup());
});
}

/// Attempt a backup.
Future<void> doBackup() async {
if (_isDisposed) return;
if (_status == AutoSWBStatus.backingUp) {
Logging.instance.w(
"AutoSWBService attempted to run doBackup() while a backup is in progress!",
);
_backupPending = true;
return;
}
Logging.instance.d("AutoSWBService.doBackup() started...");

// set running backup status and notify listeners
_status = AutoSWBStatus.backingUp;
notifyListeners();

try {
if (!Prefs.instance.isInitialized) {
await Prefs.instance.init();
}

final autoBackupDirectoryPath = Prefs.instance.autoBackupLocation;
if (autoBackupDirectoryPath == null) {
Logging.instance.e(
"AutoSWBService attempted to run doBackup() when no auto backup directory was set!",
);
// set error backup status and notify listeners
_status = AutoSWBStatus.error;
notifyListeners();
return;
}

final json = await SWB.createStackWalletJSON(
secureStorage: secureStorageInterface,
);
final jsonString = jsonEncode(json);

final adkString = await secureStorageInterface.read(
key: "auto_adk_string",
);

final adkVersionString = await secureStorageInterface.read(
key: "auto_adk_version_string",
);
final int adkVersion = int.parse(adkVersionString!);

final DateTime now = DateTime.now();
final String fileToSave = createAutoBackupFilename(
autoBackupDirectoryPath,
now,
);

final content = await SWB.encryptStackWalletWithADK(
adkString!,
jsonString,
adkVersion,
);

await FS.writeStringToFile(
content,
autoBackupDirectoryPath,
fileToSave.split("/").last,
);

Prefs.instance.lastAutoBackup = now;
do {
_backupPending = false;
await _doBackupOnce();
} while (_backupPending && !_isDisposed);
}

// delete all but the latest 3 auto backups
trimBackups(autoBackupDirectoryPath, 3);
Future<void> _doBackupOnce() async {
Logging.instance.d("AutoSWBService.doBackup() started...");
_setStatus(AutoSWBStatus.backingUp);

try {
await (backupRunner?.call() ?? _writeBackup());
Logging.instance.d("AutoSWBService.doBackup() succeeded");
} on Exception catch (e, s) {
final String err = getErrorMessageFromSWBException(e);
Logging.instance.e("$err\n$s", error: e, stackTrace: s);
// set error backup status and notify listeners
_status = AutoSWBStatus.error;
notifyListeners();
_setStatus(AutoSWBStatus.error);
return;
} catch (e, s) {
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
// set error backup status and notify listeners
_status = AutoSWBStatus.error;
notifyListeners();
_setStatus(AutoSWBStatus.error);
return;
}

// set done/idle backup status and notify listeners
_status = AutoSWBStatus.idle;
notifyListeners();
_setStatus(AutoSWBStatus.idle);
}

Future<void> _writeBackup() async {
if (!Prefs.instance.isInitialized) {
await Prefs.instance.init();
}

final autoBackupDirectoryPath = Prefs.instance.autoBackupLocation;
if (autoBackupDirectoryPath == null) {
throw StateError("No auto backup directory is set");
}

final json = await SWB.createStackWalletJSON(
secureStorage: secureStorageInterface,
);
final jsonString = jsonEncode(json);

final adkString = await secureStorageInterface.read(key: "auto_adk_string");
final adkVersionString = await secureStorageInterface.read(
key: "auto_adk_version_string",
);
final adkVersion = int.parse(adkVersionString!);
final now = DateTime.now();
final fileToSave = createAutoBackupFilename(autoBackupDirectoryPath, now);
final content = await SWB.encryptStackWalletWithADK(
adkString!,
jsonString,
adkVersion,
);

await FS.writeStringToFile(
content,
autoBackupDirectoryPath,
fileToSave.split("/").last,
);

Prefs.instance.lastAutoBackup = now;
trimBackups(autoBackupDirectoryPath, 3);
}

void _setStatus(AutoSWBStatus status) {
_status = status;
if (!_isDisposed) {
notifyListeners();
}
}

/// Trim the number of auto backup files based on age
Expand Down Expand Up @@ -165,7 +188,7 @@ class AutoSWBService extends ChangeNotifier {
(a, b) => b.item1.millisecondsSinceEpoch - a.item1.millisecondsSinceEpoch,
);

// delete any older backups if there are more than the number we want to keep
// Delete backups beyond the retention limit.
while (files.length > numberToKeep) {
final fileToDelete = files.removeLast().item2;
fileToDelete.deleteSync();
Expand Down Expand Up @@ -199,6 +222,10 @@ class AutoSWBService extends ChangeNotifier {

@override
void dispose() {
_isDisposed = true;
_backupPending = false;
_debounceTimer?.cancel();
_debounceTimer = null;
stopPeriodicBackupTimer(shouldNotifyListeners: false);
super.dispose();
}
Expand Down
1 change: 1 addition & 0 deletions lib/utilities/enums/backup_frequency_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ enum BackupFrequencyType {
everyTenMinutes,
everyAppStart,
afterClosingAWallet,
afterChanges,
}
Loading
Loading