opt.: bak pwd is optional (#872)

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-08-31 11:11:47 +08:00
committed by GitHub
parent 53a7c0d8ff
commit a97b3cf43e
5 changed files with 38 additions and 69 deletions

View File

@@ -14,11 +14,7 @@ final class BakSyncer extends SyncIface {
@override @override
Future<void> saveToFile() async { Future<void> saveToFile() async {
final pwd = await SecureStoreProps.bakPwd.read(); final pwd = await SecureStoreProps.bakPwd.read();
if (pwd == null || pwd.isEmpty) { await BackupV2.backup(null, pwd?.isEmpty == true ? null : pwd);
// Enforce password for non-clipboard backups
throw Exception('Backup password not set');
}
await BackupV2.backup(null, pwd);
} }
@override @override

View File

@@ -11,27 +11,10 @@ class BackupService {
/// Perform backup operation with the given source /// Perform backup operation with the given source
static Future<void> backup(BuildContext context, BackupSource source) async { static Future<void> backup(BuildContext context, BackupSource source) async {
try { try {
String? password; final saved = await SecureStoreProps.bakPwd.read();
final password = saved?.isEmpty == true ? null : saved;
if (source is ClipboardBackupSource) { final path = await BackupV2.backup(null, password?.isEmpty == true ? null : password);
// Clipboard backup: allow optional password
password = await _getClipboardPassword(context);
if (password == null) return; // canceled
} else {
// All other backups require pre-set bakPwd (SecureStore)
final saved = await SecureStoreProps.bakPwd.read();
if (saved == null || saved.isEmpty) {
// Prompt to set before proceeding
password = await _showPasswordDialog(context, hint: l10n.backupPasswordTip);
if (password == null || password.isEmpty) return; // Not set
await SecureStoreProps.bakPwd.write(password);
context.showSnackBar(l10n.backupPasswordSet);
} else {
password = saved;
}
}
final path = await BackupV2.backup(null, password.isEmpty ? null : password);
await source.saveContent(path); await source.saveContent(path);
if (source is ClipboardBackupSource) { if (source is ClipboardBackupSource) {
@@ -56,34 +39,6 @@ class BackupService {
await restoreFromText(context, text); await restoreFromText(context, text);
} }
/// Handle password dialog for backup operations
static Future<String?> _getClipboardPassword(BuildContext context) async {
// Use saved bakPwd as default for clipboard flow if exists, but allow empty/custom
final savedPassword = await SecureStoreProps.bakPwd.read();
String? password;
if (savedPassword != null && savedPassword.isNotEmpty) {
final useCustom = await context.showRoundDialog<bool>(
title: l10n.backupPassword,
child: Text(l10n.backupPasswordTip),
actions: [
Btn.cancel(),
TextButton(onPressed: () => context.pop(false), child: Text(l10n.backupPasswordSet)),
TextButton(onPressed: () => context.pop(true), child: Text(libL10n.custom)),
],
);
if (useCustom == null) return null;
if (useCustom) {
password = await _showPasswordDialog(context, initial: savedPassword);
} else {
password = savedPassword;
}
} else {
password = await _showPasswordDialog(context);
}
return password;
}
/// Handle restore from text with decryption support /// Handle restore from text with decryption support
static Future<void> restoreFromText(BuildContext context, String text) async { static Future<void> restoreFromText(BuildContext context, String text) async {
// Check if backup is encrypted // Check if backup is encrypted

View File

@@ -14,8 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$ServersState { mixin _$ServersState {
Map<String, Spi> get servers;// Only store server configuration information Map<String, Spi> get servers; List<String> get serverOrder; Set<String> get tags; Set<String> get manualDisconnectedIds; Timer? get autoRefreshTimer;
List<String> get serverOrder; Set<String> get tags; Set<String> get manualDisconnectedIds; Timer? get autoRefreshTimer;
/// Create a copy of ServersState /// Create a copy of ServersState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -221,9 +220,7 @@ class _ServersState implements ServersState {
return EqualUnmodifiableMapView(_servers); return EqualUnmodifiableMapView(_servers);
} }
// Only store server configuration information
final List<String> _serverOrder; final List<String> _serverOrder;
// Only store server configuration information
@override@JsonKey() List<String> get serverOrder { @override@JsonKey() List<String> get serverOrder {
if (_serverOrder is EqualUnmodifiableListView) return _serverOrder; if (_serverOrder is EqualUnmodifiableListView) return _serverOrder;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type

View File

@@ -124,6 +124,7 @@ abstract final class GithubIds {
'CreeperKong', 'CreeperKong',
'zxf945', 'zxf945',
'cnen2018', 'cnen2018',
'xiaomeng9597',
}; };
} }

View File

@@ -131,14 +131,6 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
} }
} }
Future<bool> _ensureBakPwd(BuildContext context) async {
final saved = await SecureStoreProps.bakPwd.read();
if (saved != null && saved.isNotEmpty) return true;
await _onTapSetBakPwd(context);
final after = await SecureStoreProps.bakPwd.read();
return after != null && after.isNotEmpty;
}
Widget get _buildTip { Widget get _buildTip {
return CardX( return CardX(
child: ListTile( child: ListTile(
@@ -415,6 +407,11 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
).cardx; ).cardx;
} }
@override
bool get wantKeepAlive => true;
}
extension on _BackupPageState {
Future<void> _onTapWebdavDl(BuildContext context) async { Future<void> _onTapWebdavDl(BuildContext context) async {
webdavLoading.value = true; webdavLoading.value = true;
try { try {
@@ -443,7 +440,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
final ok = await _ensureBakPwd(context); final ok = await _ensureBakPwd(context);
if (!ok) return; if (!ok) return;
final savedPassword = await SecureStoreProps.bakPwd.read(); final savedPassword = await SecureStoreProps.bakPwd.read();
await BackupV2.backup(bakName, savedPassword); await BackupV2.backup(bakName, savedPassword?.isEmpty == true ? null : savedPassword);
await Webdav.shared.upload(relativePath: bakName); await Webdav.shared.upload(relativePath: bakName);
Loggers.app.info('Upload webdav backup success'); Loggers.app.info('Upload webdav backup success');
} catch (e, s) { } catch (e, s) {
@@ -482,7 +479,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
final ok = await _ensureBakPwd(context); final ok = await _ensureBakPwd(context);
if (!ok) return; if (!ok) return;
final savedPassword = await SecureStoreProps.bakPwd.read(); final savedPassword = await SecureStoreProps.bakPwd.read();
await BackupV2.backup(bakName, savedPassword); await BackupV2.backup(bakName, savedPassword?.isEmpty == true ? null : savedPassword);
await GistRs.shared.upload(relativePath: bakName); await GistRs.shared.upload(relativePath: bakName);
Loggers.app.info('Upload gist backup success'); Loggers.app.info('Upload gist backup success');
} catch (e, s) { } catch (e, s) {
@@ -624,6 +621,29 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
} }
} }
@override Future<bool> _ensureBakPwd(BuildContext context) async {
bool get wantKeepAlive => true; final saved = await SecureStoreProps.bakPwd.read();
if (saved != null && saved.isNotEmpty) return true;
// Show dialog asking if user wants to set password or continue without
final result = await context.showRoundDialog<bool>(
title: l10n.backupPassword,
child: Text(l10n.backupPasswordTip, style: UIs.textGrey),
actions: [
TextButton(onPressed: () => context.pop(true), child: Text(libL10n.cancel)),
TextButton(onPressed: () => context.pop(false), child: Text(libL10n.setting)),
],
);
if (result == true) {
// Continue without password
return true;
} else if (result == false) {
// User wants to set password
await _onTapSetBakPwd(context);
return true; // Allow continuing even if password setting was cancelled
}
return false; // User cancelled the dialog
}
} }