mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
opt.: icloud sync (#187)
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:icloud_storage/icloud_storage.dart';
|
||||
import 'package:toolbox/data/model/app/backup.dart';
|
||||
import 'package:toolbox/data/model/app/sync.dart';
|
||||
import 'package:toolbox/data/res/logger.dart';
|
||||
|
||||
import '../../data/model/app/error.dart';
|
||||
import '../../data/model/app/json.dart';
|
||||
import '../../data/res/path.dart';
|
||||
|
||||
abstract final class ICloud {
|
||||
@@ -93,8 +94,6 @@ abstract final class ICloud {
|
||||
/// All files downloaded from cloud will be suffixed with [bakSuffix].
|
||||
///
|
||||
/// Return `null` if upload success, `ICloudErr` otherwise
|
||||
///
|
||||
/// TODO: consider merge strategy, use [SyncAble] and [JsonSerializable]
|
||||
static Future<SyncResult<String, ICloudErr>> syncFiles({
|
||||
required Iterable<String> relativePaths,
|
||||
String? bakPrefix,
|
||||
@@ -181,4 +180,35 @@ abstract final class ICloud {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> sync() async {
|
||||
try {
|
||||
final result = await download(relativePath: Paths.bakName);
|
||||
if (result != null) {
|
||||
Loggers.app.warning('Download backup failed: $result');
|
||||
return;
|
||||
}
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Download backup failed', e, s);
|
||||
}
|
||||
final dlFile = await File(await Paths.bak).readAsString();
|
||||
final dlBak = await compute(Backup.fromJsonString, dlFile);
|
||||
final restore = await dlBak.restore();
|
||||
switch (restore) {
|
||||
case true:
|
||||
Loggers.app.info('Restore from iCloud (${dlBak.lastModTime}) success');
|
||||
break;
|
||||
case false:
|
||||
await Backup.backup();
|
||||
final uploadResult = await upload(relativePath: Paths.bakName);
|
||||
if (uploadResult != null) {
|
||||
Loggers.app.warning('Upload iCloud backup failed: $uploadResult');
|
||||
} else {
|
||||
Loggers.app.info('Upload iCloud backup success');
|
||||
}
|
||||
break;
|
||||
case null:
|
||||
Loggers.app.info('Skip iCloud sync');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class Backup {
|
||||
final List<PrivateKeyInfo> keys;
|
||||
final Map<String, dynamic> dockerHosts;
|
||||
final Map<String, dynamic> settings;
|
||||
final int? lastModTime;
|
||||
|
||||
const Backup({
|
||||
required this.version,
|
||||
@@ -29,6 +30,7 @@ class Backup {
|
||||
required this.keys,
|
||||
required this.dockerHosts,
|
||||
required this.settings,
|
||||
this.lastModTime,
|
||||
});
|
||||
|
||||
Backup.fromJson(Map<String, dynamic> json)
|
||||
@@ -43,7 +45,8 @@ class Backup {
|
||||
.map((e) => PrivateKeyInfo.fromJson(e))
|
||||
.toList(),
|
||||
dockerHosts = json['dockerHosts'] ?? {},
|
||||
settings = json['settings'] ?? {};
|
||||
settings = json['settings'] ?? {},
|
||||
lastModTime = json['lastModTime'];
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'version': version,
|
||||
@@ -53,6 +56,7 @@ class Backup {
|
||||
'keys': keys,
|
||||
'dockerHosts': dockerHosts,
|
||||
'settings': settings,
|
||||
'lastModTime': lastModTime,
|
||||
};
|
||||
|
||||
Backup.loadFromStore()
|
||||
@@ -62,7 +66,8 @@ class Backup {
|
||||
snippets = Stores.snippet.fetch(),
|
||||
keys = Stores.key.fetch(),
|
||||
dockerHosts = Stores.docker.box.toJson(),
|
||||
settings = Stores.setting.box.toJson();
|
||||
settings = Stores.setting.box.toJson(),
|
||||
lastModTime = Stores.lastModTime;
|
||||
|
||||
static Future<String> backup() async {
|
||||
final result = _diyEncrypt(json.encode(Backup.loadFromStore()));
|
||||
@@ -71,7 +76,15 @@ class Backup {
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<void> restore() async {
|
||||
Future<bool?> restore({bool force = false}) async {
|
||||
final curTime = Stores.lastModTime ?? 0;
|
||||
final thisTime = lastModTime ?? 0;
|
||||
if (curTime == thisTime) {
|
||||
return null;
|
||||
}
|
||||
if (curTime > thisTime && !force) {
|
||||
return false;
|
||||
}
|
||||
for (final s in settings.keys) {
|
||||
Stores.setting.box.put(s, settings[s]);
|
||||
}
|
||||
@@ -90,6 +103,7 @@ class Backup {
|
||||
Stores.docker.put(k, val);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Backup.fromJsonString(String raw)
|
||||
|
||||
@@ -46,7 +46,9 @@ abstract final class Paths {
|
||||
return _fontDir!;
|
||||
}
|
||||
|
||||
static Future<String> get bak async => '${await doc}/srvbox_bak.json';
|
||||
static const String bakName = 'srvbox_bak.json';
|
||||
|
||||
static Future<String> get bak async => '${await doc}/$bakName';
|
||||
|
||||
static Future<String> get dl async => joinPath(await doc, 'dl');
|
||||
}
|
||||
|
||||
@@ -23,4 +23,15 @@ abstract final class Stores {
|
||||
key,
|
||||
snippet,
|
||||
];
|
||||
|
||||
static int? get lastModTime {
|
||||
int? lastModTime = 0;
|
||||
for (final store in all) {
|
||||
final last = store.box.lastModified ?? 0;
|
||||
if (last > (lastModTime ?? 0)) {
|
||||
lastModTime = last;
|
||||
}
|
||||
}
|
||||
return lastModTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class PrivateKeyStore extends PersistentStore {
|
||||
final ps = <PrivateKeyInfo>[];
|
||||
for (final key in keys) {
|
||||
final s = box.get(key);
|
||||
if (s != null) {
|
||||
if (s != null && s is PrivateKeyInfo) {
|
||||
ps.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class ServerStore extends PersistentStore {
|
||||
final List<ServerPrivateInfo> ss = [];
|
||||
for (final id in ids) {
|
||||
final s = box.get(id);
|
||||
if (s != null) {
|
||||
if (s != null && s is ServerPrivateInfo) {
|
||||
ss.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class SnippetStore extends PersistentStore {
|
||||
final ss = <Snippet>[];
|
||||
for (final key in keys) {
|
||||
final s = box.get(key);
|
||||
if (s != null) {
|
||||
if (s != null && s is Snippet) {
|
||||
ss.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:macos_window_utils/window_manipulator.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:toolbox/core/channel/bg_run.dart';
|
||||
import 'package:toolbox/core/utils/icloud.dart';
|
||||
import 'package:toolbox/core/utils/platform/base.dart';
|
||||
import 'package:toolbox/data/res/logger.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
@@ -91,6 +92,9 @@ Future<void> initApp() async {
|
||||
// SharedPreferences is only used on Android for saving home widgets settings.
|
||||
SharedPreferences.setPrefix('');
|
||||
}
|
||||
if (isIOS || isMacOS) {
|
||||
if (Stores.setting.icloudSync.fetch()) ICloud.sync();
|
||||
}
|
||||
}
|
||||
|
||||
void _setupProviders() {
|
||||
|
||||
@@ -190,7 +190,8 @@ class BackupPage extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await backup.restore();
|
||||
/// TODO: add checkbox for not force restore
|
||||
await backup.restore(force: true);
|
||||
Pros.reload();
|
||||
context.pop();
|
||||
RebuildNodes.app.rebuild();
|
||||
|
||||
Reference in New Issue
Block a user