mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
opt.: bak pwd is optional (#872)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ abstract final class GithubIds {
|
|||||||
'CreeperKong',
|
'CreeperKong',
|
||||||
'zxf945',
|
'zxf945',
|
||||||
'cnen2018',
|
'cnen2018',
|
||||||
|
'xiaomeng9597',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user