opt.: webdav sync logic

This commit is contained in:
lollipopkit
2023-12-04 15:11:12 +08:00
parent 5a982ae32e
commit 03b9a46a4c
6 changed files with 112 additions and 80 deletions

View File

@@ -3,9 +3,9 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:icloud_storage/icloud_storage.dart'; import 'package:icloud_storage/icloud_storage.dart';
import 'package:logging/logging.dart';
import 'package:toolbox/data/model/app/backup.dart'; import 'package:toolbox/data/model/app/backup.dart';
import 'package:toolbox/data/model/app/sync.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/error.dart';
import '../../../data/res/path.dart'; import '../../../data/res/path.dart';
@@ -13,6 +13,8 @@ import '../../../data/res/path.dart';
abstract final class ICloud { abstract final class ICloud {
static const _containerId = 'iCloud.tech.lolli.serverbox'; static const _containerId = 'iCloud.tech.lolli.serverbox';
static final _logger = Logger('iCloud');
/// Upload file to iCloud /// Upload file to iCloud
/// ///
/// - [relativePath] is the path relative to [docDir], /// - [relativePath] is the path relative to [docDir],
@@ -171,12 +173,12 @@ abstract final class ICloud {
return SyncResult(up: uploadFiles, down: downloadFiles, err: errs); return SyncResult(up: uploadFiles, down: downloadFiles, err: errs);
} catch (e, s) { } catch (e, s) {
Loggers.app.warning('iCloud sync: $relativePaths failed', e, s); _logger.warning('Sync: $relativePaths failed', e, s);
return SyncResult(up: uploadFiles, down: downloadFiles, err: { return SyncResult(up: uploadFiles, down: downloadFiles, err: {
'Generic': ICloudErr(type: ICloudErrType.generic, message: '$e') 'Generic': ICloudErr(type: ICloudErrType.generic, message: '$e')
}); });
} finally { } finally {
Loggers.app.info('iCloud sync, up: $uploadFiles, down: $downloadFiles'); _logger.info('Sync, up: $uploadFiles, down: $downloadFiles');
} }
} }
@@ -184,30 +186,30 @@ abstract final class ICloud {
try { try {
final result = await download(relativePath: Paths.bakName); final result = await download(relativePath: Paths.bakName);
if (result != null) { if (result != null) {
Loggers.app.warning('Download backup failed: $result'); _logger.warning('Download backup failed: $result');
return; return;
} }
} catch (e, s) { } catch (e, s) {
Loggers.app.warning('Download backup failed', e, s); _logger.warning('Download backup failed', e, s);
} }
final dlFile = await File(await Paths.bak).readAsString(); final dlFile = await File(await Paths.bak).readAsString();
final dlBak = await compute(Backup.fromJsonString, dlFile); final dlBak = await compute(Backup.fromJsonString, dlFile);
final restore = await dlBak.restore(); final restore = await dlBak.restore();
switch (restore) { switch (restore) {
case true: case true:
Loggers.app.info('Restore from iCloud (${dlBak.lastModTime}) success'); _logger.info('Restore from ${dlBak.lastModTime} success');
break; break;
case false: case false:
await Backup.backup(); await Backup.backup();
final uploadResult = await upload(relativePath: Paths.bakName); final uploadResult = await upload(relativePath: Paths.bakName);
if (uploadResult != null) { if (uploadResult != null) {
Loggers.app.warning('Upload iCloud backup failed: $uploadResult'); _logger.warning('Upload backup failed: $uploadResult');
} else { } else {
Loggers.app.info('Upload iCloud backup success'); _logger.info('Upload backup success');
} }
break; break;
case null: case null:
Loggers.app.info('Skip iCloud sync'); _logger.info('Skip sync');
break; break;
} }
} }

View File

