From 292a29a6115d7879b04de59fb6ee3a5465b60434 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Thu, 25 Jan 2024 20:00:40 +0800 Subject: [PATCH] opt.: backup restore --- README.md | 2 +- README_zh.md | 4 +- ios/Runner.xcodeproj/project.pbxproj | 36 ++++---- lib/core/persistant_store.dart | 45 ++++++++-- lib/core/utils/sync/icloud.dart | 24 ++--- lib/core/utils/sync/webdav.dart | 14 +-- lib/data/model/app/backup.dart | 116 +++++++++++++++++++++---- lib/data/res/build_data.dart | 6 +- lib/data/store/setting.dart | 8 +- macos/Runner.xcodeproj/project.pbxproj | 12 +-- 10 files changed, 176 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 4de1e5f2..a41ba93d 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ After you read the above, you can: ## 💡 My other apps - [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms. -- [2fa Box](https://github.com/lollipopkit/flutter_2fa) - Open source 2FA app for Android, iOS and the web. +- [2FA Box](https://github.com/lollipopkit/flutter_2fa) - Open source 2FA app for Android, iOS and the web. - [More](https://github.com/lollipopkit) - Tools & etc. diff --git a/README_zh.md b/README_zh.md index b991a7c5..5773e7e3 100644 --- a/README_zh.md +++ b/README_zh.md @@ -86,8 +86,8 @@ ## 💡 我的其它 Apps -- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 一个支持 OpenAI API 的 第三方全平台客户端。 -- [2fa Box](https://github.com/lollipopkit/flutter_2fa) - 开源的 2FA 应用,支持 Android、iOS 和 Web。 +- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。 +- [2FA Box](https://github.com/lollipopkit/flutter_2fa) - 开源的 2FA 应用。 - [更多](https://github.com/lollipopkit) - 工具 & etc. diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 208635d1..ea1703a8 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -586,7 +586,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -720,7 +720,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -748,7 +748,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -779,7 +779,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -792,7 +792,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -818,7 +818,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -831,7 +831,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -854,7 +854,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -867,7 +867,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -890,7 +890,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; @@ -931,7 +931,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; @@ -969,7 +969,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 717; + CURRENT_PROJECT_VERSION = 719; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.717; + MARKETING_VERSION = 1.0.719; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/lib/core/persistant_store.dart b/lib/core/persistant_store.dart index c632c892..28465661 100644 --- a/lib/core/persistant_store.dart +++ b/lib/core/persistant_store.dart @@ -78,12 +78,30 @@ extension BoxX on Box { } extension StoreX on PersistentStore { - _StoreProperty property(String key, T defaultValue) { - return _StoreProperty(box, key, defaultValue); + _StoreProperty property( + String key, + T defaultValue, { + bool updateLastModified = true, + }) { + return _StoreProperty( + box, + key, + defaultValue, + updateLastModified: updateLastModified, + ); } - _StoreListProperty listProperty(String key, List defaultValue) { - return _StoreListProperty(box, key, defaultValue); + _StoreListProperty listProperty( + String key, + List defaultValue, { + bool updateLastModified = true, + }) { + return _StoreListProperty( + box, + key, + defaultValue, + updateLastModified: updateLastModified, + ); } } @@ -95,11 +113,17 @@ abstract class StorePropertyBase { } class _StoreProperty implements StorePropertyBase { - _StoreProperty(this._box, this._key, this.defaultValue); + _StoreProperty( + this._box, + this._key, + this.defaultValue, { + this.updateLastModified = true, + }); final Box _box; final String _key; T defaultValue; + bool updateLastModified; @override ValueListenable listenable() { @@ -117,7 +141,7 @@ class _StoreProperty implements StorePropertyBase { @override Future put(T value) { - _box.updateLastModified(); + if (updateLastModified) _box.updateLastModified(); return _box.put(_key, value); } @@ -128,11 +152,17 @@ class _StoreProperty implements StorePropertyBase { } class _StoreListProperty implements StorePropertyBase> { - _StoreListProperty(this._box, this._key, this.defaultValue); + _StoreListProperty( + this._box, + this._key, + this.defaultValue, { + this.updateLastModified = true, + }); final Box _box; final String _key; List defaultValue; + bool updateLastModified; @override ValueListenable> listenable() { @@ -152,6 +182,7 @@ class _StoreListProperty implements StorePropertyBase> { @override Future put(List value) { + if (updateLastModified) _box.updateLastModified(); return _box.put(_key, value); } diff --git a/lib/core/utils/sync/icloud.dart b/lib/core/utils/sync/icloud.dart index 359551ea..7d08594c 100644 --- a/lib/core/utils/sync/icloud.dart +++ b/lib/core/utils/sync/icloud.dart @@ -205,23 +205,13 @@ abstract final class ICloud { } final dlFile = await File(await Paths.bak).readAsString(); final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile); - final restore = await dlBak.restore(); - switch (restore) { - case true: - _logger.info('Restore from ${dlBak.lastModTime} success'); - break; - case false: - await Backup.backup(); - final uploadResult = await upload(relativePath: Paths.bakName); - if (uploadResult != null) { - _logger.warning('Upload backup failed: $uploadResult'); - } else { - _logger.info('Upload backup success'); - } - break; - case null: - _logger.info('Skip sync'); - break; + await dlBak.restore(); + await Backup.backup(); + final uploadResult = await upload(relativePath: Paths.bakName); + if (uploadResult != null) { + _logger.warning('Upload backup failed: $uploadResult'); + } else { + _logger.info('Upload backup success'); } } } diff --git a/lib/core/utils/sync/webdav.dart b/lib/core/utils/sync/webdav.dart index 6ac9ccae..077e7b45 100644 --- a/lib/core/utils/sync/webdav.dart +++ b/lib/core/utils/sync/webdav.dart @@ -117,18 +117,8 @@ abstract final class Webdav { await backup(); return; } - final restore = await dlFile.restore(); - switch (restore) { - case true: - _logger.info('Restore from ${dlFile.lastModTime} success'); - break; - case false: - await backup(); - break; - case null: - _logger.info('Skip sync'); - break; - } + await dlFile.restore(); + await backup(); } /// Create a local backup and upload it to WebDAV diff --git a/lib/data/model/app/backup.dart b/lib/data/model/app/backup.dart index e119c9cf..8f353579 100644 --- a/lib/data/model/app/backup.dart +++ b/lib/data/model/app/backup.dart @@ -86,34 +86,116 @@ class Backup { /// - Return null if same time /// - Return false if local is newer /// - Return true if restore success - Future restore({bool force = false}) async { + Future restore({bool force = false}) async { final curTime = Stores.lastModTime ?? 0; final bakTime = lastModTime ?? 0; - if (curTime == bakTime && !force) { - return null; + final shouldRestore = force || curTime < bakTime; + + // Settings + final nowSettingsKeys = Stores.setting.box.keys.toSet(); + final bakSettingsKeys = settings.keys.toSet(); + final newSettingsKeys = bakSettingsKeys.difference(nowSettingsKeys); + final delSettingsKeys = nowSettingsKeys.difference(bakSettingsKeys); + final updateSettingsKeys = nowSettingsKeys.intersection(bakSettingsKeys); + for (final k in newSettingsKeys) { + Stores.setting.box.put(k, settings[k]); } - if (curTime > bakTime && !force) { - return false; + if (shouldRestore) { + for (final k in delSettingsKeys) { + Stores.setting.box.delete(k); + } + for (final k in updateSettingsKeys) { + Stores.setting.box.put(k, settings[k]); + } } - for (final s in settings.keys) { - Stores.setting.box.put(s, settings[s]); - } - for (final s in snippets) { + + // Snippets + final nowSnippets = Stores.snippet.fetch().toSet(); + final bakSnippets = snippets.toSet(); + final newSnippets = bakSnippets.difference(nowSnippets); + final delSnippets = nowSnippets.difference(bakSnippets); + final updateSnippets = nowSnippets.intersection(bakSnippets); + for (final s in newSnippets) { Stores.snippet.put(s); } - for (final s in spis) { + if (shouldRestore) { + for (final s in delSnippets) { + Stores.snippet.delete(s); + } + for (final s in updateSnippets) { + Stores.snippet.put(s); + } + } + + // ServerPrivateInfo + final nowSpis = Stores.server.fetch().toSet(); + final bakSpis = spis.toSet(); + final newSpis = bakSpis.difference(nowSpis); + final delSpis = nowSpis.difference(bakSpis); + final updateSpis = nowSpis.intersection(bakSpis); + for (final s in newSpis) { Stores.server.put(s); } - for (final s in keys) { + if (shouldRestore) { + for (final s in delSpis) { + Stores.server.delete(s.id); + } + for (final s in updateSpis) { + Stores.server.put(s); + } + } + + // PrivateKeyInfo + final nowKeys = Stores.key.fetch().toSet(); + final bakKeys = keys.toSet(); + final newKeys = bakKeys.difference(nowKeys); + final delKeys = nowKeys.difference(bakKeys); + final updateKeys = nowKeys.intersection(bakKeys); + for (final s in newKeys) { Stores.key.put(s); } - for (final s in history.keys) { + if (shouldRestore) { + for (final s in delKeys) { + Stores.key.delete(s); + } + for (final s in updateKeys) { + Stores.key.put(s); + } + } + + // History + final nowHistory = Stores.history.box.keys.toSet(); + final bakHistory = history.keys.toSet(); + final newHistory = bakHistory.difference(nowHistory); + final delHistory = nowHistory.difference(bakHistory); + final updateHistory = nowHistory.intersection(bakHistory); + for (final s in newHistory) { Stores.history.box.put(s, history[s]); } - for (final k in container.keys) { - final val = container[k]; - if (val != null && val is String && val.isNotEmpty) { - Stores.docker.put(k, val); + if (shouldRestore) { + for (final s in delHistory) { + Stores.history.box.delete(s); + } + for (final s in updateHistory) { + Stores.history.box.put(s, history[s]); + } + } + + // Container + final nowContainer = Stores.docker.box.keys.toSet(); + final bakContainer = container.keys.toSet(); + final newContainer = bakContainer.difference(nowContainer); + final delContainer = nowContainer.difference(bakContainer); + final updateContainer = nowContainer.intersection(bakContainer); + for (final s in newContainer) { + Stores.docker.put(s, container[s]); + } + if (shouldRestore) { + for (final s in delContainer) { + Stores.docker.box.delete(s); + } + for (final s in updateContainer) { + Stores.docker.put(s, container[s]); } } @@ -122,8 +204,6 @@ class Backup { Pros.reload(); RebuildNodes.app.rebuild(); - - return true; } Backup.fromJsonString(String raw) diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index d2b0ff17..a71f5c0f 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 717; + static const int build = 719; static const String engine = "3.16.8"; - static const String buildAt = "2024-01-22 16:31:50"; - static const int modifications = 6; + static const String buildAt = "2024-01-23 22:32:15"; + static const int modifications = 3; static const int script = 36; } diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 0a63c34a..062b8dfe 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -201,10 +201,10 @@ class SettingStore extends PersistentStore { late final showSuspendTip = property('showSuspendTip', true); /// Webdav sync - late final webdavSync = property('webdavSync', false); - late final webdavUrl = property('webdavUrl', ''); - late final webdavUser = property('webdavUser', ''); - late final webdavPwd = property('webdavPwd', ''); + late final webdavSync = property('webdavSync', false, updateLastModified: false); + late final webdavUrl = property('webdavUrl', '', updateLastModified: false); + late final webdavUser = property('webdavUser', '', updateLastModified: false); + late final webdavPwd = property('webdavPwd', '', updateLastModified: false); /// Whether collapse UI items by default late final collapseUIDefault = property('collapseUIDefault', true); diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 18337b16..f5240dc0 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -437,11 +437,9 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -453,7 +451,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "Server Box"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = serverbox; SWIFT_VERSION = 5.0; }; name = Profile; @@ -573,11 +570,9 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6; + DEVELOPMENT_TEAM = BA88US33G6; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -589,7 +584,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "Server Box"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = serverbox; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; };