From cd525f5168034ad8bad69e5e924827fe4875be29 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sun, 27 Aug 2023 15:45:00 +0800 Subject: [PATCH] Refactor: move the higher level platform lock code to its own package --- app/lib/entity/sqlite/database.dart | 1 + app/lib/entity/sqlite/database_extension.dart | 6 +- app/lib/mobile/lock.dart | 32 ---------- app/lib/mobile/platform.dart | 1 - app/lib/web/lock.dart | 9 --- app/lib/web/platform.dart | 1 - app/pubspec.lock | 9 ++- app/pubspec.yaml | 1 - np_platform_lock/lib/src/lock.dart | 20 ++----- np_platform_lock/lib/src/native/lock.dart | 17 ++++++ np_platform_lock/lib/src/native/raw_lock.dart | 41 +++++++++++++ np_platform_lock/lib/src/raw_lock.dart | 17 ++++++ np_platform_lock/lib/src/web/raw_lock.dart | 19 ++++++ np_platform_lock/pubspec.yaml | 3 + np_platform_util/.gitignore | 30 ++++++++++ np_platform_util/.metadata | 10 ++++ np_platform_util/analysis_options.yaml | 1 + np_platform_util/lib/np_platform_util.dart | 3 + np_platform_util/lib/src/env.dart | 60 +++++++++++++++++++ np_platform_util/pubspec.yaml | 17 ++++++ 20 files changed, 236 insertions(+), 62 deletions(-) delete mode 100644 app/lib/mobile/lock.dart delete mode 100644 app/lib/web/lock.dart create mode 100644 np_platform_lock/lib/src/native/lock.dart create mode 100644 np_platform_lock/lib/src/native/raw_lock.dart create mode 100644 np_platform_lock/lib/src/raw_lock.dart create mode 100644 np_platform_lock/lib/src/web/raw_lock.dart create mode 100644 np_platform_util/.gitignore create mode 100644 np_platform_util/.metadata create mode 100644 np_platform_util/analysis_options.yaml create mode 100644 np_platform_util/lib/np_platform_util.dart create mode 100644 np_platform_util/lib/src/env.dart create mode 100644 np_platform_util/pubspec.yaml diff --git a/app/lib/entity/sqlite/database.dart b/app/lib/entity/sqlite/database.dart index 06c7e85f..bb66fb03 100644 --- a/app/lib/entity/sqlite/database.dart +++ b/app/lib/entity/sqlite/database.dart @@ -17,6 +17,7 @@ import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:np_codegen/np_codegen.dart'; import 'package:np_collection/np_collection.dart'; +import 'package:np_platform_lock/np_platform_lock.dart'; part 'database.g.dart'; part 'database/nc_album_extension.dart'; diff --git a/app/lib/entity/sqlite/database_extension.dart b/app/lib/entity/sqlite/database_extension.dart index 824fa518..c8491963 100644 --- a/app/lib/entity/sqlite/database_extension.dart +++ b/app/lib/entity/sqlite/database_extension.dart @@ -122,7 +122,7 @@ extension SqliteDbExtension on SqliteDb { /// /// Do NOT call this when using [isolate], call [useInIsolate] instead Future use(Future Function(SqliteDb db) block) async { - return await platform.Lock.synchronized(k.appDbLockId, () async { + return await PlatformLock.synchronized(k.appDbLockId, () async { return await transaction(() async { return await block(this); }); @@ -135,7 +135,7 @@ extension SqliteDbExtension on SqliteDb { /// /// This function does not start a transaction, see [use] instead Future useNoTransaction(Future Function(SqliteDb db) block) async { - return await platform.Lock.synchronized(k.appDbLockId, () async { + return await PlatformLock.synchronized(k.appDbLockId, () async { return await block(this); }); } @@ -145,7 +145,7 @@ extension SqliteDbExtension on SqliteDb { Future isolate(T args, ComputeWithDbCallback callback) async { // we need to acquire the lock here as method channel is not supported in // background isolates - return await platform.Lock.synchronized(k.appDbLockId, () async { + return await PlatformLock.synchronized(k.appDbLockId, () async { // in unit tests we use an in-memory db, which mean there's no way to // access it in other isolates if (platform_k.isUnitTest) { diff --git a/app/lib/mobile/lock.dart b/app/lib/mobile/lock.dart deleted file mode 100644 index d2a8df4c..00000000 --- a/app/lib/mobile/lock.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:nc_photos/platform/k.dart' as platform_k; -import 'package:nc_photos/web/lock.dart' as web; -import 'package:np_platform_lock/np_platform_lock.dart' as plugin; - -class Lock { - static Future synchronized(int lockId, Future Function() fn) async { - if (platform_k.isAndroid) { - return _synchronizedAndroid(lockId, fn); - } else if (platform_k.isDesktop) { - return _synchronizedDesktop(lockId, fn); - } else { - throw UnimplementedError(); - } - } - - static Future _synchronizedAndroid( - int lockId, Future Function() fn) async { - while (!await plugin.Lock.tryLock(lockId)) { - await Future.delayed(const Duration(milliseconds: 50)); - } - try { - return await fn(); - } finally { - await plugin.Lock.unlock(lockId); - } - } - - // this is mainly used to run test cases - static Future _synchronizedDesktop( - int lockId, Future Function() fn) => - web.Lock.synchronized(lockId, fn); -} diff --git a/app/lib/mobile/platform.dart b/app/lib/mobile/platform.dart index caf75a04..07b05654 100644 --- a/app/lib/mobile/platform.dart +++ b/app/lib/mobile/platform.dart @@ -2,6 +2,5 @@ export 'db_util.dart'; export 'download.dart'; export 'file_saver.dart'; export 'google_gps_map.dart'; -export 'lock.dart'; export 'notification.dart'; export 'universal_storage.dart'; diff --git a/app/lib/web/lock.dart b/app/lib/web/lock.dart deleted file mode 100644 index 450c7f43..00000000 --- a/app/lib/web/lock.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:synchronized/synchronized.dart' as dart; - -// Isolates are not supported on web -class Lock { - static Future synchronized(int lockId, Future Function() fn) => - (_locks[lockId] ??= dart.Lock(reentrant: true)).synchronized(fn); - - static final _locks = {}; -} diff --git a/app/lib/web/platform.dart b/app/lib/web/platform.dart index caf75a04..07b05654 100644 --- a/app/lib/web/platform.dart +++ b/app/lib/web/platform.dart @@ -2,6 +2,5 @@ export 'db_util.dart'; export 'download.dart'; export 'file_saver.dart'; export 'google_gps_map.dart'; -export 'lock.dart'; export 'notification.dart'; export 'universal_storage.dart'; diff --git a/app/pubspec.lock b/app/pubspec.lock index 7e4128e6..0d0df3f8 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1004,6 +1004,13 @@ packages: relative: true source: path version: "0.0.1" + np_platform_util: + dependency: transitive + description: + path: "../np_platform_util" + relative: true + source: path + version: "0.0.1" np_string: dependency: "direct main" description: @@ -1504,7 +1511,7 @@ packages: source: hosted version: "1.2.0" synchronized: - dependency: "direct main" + dependency: transitive description: name: synchronized sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 497eb102..cd454c09 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -127,7 +127,6 @@ dependencies: smooth_corner: ^1.1.0 sqlite3: any sqlite3_flutter_libs: ^0.5.15 - synchronized: ^3.1.0 to_string: git: url: https://gitlab.com/nkming2/dart-to-string diff --git a/np_platform_lock/lib/src/lock.dart b/np_platform_lock/lib/src/lock.dart index 7b4e9db8..17d47279 100644 --- a/np_platform_lock/lib/src/lock.dart +++ b/np_platform_lock/lib/src/lock.dart @@ -1,17 +1,9 @@ -import 'package:flutter/services.dart'; -import 'package:np_platform_lock/src/k.dart' as k; +import 'package:np_platform_lock/src/raw_lock.dart'; -class Lock { - static Future tryLock(int lockId) async { - return (await _channel.invokeMethod("tryLock", { - "lockId": lockId, - }))!; - } +class PlatformLock { + static Future synchronized(int lockId, Future Function() fn) => + RawLockInterface().synchronized(lockId, fn); - static Future unlock(int lockId) => - _channel.invokeMethod("unlock", { - "lockId": lockId, - }); - - static const _channel = MethodChannel("${k.libId}/lock"); + static Future forceUnlock(int lockId) => + RawLockInterface().forceUnlock(lockId); } diff --git a/np_platform_lock/lib/src/native/lock.dart b/np_platform_lock/lib/src/native/lock.dart new file mode 100644 index 00000000..7b4e9db8 --- /dev/null +++ b/np_platform_lock/lib/src/native/lock.dart @@ -0,0 +1,17 @@ +import 'package:flutter/services.dart'; +import 'package:np_platform_lock/src/k.dart' as k; + +class Lock { + static Future tryLock(int lockId) async { + return (await _channel.invokeMethod("tryLock", { + "lockId": lockId, + }))!; + } + + static Future unlock(int lockId) => + _channel.invokeMethod("unlock", { + "lockId": lockId, + }); + + static const _channel = MethodChannel("${k.libId}/lock"); +} diff --git a/np_platform_lock/lib/src/native/raw_lock.dart b/np_platform_lock/lib/src/native/raw_lock.dart new file mode 100644 index 00000000..4bbf8961 --- /dev/null +++ b/np_platform_lock/lib/src/native/raw_lock.dart @@ -0,0 +1,41 @@ +import 'package:np_platform_lock/src/native/lock.dart'; +import 'package:np_platform_lock/src/raw_lock.dart'; +import 'package:np_platform_lock/src/web/raw_lock.dart' as web; +import 'package:np_platform_util/np_platform_util.dart'; + +class RawLock implements RawLockInterface { + RawLock._(); + + factory RawLock() => _inst; + + @override + Future synchronized(int lockId, Future Function() fn) async { + if (isUnitTest) { + return _synchronizedTest(lockId, fn); + } else { + return _synchronized(lockId, fn); + } + } + + @override + Future forceUnlock(int lockId) { + return Lock.unlock(lockId); + } + + Future _synchronized(int lockId, Future Function() fn) async { + while (!await Lock.tryLock(lockId)) { + await Future.delayed(const Duration(milliseconds: 50)); + } + try { + return await fn(); + } finally { + await Lock.unlock(lockId); + } + } + + // this is mainly used to run test cases + Future _synchronizedTest(int lockId, Future Function() fn) => + web.RawLock().synchronized(lockId, fn); + + static final _inst = RawLock._(); +} diff --git a/np_platform_lock/lib/src/raw_lock.dart b/np_platform_lock/lib/src/raw_lock.dart new file mode 100644 index 00000000..698bb0df --- /dev/null +++ b/np_platform_lock/lib/src/raw_lock.dart @@ -0,0 +1,17 @@ +import 'package:np_platform_lock/src/native/raw_lock.dart' + if (dart.library.html) 'package:np_platform_lock/src/web/raw_lock.dart'; + +abstract class RawLockInterface { + factory RawLockInterface() => RawLock(); + + /// Safely run [fn] with an async lock + Future synchronized(int lockId, Future Function() fn); + + /// Forcefully unlock an async lock + /// + /// This function is mostly for development use only, for example, to fix a + /// dangling lock after hot reload. This should not be used in production code + /// + /// This method may not be supported by all implementations + Future forceUnlock(int lockId); +} diff --git a/np_platform_lock/lib/src/web/raw_lock.dart b/np_platform_lock/lib/src/web/raw_lock.dart new file mode 100644 index 00000000..75d10942 --- /dev/null +++ b/np_platform_lock/lib/src/web/raw_lock.dart @@ -0,0 +1,19 @@ +import 'package:np_platform_lock/src/raw_lock.dart'; +import 'package:synchronized/synchronized.dart'; + +class RawLock implements RawLockInterface { + RawLock._(); + + factory RawLock() => _inst; + + @override + Future synchronized(int lockId, Future Function() fn) => + (_locks[lockId] ??= Lock(reentrant: true)).synchronized(fn); + + @override + Future forceUnlock(int lockId) => Future.value(); + + static final _inst = RawLock._(); + + final _locks = {}; +} diff --git a/np_platform_lock/pubspec.yaml b/np_platform_lock/pubspec.yaml index bf063d90..b775d146 100644 --- a/np_platform_lock/pubspec.yaml +++ b/np_platform_lock/pubspec.yaml @@ -11,6 +11,9 @@ environment: dependencies: flutter: sdk: flutter + np_platform_util: + path: ../np_platform_util + synchronized: ^3.1.0 dev_dependencies: np_lints: diff --git a/np_platform_util/.gitignore b/np_platform_util/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_platform_util/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_platform_util/.metadata b/np_platform_util/.metadata new file mode 100644 index 00000000..788b91db --- /dev/null +++ b/np_platform_util/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + channel: stable + +project_type: package diff --git a/np_platform_util/analysis_options.yaml b/np_platform_util/analysis_options.yaml new file mode 100644 index 00000000..f92d2567 --- /dev/null +++ b/np_platform_util/analysis_options.yaml @@ -0,0 +1 @@ +include: package:np_lints/np.yaml diff --git a/np_platform_util/lib/np_platform_util.dart b/np_platform_util/lib/np_platform_util.dart new file mode 100644 index 00000000..7165c871 --- /dev/null +++ b/np_platform_util/lib/np_platform_util.dart @@ -0,0 +1,3 @@ +library np_platform_util; + +export 'src/env.dart'; diff --git a/np_platform_util/lib/src/env.dart b/np_platform_util/lib/src/env.dart new file mode 100644 index 00000000..71ffab97 --- /dev/null +++ b/np_platform_util/lib/src/env.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +enum NpPlatform { + android, + fuchsia, + iOs, + linux, + macOs, + web, + windows, + ; + + bool get isMobile => this == android || this == iOs; + bool get isDesktop => + this == fuchsia || this == linux || this == macOs || this == windows; +} + +/// Get the current running platform +/// +/// This function does not take the current context into account +NpPlatform getRawPlatform() { + if (kIsWeb) { + return NpPlatform.web; + } else { + return defaultTargetPlatform.toPlatform(); + } +} + +/// Get the current platform from [context] +NpPlatform getThemePlatform(BuildContext context) { + if (kIsWeb) { + return NpPlatform.web; + } else { + return Theme.of(context).platform.toPlatform(); + } +} + +final isUnitTest = !kIsWeb && Platform.environment.containsKey("FLUTTER_TEST"); + +extension on TargetPlatform { + NpPlatform toPlatform() { + switch (this) { + case TargetPlatform.android: + return NpPlatform.android; + case TargetPlatform.iOS: + return NpPlatform.iOs; + case TargetPlatform.linux: + return NpPlatform.linux; + case TargetPlatform.macOS: + return NpPlatform.macOs; + case TargetPlatform.windows: + return NpPlatform.windows; + case TargetPlatform.fuchsia: + return NpPlatform.fuchsia; + } + } +} diff --git a/np_platform_util/pubspec.yaml b/np_platform_util/pubspec.yaml new file mode 100644 index 00000000..a34da98b --- /dev/null +++ b/np_platform_util/pubspec.yaml @@ -0,0 +1,17 @@ +name: np_platform_util +description: A new Flutter plugin project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.19.6 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + np_lints: + path: ../np_lints