@@ -1,9 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:toolbox/data/model/app/backup.dart'; import 'package:toolbox/data/model/app/backup.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:toolbox/data/model/app/error.dart';
import 'package:toolbox/data/res/logger.dart';
import 'package:toolbox/data/res/path.dart'; import 'package:toolbox/data/res/path.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:toolbox/data/res/store.dart';
// ignore: implementation_imports // ignore: implementation_imports
@@ -16,13 +16,15 @@ abstract final class Webdav {
pwd: Stores.setting.webdavPwd.fetch(), pwd: Stores.setting.webdavPwd.fetch(),
); );
static final _logger = Logger('Webdav');
static Future<String?> test(String url, String user, String pwd) async { static Future<String?> test(String url, String user, String pwd) async {
final client = WebdavClient(url: url, user: user, pwd: pwd); final client = WebdavClient(url: url, user: user, pwd: pwd);
try { try {
await client.ping(); await client.ping();
return null; return null;
} catch (e, s) { } catch (e, s) {
Loggers.app.warning('Webdav test failed', e, s); _logger.warning('Test failed', e, s);
return e.toString(); return e.toString();
} }
} }
@@ -37,7 +39,7 @@ abstract final class Webdav {
relativePath, relativePath,
); );
} catch (e, s) { } catch (e, s) {
Loggers.app.warning('Webdav upload failed', e, s); _logger.warning('Upload $relativePath failed', e, s);
return WebdavErr(type: WebdavErrType.generic, message: '$e'); return WebdavErr(type: WebdavErrType.generic, message: '$e');
} }
return null; return null;
@@ -47,7 +49,7 @@ abstract final class Webdav {
try { try {
await _client.remove(relativePath); await _client.remove(relativePath);
} catch (e, s) { } catch (e, s) {
Loggers.app.warning('Webdav delete failed', e, s); _logger.warning('Delete $relativePath failed', e, s);
return WebdavErr(type: WebdavErrType.generic, message: '$e'); return WebdavErr(type: WebdavErrType.generic, message: '$e');
} }
return null; return null;
@@ -62,8 +64,8 @@ abstract final class Webdav {
relativePath, relativePath,
localPath ?? '${await Paths.doc}/$relativePath', localPath ?? '${await Paths.doc}/$relativePath',
); );
} catch (e, s) { } catch (e) {
Loggers.app.warning('Webdav download failed', e, s); _logger.warning('Download $relativePath failed');
return WebdavErr(type: WebdavErrType.generic, message: '$e'); return WebdavErr(type: WebdavErrType.generic, message: '$e');
} }
return null; return null;
@@ -74,34 +76,49 @@ abstract final class Webdav {
} }
static Future<void> sync() async { static Future<void> sync() async {
try { final result = await download(relativePath: Paths.bakName);
final result = await download(relativePath: Paths.bakName); if (result != null) {
if (result != null) { await backup();
Loggers.app.warning('Download backup failed: $result'); return;
return;
}
} catch (e, s) {
Loggers.app.warning('Download backup failed', e, s);
} }
final dlFile = await File(await Paths.bak).readAsString(); final dlFile = await compute(
final dlBak = await compute(Backup.fromJsonString, dlFile); (message) async {
final restore = await dlBak.restore(); try {
final file = await File(message).readAsString();
final bak = Backup.fromJsonString(file);
return bak;
} catch (_) {
return null;
}
},
await Paths.bak,
);
if (dlFile == null) {
await backup();
return;
}
final restore = await dlFile.restore();
switch (restore) { switch (restore) {
case true: case true:
Loggers.app.info('Restore from iCloud (${dlBak.lastModTime}) success'); _logger.info('Restore from ${dlFile.lastModTime} success');
break; break;
case false: case false:
await Backup.backup(); await 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; break;
case null: case null:
Loggers.app.info('Skip iCloud sync'); _logger.info('Skip sync');
break; break;
} }
} }
/// Create a local backup and upload it to WebDAV
static Future<void> backup() async {
await Backup.backup();
final uploadResult = await upload(relativePath: Paths.bakName);
if (uploadResult != null) {
_logger.warning('Upload failed: $uploadResult');
} else {
_logger.info('Upload success');
}
}
} }

View File

