diff --git a/app/lib/app_init.dart b/app/lib/app_init.dart index e5ac9639..5f6626af 100644 --- a/app/lib/app_init.dart +++ b/app/lib/app_init.dart @@ -39,7 +39,10 @@ import 'package:visibility_detector/visibility_detector.dart'; enum InitIsolateType { main, - service, + + /// Isolates with Flutter engine, e.g., those spawned by flutter_isolate or + /// flutter_background_service + flutterIsolate, } Future init(InitIsolateType isolateType) async { @@ -200,7 +203,7 @@ Future _createDb(InitIsolateType isolateType) async { return sql_isolate.createDb(); } - case InitIsolateType.service: + case InitIsolateType.flutterIsolate: // service already runs in an isolate return sql.SqliteDb(); } diff --git a/app/lib/service.dart b/app/lib/service.dart index 417382b9..a54811cd 100644 --- a/app/lib/service.dart +++ b/app/lib/service.dart @@ -70,7 +70,7 @@ class _Service { final service = FlutterBackgroundService(); service.setForegroundMode(true); - await app_init.init(app_init.InitIsolateType.service); + await app_init.init(app_init.InitIsolateType.flutterIsolate); await _L10n().init(); _log.info("[call] Service started"); diff --git a/app/lib/use_case/startup_sync.dart b/app/lib/use_case/startup_sync.dart new file mode 100644 index 00000000..ca1b8e4c --- /dev/null +++ b/app/lib/use_case/startup_sync.dart @@ -0,0 +1,104 @@ +import 'dart:async'; + +import 'package:event_bus/event_bus.dart'; +import 'package:flutter_isolate/flutter_isolate.dart'; +import 'package:kiwi/kiwi.dart'; +import 'package:logging/logging.dart'; +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/app_init.dart' as app_init; +import 'package:nc_photos/di_container.dart'; +import 'package:nc_photos/event/event.dart'; +import 'package:nc_photos/platform/k.dart' as platform_k; +import 'package:nc_photos/type.dart'; +import 'package:nc_photos/use_case/sync_favorite.dart'; + +/// Sync various properties with server during startup +class StartupSync { + StartupSync(this._c) + : assert(require(_c)), + assert(SyncFavorite.require(_c)); + + static bool require(DiContainer c) => true; + + /// Sync in a background isolate + static Future runInIsolate(Account account) async { + if (platform_k.isWeb) { + // not supported on web + final c = KiwiContainer().resolve(); + return await StartupSync(c)(account); + } else { + // we can't use regular isolate here because self-signed cert support + // requires native plugins + final resultJson = + await flutterCompute(_isolateMain, _IsolateMessage(account).toJson()); + final result = SyncResult.fromJson(resultJson); + // events fired in background isolate won't be noticed by the main isolate, + // so we fire them again here + _broadcastResult(account, result); + return result; + } + } + + Future call(Account account) async { + _log.info("[_run] Begin sync"); + late final int syncFavoriteCount; + try { + syncFavoriteCount = await SyncFavorite(_c)(account); + } catch (e, stackTrace) { + _log.shout("[_run] Failed while SyncFavorite", e, stackTrace); + syncFavoriteCount = -1; + } + return SyncResult(syncFavoriteCount); + } + + static void _broadcastResult(Account account, SyncResult result) { + final eventBus = KiwiContainer().resolve(); + if (result.syncFavoriteCount > 0) { + eventBus.fire(FavoriteResyncedEvent(account)); + } + } + + final DiContainer _c; + + static final _log = Logger("use_case.startup_sync.StartupSync"); +} + +class SyncResult { + const SyncResult(this.syncFavoriteCount); + + factory SyncResult.fromJson(JsonObj json) => SyncResult( + json["syncFavoriteCount"], + ); + + JsonObj toJson() => { + "syncFavoriteCount": syncFavoriteCount, + }; + + final int syncFavoriteCount; +} + +class _IsolateMessage { + const _IsolateMessage(this.account); + + factory _IsolateMessage.fromJson(JsonObj json) => _IsolateMessage( + Account.fromJson( + json["account"].cast(), + upgraderV1: const AccountUpgraderV1(), + )!, + ); + + JsonObj toJson() => { + "account": account.toJson(), + }; + + final Account account; +} + +Future _isolateMain(JsonObj messageJson) async { + final message = _IsolateMessage.fromJson(messageJson); + await app_init.init(app_init.InitIsolateType.flutterIsolate); + + final c = KiwiContainer().resolve(); + final result = await StartupSync(c)(message.account); + return result.toJson(); +} diff --git a/app/lib/widget/home_photos.dart b/app/lib/widget/home_photos.dart index 0e598c52..464026cf 100644 --- a/app/lib/widget/home_photos.dart +++ b/app/lib/widget/home_photos.dart @@ -21,7 +21,6 @@ import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/event/event.dart'; -import 'package:nc_photos/exception.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/language_util.dart' as language_util; @@ -35,7 +34,7 @@ import 'package:nc_photos/share_handler.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/throttler.dart'; -import 'package:nc_photos/use_case/sync_favorite.dart'; +import 'package:nc_photos/use_case/startup_sync.dart'; import 'package:nc_photos/widget/album_browser_util.dart' as album_browser_util; import 'package:nc_photos/widget/builder/photo_list_item_builder.dart'; import 'package:nc_photos/widget/handler/add_selection_to_album_handler.dart'; @@ -381,7 +380,7 @@ class _HomePhotosState extends State state is ScanAccountDirBlocLoading) { _transformItems(state.files); if (state is ScanAccountDirBlocSuccess) { - _syncFavorite(); + _startupSync(); _tryStartMetadataTask(); } } else if (state is ScanAccountDirBlocFailure) { @@ -536,18 +535,10 @@ class _HomePhotosState extends State } } - Future _syncFavorite() async { + Future _startupSync() async { if (!_hasResyncedFavorites.value) { - final c = KiwiContainer().resolve(); - try { - await SyncFavorite(c)(widget.account); - } catch (e, stackTrace) { - if (e is! ApiException) { - _log.shout( - "[_syncFavorite] Failed while SyncFavorite", e, stackTrace); - } - } _hasResyncedFavorites.value = true; + StartupSync.runInIsolate(widget.account); } } diff --git a/app/pubspec.lock b/app/pubspec.lock index a183664f..ee441139 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -490,6 +490,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.0" + flutter_isolate: + dependency: "direct main" + description: + path: "." + ref: "2.0.2-nc-photos-1" + resolved-ref: "8d8516b169860caba762baed43a7888798c5b7d7" + url: "https://gitlab.com/nc-photos/flutter_isolate.git" + source: git + version: "2.0.2" flutter_keyboard_visibility: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index af00fef4..da727423 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -57,6 +57,10 @@ dependencies: url: https://gitlab.com/nc-photos/flutter_background_service.git ref: v0.2.6-nc-photos-2 flutter_bloc: ^8.0.0 + flutter_isolate: + git: + url: https://gitlab.com/nc-photos/flutter_isolate.git + ref: 2.0.2-nc-photos-1 flutter_map: ^1.1.1 flutter_staggered_grid_view: git: