Refactor: move the higher level platform lock code to its own package

This commit is contained in:
Ming Ming 2023-08-27 15:45:00 +08:00
parent c496e2d1e2
commit cd525f5168
20 changed files with 236 additions and 62 deletions

View file

@ -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';

View file

@ -122,7 +122,7 @@ extension SqliteDbExtension on SqliteDb {
///
/// Do NOT call this when using [isolate], call [useInIsolate] instead
Future<T> use<T>(Future<T> 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<T> useNoTransaction<T>(Future<T> 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<U> isolate<T, U>(T args, ComputeWithDbCallback<T, U> 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) {

View file

@ -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<T> synchronized<T>(int lockId, Future<T> 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<T> _synchronizedAndroid<T>(
int lockId, Future<T> 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<T> _synchronizedDesktop<T>(
int lockId, Future<T> Function() fn) =>
web.Lock.synchronized(lockId, fn);
}

View file

@ -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';

View file

@ -1,9 +0,0 @@
import 'package:synchronized/synchronized.dart' as dart;
// Isolates are not supported on web
class Lock {
static Future<T> synchronized<T>(int lockId, Future<T> Function() fn) =>
(_locks[lockId] ??= dart.Lock(reentrant: true)).synchronized(fn);
static final _locks = <int, dart.Lock>{};
}

View file

@ -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';

View file

@ -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"

View file

@ -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

View file

@ -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<bool> tryLock(int lockId) async {
return (await _channel.invokeMethod<bool>("tryLock", <String, dynamic>{
"lockId": lockId,
}))!;
}
class PlatformLock {
static Future<T> synchronized<T>(int lockId, Future<T> Function() fn) =>
RawLockInterface().synchronized(lockId, fn);
static Future<void> unlock(int lockId) =>
_channel.invokeMethod("unlock", <String, dynamic>{
"lockId": lockId,
});
static const _channel = MethodChannel("${k.libId}/lock");
static Future<void> forceUnlock(int lockId) =>
RawLockInterface().forceUnlock(lockId);
}

View file

@ -0,0 +1,17 @@
import 'package:flutter/services.dart';
import 'package:np_platform_lock/src/k.dart' as k;
class Lock {
static Future<bool> tryLock(int lockId) async {
return (await _channel.invokeMethod<bool>("tryLock", <String, dynamic>{
"lockId": lockId,
}))!;
}
static Future<void> unlock(int lockId) =>
_channel.invokeMethod("unlock", <String, dynamic>{
"lockId": lockId,
});
static const _channel = MethodChannel("${k.libId}/lock");
}

View file

@ -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<T> synchronized<T>(int lockId, Future<T> Function() fn) async {
if (isUnitTest) {
return _synchronizedTest(lockId, fn);
} else {
return _synchronized(lockId, fn);
}
}
@override
Future<void> forceUnlock(int lockId) {
return Lock.unlock(lockId);
}
Future<T> _synchronized<T>(int lockId, Future<T> 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<T> _synchronizedTest<T>(int lockId, Future<T> Function() fn) =>
web.RawLock().synchronized(lockId, fn);
static final _inst = RawLock._();
}

View file

@ -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<T> synchronized<T>(int lockId, Future<T> 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<void> forceUnlock(int lockId);
}

View file

@ -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<T> synchronized<T>(int lockId, Future<T> Function() fn) =>
(_locks[lockId] ??= Lock(reentrant: true)).synchronized(fn);
@override
Future<void> forceUnlock(int lockId) => Future.value();
static final _inst = RawLock._();
final _locks = <int, Lock>{};
}

View file

@ -11,6 +11,9 @@ environment:
dependencies:
flutter:
sdk: flutter
np_platform_util:
path: ../np_platform_util
synchronized: ^3.1.0
dev_dependencies:
np_lints:

30
np_platform_util/.gitignore vendored Normal file
View file

@ -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/

View file

@ -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

View file

@ -0,0 +1 @@
include: package:np_lints/np.yaml

View file

@ -0,0 +1,3 @@
library np_platform_util;
export 'src/env.dart';

View file

@ -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;
}
}
}

View file

@ -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