Skip to content

Commit

Permalink
new: icloud manual
Browse files Browse the repository at this point in the history
  • Loading branch information
lollipopkit committed Oct 17, 2023
1 parent 439aa91 commit 8ce2cc5
Show file tree
Hide file tree
Showing 36 changed files with 261 additions and 173 deletions.
39 changes: 34 additions & 5 deletions lib/core/persistant_store.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/data/res/path.dart';

class PersistentStore<E> {
late Box<E> box;

Future<PersistentStore<E>> init({String boxName = 'defaultBox'}) async {
box = await Hive.openBox(boxName);
return this;
late final Box<E> box;

final String boxName;

PersistentStore(this.boxName);

Future<void> init() async => box = await Hive.openBox(boxName);

/// Get all db filenames.
///
/// - [suffixs] defaults to ['.hive']
///
/// - If [hideSetting] is true, hide 'setting.hive'
static Future<List<String>> getFileNames({
bool hideSetting = false,
List<String>? suffixs,
}) async {
final docPath = await Paths.doc;
final dir = Directory(docPath);
final files = await dir.list().toList();
if (suffixs != null) {
files.removeWhere((e) => !suffixs.contains(e.path.split('.').last));
} else {
// filter out non-hive(db) files
files.removeWhere((e) => !e.path.endsWith('.hive'));
}
if (hideSetting) {
files.removeWhere((e) => e.path.endsWith('setting.hive'));
}
final paths =
files.map((e) => e.path.replaceFirst('$docPath/', '')).toList();
return paths;
}
}

Expand Down
64 changes: 39 additions & 25 deletions lib/core/utils/icloud.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,29 @@ import 'dart:async';
import 'dart:io';

import 'package:icloud_storage/icloud_storage.dart';
import 'package:toolbox/core/utils/platform/base.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';

class SyncResult<T, E> {
final List<T> up;
final List<T> down;
final Map<T, E> err;

const SyncResult({
required this.up,
required this.down,
required this.err,
});

@override
String toString() {
return 'SyncResult{up: $up, down: $down, err: $err}';
}
}

class ICloud {
static const _containerId = 'iCloud.tech.lolli.serverbox';

Expand Down Expand Up @@ -91,41 +107,44 @@ class ICloud {
///
/// - [relativePath] is the path relative to [docDir],
/// must not starts with `/`
/// - [bakSuffix] is the suffix of backup file, default to [null].
/// 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<Iterable<ICloudErr>?> sync({
static Future<SyncResult<String, ICloudErr>> sync({
required Iterable<String> relativePaths,
String? bakPrefix,
}) async {
final uploadFiles = <String>[];
final downloadFiles = <String>[];

try {
final errs = <ICloudErr>[];
final errs = <String, ICloudErr>{};

final allFiles = await getAll();

/// remove files not in relativePaths
allFiles.removeWhere((e) => !relativePaths.contains(e.relativePath));

final mission = <Future<void>>[];
final missions = <Future<void>>[];

/// upload files not in iCloud
final missed = relativePaths.where((e) {
return !allFiles.any((f) => f.relativePath == e);
});
mission.addAll(missed.map((e) async {
missions.addAll(missed.map((e) async {
final err = await upload(relativePath: e);
if (err != null) {
errs.add(err);
errs[e] = err;
}
}));

final docPath = await Paths.doc;

/// compare files in iCloud and local
mission.addAll(allFiles.map((file) async {
missions.addAll(allFiles.map((file) async {
final relativePath = file.relativePath;

/// Check date
Expand All @@ -134,7 +153,7 @@ class ICloud {
/// Local file not found, download remote file
final err = await download(relativePath: relativePath);
if (err != null) {
errs.add(err);
errs[relativePath] = err;
}
return;
}
Expand All @@ -149,39 +168,34 @@ class ICloud {
await delete(relativePath);
final err = await upload(relativePath: relativePath);
if (err != null) {
errs.add(err);
errs[relativePath] = err;
}
uploadFiles.add(relativePath);
return;
}

/// Remote is newer than local, so download remote
final err = await download(relativePath: relativePath);
final localPath = '$docPath/${bakPrefix ?? ''}$relativePath';
final err = await download(
relativePath: relativePath,
localPath: localPath,
);
if (err != null) {
errs.add(err);
errs[relativePath] = err;
}
downloadFiles.add(relativePath);
}));

await Future.wait(mission);
await Future.wait(missions);

return errs.isEmpty ? null : errs;
return SyncResult(up: uploadFiles, down: downloadFiles, err: errs);
} catch (e, s) {
Loggers.app.warning('iCloud sync: $relativePaths failed', e, s);
return [ICloudErr(type: ICloudErrType.generic, message: '$e')];
return SyncResult(up: uploadFiles, down: downloadFiles, err: {
'Generic': ICloudErr(type: ICloudErrType.generic, message: '$e')
});
} finally {
Loggers.app.info('iCloud sync, up: $uploadFiles, down: $downloadFiles');
}
}

static Future<void> syncDb() async {
if (!isIOS && !isMacOS) return;
final docPath = await Paths.doc;
final dir = Directory(docPath);
final files = await dir.list().toList();
// filter out non-hive(db) files
files.removeWhere((e) => !e.path.endsWith('.hive'));
final paths = files.map((e) => e.path.replaceFirst('$docPath/', ''));
await ICloud.sync(relativePaths: paths);
}
}
6 changes: 3 additions & 3 deletions lib/data/res/build_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

class BuildData {
static const String name = "ServerBox";
static const int build = 597;
static const int build = 599;
static const String engine = "3.13.7";
static const String buildAt = "2023-10-15 13:38:49";
static const int modifications = 9;
static const String buildAt = "2023-10-15 21:24:51";
static const int modifications = 4;
static const int script = 21;
}
11 changes: 11 additions & 0 deletions lib/data/res/store.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:toolbox/core/persistant_store.dart';
import 'package:toolbox/data/store/docker.dart';
import 'package:toolbox/data/store/first.dart';
import 'package:toolbox/data/store/history.dart';
Expand All @@ -17,4 +18,14 @@ class Stores {
static final key = locator<PrivateKeyStore>();
static final snippet = locator<SnippetStore>();
static final first = locator<FirstStore>();

static final List<PersistentStore> all = [
setting,
server,
docker,
history,
key,
snippet,
first,
];
}
2 changes: 2 additions & 0 deletions lib/data/store/docker.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import '../../core/persistant_store.dart';

class DockerStore extends PersistentStore<String> {
DockerStore() : super('docker');

String? fetch(String id) {
return box.get(id);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/data/store/first.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:toolbox/core/persistant_store.dart';

/// It stores whether is the first time of some.
class FirstStore extends PersistentStore<bool> {
FirstStore() : super('first');

/// Add Snippet `Install ServerBoxMonitor`
late final iSSBM = StoreProperty(box, 'installMonitorSnippet', true);

Expand Down
2 changes: 2 additions & 0 deletions lib/data/store/history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class _MapHistory {
}

class HistoryStore extends PersistentStore {
HistoryStore() : super('history');

/// Paths that user has visited by 'Locate' button
late final sftpGoPath = _ListHistory(box: box, name: 'sftpPath');

Expand Down
2 changes: 2 additions & 0 deletions lib/data/store/private_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import '../../core/persistant_store.dart';
import '../model/server/private_key_info.dart';

class PrivateKeyStore extends PersistentStore<PrivateKeyInfo> {
PrivateKeyStore() : super('key');

void put(PrivateKeyInfo info) {
box.put(info.id, info);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/data/store/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import '../../core/persistant_store.dart';
import '../model/server/server_private_info.dart';

class ServerStore extends PersistentStore<ServerPrivateInfo> {
ServerStore() : super('server');

void put(ServerPrivateInfo info) {
box.put(info.id, info);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/data/store/setting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import '../model/app/net_view.dart';
import '../res/default.dart';

class SettingStore extends PersistentStore {
SettingStore() : super('setting');

/// Convert all settings into json
Map<String, dynamic> toJson() => {for (var e in box.keys) e: box.get(e)};

Expand Down
2 changes: 2 additions & 0 deletions lib/data/store/snippet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import '../../core/persistant_store.dart';
import '../model/server/snippet.dart';

class SnippetStore extends PersistentStore<Snippet> {
SnippetStore() : super('snippet');

void put(Snippet snippet) {
box.put(snippet.name, snippet);
}
Expand Down
14 changes: 7 additions & 7 deletions lib/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,31 @@ void _setupLocatorForProviders() {

Future<void> _setupLocatorForStores() async {
final setting = SettingStore();
await setting.init(boxName: 'setting');
await setting.init();
locator.registerSingleton(setting);

final server = ServerStore();
await server.init(boxName: 'server');
await server.init();
locator.registerSingleton(server);

final key = PrivateKeyStore();
await key.init(boxName: 'key');
await key.init();
locator.registerSingleton(key);

final snippet = SnippetStore();
await snippet.init(boxName: 'snippet');
await snippet.init();
locator.registerSingleton(snippet);

final docker = DockerStore();
await docker.init(boxName: 'docker');
await docker.init();
locator.registerSingleton(docker);

final history = HistoryStore();
await history.init(boxName: 'history');
await history.init();
locator.registerSingleton(history);

final first = FirstStore();
await first.init(boxName: 'first');
await first.init();
locator.registerSingleton(first);
}

Expand Down
4 changes: 0 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:toolbox/data/res/store.dart';

import 'app.dart';
import 'core/analysis.dart';
import 'core/utils/icloud.dart';
import 'core/utils/ui.dart';
import 'data/model/app/net_view.dart';
import 'data/model/server/private_key_info.dart';
Expand Down Expand Up @@ -78,9 +77,6 @@ Future<void> initApp() async {
primaryColor = Color(Stores.setting.primaryColor.fetch());
loadFontFile(Stores.setting.fontPath.fetch());

// Don't call it via `await`, it will block the main thread.
if (Stores.setting.icloudSync.fetch()) ICloud.syncDb();

if (isAndroid) {
// Only start service when [bgRun] is true.
if (Stores.setting.bgRun.fetch()) {
Expand Down
Loading

0 comments on commit 8ce2cc5

Please sign in to comment.