@@ -78,6 +78,9 @@ class Backup {
return path; return path;
} }
/// - Return null if same time
/// - Return false if local is newer
/// - Return true if restore success
Future<bool?> restore({bool force = false}) async { Future<bool?> restore({bool force = false}) async {
final curTime = Stores.lastModTime ?? 0; final curTime = Stores.lastModTime ?? 0;
final thisTime = lastModTime ?? 0; final thisTime = lastModTime ?? 0;

View File

@@ -11,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:toolbox/core/channel/bg_run.dart'; import 'package:toolbox/core/channel/bg_run.dart';
import 'package:toolbox/core/utils/sync/icloud.dart'; import 'package:toolbox/core/utils/sync/icloud.dart';
import 'package:toolbox/core/utils/platform/base.dart'; import 'package:toolbox/core/utils/platform/base.dart';
import 'package:toolbox/core/utils/sync/webdav.dart';
import 'package:toolbox/data/res/logger.dart'; import 'package:toolbox/data/res/logger.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:toolbox/data/res/store.dart';
@@ -95,6 +96,7 @@ Future<void> initApp() async {
if (isIOS || isMacOS) { if (isIOS || isMacOS) {
if (Stores.setting.icloudSync.fetch()) ICloud.sync(); if (Stores.setting.icloudSync.fetch()) ICloud.sync();
} }
if (Stores.setting.webdavSync.fetch()) Webdav.sync();
} }
void _setupProviders() { void _setupProviders() {

View File

@@ -54,7 +54,7 @@ class BackupPage extends StatelessWidget {
Widget _buildFile(BuildContext context) { Widget _buildFile(BuildContext context) {
return CardX( return CardX(
ExpandTile( ExpandTile(
leading: const Icon(Icons.file_open, size: 19), leading: const Icon(Icons.file_open),
title: Text(l10n.files), title: Text(l10n.files),
initiallyExpanded: true, initiallyExpanded: true,
children: [ children: [
@@ -139,7 +139,7 @@ class BackupPage extends StatelessWidget {
Widget _buildIcloud(BuildContext context) { Widget _buildIcloud(BuildContext context) {
return CardX( return CardX(
ExpandTile( ExpandTile(
leading: const Icon(Icons.cloud, size: 19), leading: const Icon(Icons.cloud),
title: const Text('iCloud'), title: const Text('iCloud'),
initiallyExpanded: true, initiallyExpanded: true,
children: [ children: [
@@ -231,7 +231,7 @@ class BackupPage extends StatelessWidget {
Widget _buildWebdav(BuildContext context) { Widget _buildWebdav(BuildContext context) {
return CardX( return CardX(
ExpandTile( ExpandTile(
leading: const Icon(Icons.storage, size: 19), leading: const Icon(Icons.storage),
title: const Text('WebDAV'), title: const Text('WebDAV'),
initiallyExpanded: !(isIOS || isMacOS), initiallyExpanded: !(isIOS || isMacOS),
children: [ children: [

View File

@@ -25,14 +25,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.0" version: "6.2.0"
ansicolor:
dependency: transitive
description:
name: ansicolor
sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
archive: archive:
dependency: "direct main" dependency: "direct main"
description: description:
name: archive name: archive
sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.6" version: "3.4.9"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -93,10 +101,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.1"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
@@ -109,10 +117,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.6" version: "2.4.7"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@@ -133,10 +141,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.7.0" version: "8.8.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@@ -182,10 +190,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "4.8.0"
code_text_field: code_text_field:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -214,18 +222,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: countly_flutter name: countly_flutter
sha256: "8e774b37b926523458dc7f36e4e4975671b4b2108cdfc284371c08b1bc2b475b" sha256: "06260f1a15c2abfcb0b5c96dc986de640fae27607c6b0cfdba60d5c50cf2173e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "23.8.3" version: "23.8.4"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3+6" version: "0.3.3+8"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@@ -262,10 +270,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: abd7625e16f51f554ea244d090292945ec4d4be7bfbaf2ec8cccea568919d334 sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.3.4"
dartssh2: dartssh2:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -278,10 +286,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7" sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.3" version: "5.4.0"
dynamic_color: dynamic_color:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -376,10 +384,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_native_splash name: flutter_native_splash
sha256: d93394f22f73e810bda59e11ebe83329c5511d6460b6b7509c4e1f3c92d6d625 sha256: c4d899312b36e7454bedfd0a4740275837b99e532d81c8477579d8183db1de6c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.3.6"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -754,10 +762,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.0.2"
pinenacl: pinenacl:
dependency: transitive dependency: transitive
description: description:
@@ -786,10 +794,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.6" version: "2.1.7"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
@@ -810,10 +818,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.5" version: "6.1.1"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@@ -906,10 +914,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@@ -1063,10 +1071,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.0" version: "6.2.1"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@@ -1095,10 +1103,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@@ -1111,10 +1119,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: uuid name: uuid
sha256: b715b8d3858b6fa9f68f87d20d98830283628014750c2b09b6f516c1da4af2a7 sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.2.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -1169,7 +1177,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "233b3ebaa01b7bb35a414c9cfd5e2933b3a008ba" resolved-ref: "88de97ad783c624d81b0dbcc09927b10aabd5580"
url: "https://github.com/lollipopkit/webdav_client" url: "https://github.com/lollipopkit/webdav_client"
source: git source: git
version: "1.2.1" version: "1.2.1"
@@ -1177,10 +1185,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.9" version: "5.1.1"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -1193,10 +1201,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: xml name: xml
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.4.2" version: "6.5.0"
xterm: xterm:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1223,5 +1231,5 @@ packages:
source: hosted source: hosted
version: "0.0.6" version: "0.0.6"
sdks: sdks:
dart: ">=3.2.0-194.0.dev <4.0.0" dart: ">=3.2.0 <4.0.0"
flutter: ">=3.13.0" flutter: ">=3.16.0"