diff --git a/packages/sane/example/main.dart b/packages/sane/example/main.dart index b6d5de2..6805c99 100644 --- a/packages/sane/example/main.dart +++ b/packages/sane/example/main.dart @@ -5,7 +5,7 @@ import 'dart:typed_data'; import 'package:logging/logging.dart'; import 'package:sane/sane.dart'; -import 'package:sane/src/impl/sane_dev.dart'; +import 'package:sane/src/impl/sane_mock.dart'; import 'package:sane/src/impl/sane_native.dart'; void main(List args) async { @@ -79,4 +79,6 @@ void main(List args) async { ); final rawPixelData = mergeUint8Lists(rawPixelDataList); file.writeAsBytesSync(rawPixelData, mode: FileMode.append); + + await sane.dispose(); } diff --git a/packages/sane/lib/src/extensions.dart b/packages/sane/lib/src/extensions.dart index 0f0d5fb..6f7846c 100644 --- a/packages/sane/lib/src/extensions.dart +++ b/packages/sane/lib/src/extensions.dart @@ -1,3 +1,4 @@ +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:sane/src/bindings.g.dart'; import 'package:sane/src/exceptions.dart'; @@ -12,3 +13,16 @@ extension SaneStatusExtension on SANE_Status { } } } + +@internal +extension LoggerExtension on Logger { + void redirect(LogRecord record) { + log( + record.level, + record.message, + record.error, + record.stackTrace, + record.zone, + ); + } +} diff --git a/packages/sane/lib/src/impl/sane_dev.dart b/packages/sane/lib/src/impl/sane_mock.dart similarity index 99% rename from packages/sane/lib/src/impl/sane_dev.dart rename to packages/sane/lib/src/impl/sane_mock.dart index bf5d78f..6e887e1 100644 --- a/packages/sane/lib/src/impl/sane_dev.dart +++ b/packages/sane/lib/src/impl/sane_mock.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:logging/logging.dart'; import 'package:sane/sane.dart'; -final _logger = Logger('sane.dev'); +final _logger = Logger('sane.mock'); class MockSane implements Sane { @override diff --git a/packages/sane/lib/src/impl/sane_native.dart b/packages/sane/lib/src/impl/sane_native.dart index 39955f9..c8fa160 100644 --- a/packages/sane/lib/src/impl/sane_native.dart +++ b/packages/sane/lib/src/impl/sane_native.dart @@ -66,7 +66,7 @@ class NativeSane implements Sane { SaneIsolate? _isolate; Future _getIsolate() async { - if (_isolate?.exited == true || _disposed) throw SaneDisposedError(); + if (_disposed) throw SaneDisposedError(); return _isolate ??= await SaneIsolate.spawn(backingSane); } @@ -81,17 +81,18 @@ class NativeSane implements Sane { Future dispose({bool force = false}) async { final isolate = _isolate; - if (force) { - isolate?.kill(); - return; - } - if (_disposed) return; _disposed = true; _instance = null; - await isolate?.sendMessage(ExitMessage()); + if (isolate == null) return; + + if (force) { + isolate.kill(); + } else { + await isolate.sendMessage(ExitMessage()); + } _isolate = null; } diff --git a/packages/sane/lib/src/impl/sane_sync.dart b/packages/sane/lib/src/impl/sane_sync.dart index 1902fe3..1ec82d0 100644 --- a/packages/sane/lib/src/impl/sane_sync.dart +++ b/packages/sane/lib/src/impl/sane_sync.dart @@ -393,7 +393,8 @@ class SyncSaneDevice implements SaneDevice, ffi.Finalizable { SaneOptionResult controlBoolOption( int index, SaneAction action, - bool? value,) { + bool? value, + ) { return _controlOption( index: index, action: action, @@ -405,7 +406,8 @@ class SyncSaneDevice implements SaneDevice, ffi.Finalizable { SaneOptionResult controlIntOption( int index, SaneAction action, - int? value,) { + int? value, + ) { return _controlOption( index: index, action: action, @@ -417,7 +419,8 @@ class SyncSaneDevice implements SaneDevice, ffi.Finalizable { SaneOptionResult controlFixedOption( int index, SaneAction action, - double? value,) { + double? value, + ) { return _controlOption( index: index, action: action, @@ -429,7 +432,8 @@ class SyncSaneDevice implements SaneDevice, ffi.Finalizable { SaneOptionResult controlStringOption( int index, SaneAction action, - String? value,) { + String? value, + ) { return _controlOption( index: index, action: action, diff --git a/packages/sane/lib/src/isolate.dart b/packages/sane/lib/src/isolate.dart index 65117a2..84f2daa 100644 --- a/packages/sane/lib/src/isolate.dart +++ b/packages/sane/lib/src/isolate.dart @@ -1,49 +1,53 @@ import 'dart:async'; import 'dart:isolate'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; import 'package:sane/src/exceptions.dart'; +import 'package:sane/src/extensions.dart'; import 'package:sane/src/isolate_messages/exception.dart'; +import 'package:sane/src/isolate_messages/exit.dart'; import 'package:sane/src/isolate_messages/interface.dart'; import 'package:sane/src/sane.dart'; +final _logger = Logger('sane.isolate'); + +@internal class SaneIsolate { SaneIsolate._( this._isolate, - this._sendPort, - this._exitReceivePort, - ) : _exited = false { - _exitReceivePort.listen((message) { - assert(message == null); - _exited = true; - }); - } + this._sendPort,); final Isolate _isolate; final SendPort _sendPort; - final ReceivePort _exitReceivePort; - - bool _exited; - - bool get exited => _exited; static Future spawn(Sane sane) async { final receivePort = ReceivePort(); - final exitReceivePort = ReceivePort(); final isolate = await Isolate.spawn( _entryPoint, (receivePort.sendPort, sane), - onExit: exitReceivePort.sendPort, + onExit: receivePort.sendPort, ); - final sendPort = await receivePort.first as SendPort; - return SaneIsolate._(isolate, sendPort, exitReceivePort); - } + final sendPortCompleter = Completer(); + receivePort.listen((message) { + switch (message) { + case SendPort(): + sendPortCompleter.complete(message); + case LogRecord(): + _logger.redirect(message); + case null: + receivePort.close(); + } + }); - void kill() { - _isolate.kill(priority: Isolate.immediate); + final sendPort = await sendPortCompleter.future; + return SaneIsolate._(isolate, sendPort); } + void kill() => _isolate.kill(priority: Isolate.immediate); + Future sendMessage( IsolateMessage message, ) async { @@ -75,10 +79,16 @@ typedef _EntryPointArgs = (SendPort sendPort, Sane sane); void _entryPoint(_EntryPointArgs args) { final (sendPort, sane) = args; + Logger.root.level = Level.ALL; + Logger.root.onRecord.forEach(sendPort.send); + final receivePort = ReceivePort(); sendPort.send(receivePort.sendPort); - receivePort.cast<_IsolateMessageEnvelope>().listen((envelope) async { + late StreamSubscription<_IsolateMessageEnvelope> subscription; + + subscription = + receivePort.cast<_IsolateMessageEnvelope>().listen((envelope) async { final _IsolateMessageEnvelope(:message, :replyPort) = envelope; IsolateResponse response; @@ -93,6 +103,10 @@ void _entryPoint(_EntryPointArgs args) { } replyPort.send(response); + + if (message is ExitMessage) { + await subscription.cancel(); + } }); } @@ -108,8 +122,10 @@ class _IsolateMessageEnvelope { late Map _devices; +@internal SaneDevice getDevice(String name) => _devices[name]!; +@internal void setDevices(Iterable devices) { _devices = { for (final device in devices) device.name: device, diff --git a/packages/sane/lib/src/isolate_messages/get_devices.dart b/packages/sane/lib/src/isolate_messages/get_devices.dart index cb12eda..ed6ef37 100644 --- a/packages/sane/lib/src/isolate_messages/get_devices.dart +++ b/packages/sane/lib/src/isolate_messages/get_devices.dart @@ -1,3 +1,4 @@ +import 'package:sane/src/isolate.dart'; import 'package:sane/src/isolate_messages/interface.dart'; import 'package:sane/src/sane.dart'; @@ -8,14 +9,14 @@ class GetDevicesMessage implements IsolateMessage { @override Future handle(Sane sane) async { - return GetDevicesResponse( - devices: await sane.getDevices(localOnly: localOnly), - ); + final devices = await sane.getDevices(localOnly: localOnly); + setDevices(devices); + return GetDevicesResponse(devices); } } class GetDevicesResponse implements IsolateResponse { - GetDevicesResponse({required this.devices}); + GetDevicesResponse(this.devices); final List devices; } diff --git a/packages/sane/lib/src/sane.dart b/packages/sane/lib/src/sane.dart index 821f883..05d5452 100644 --- a/packages/sane/lib/src/sane.dart +++ b/packages/sane/lib/src/sane.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:sane/sane.dart'; -import 'package:sane/src/impl/sane_dev.dart'; +import 'package:sane/src/impl/sane_mock.dart'; import 'package:sane/src/impl/sane_native.dart'; import 'package:sane/src/impl/sane_sync.dart'; @@ -15,7 +15,7 @@ abstract interface class Sane { /// See also: /// /// - [Sane.sync] - factory Sane() => NativeSane(); + factory Sane([Sane? backingSane]) => NativeSane(backingSane); /// Instantiates a new synchronous SANE instance. factory Sane.sync() => SyncSane(); diff --git a/packages/sane/test/sane_singleton_test.dart b/packages/sane/test/sane_singleton_test.dart index b033bbf..377f34c 100644 --- a/packages/sane/test/sane_singleton_test.dart +++ b/packages/sane/test/sane_singleton_test.dart @@ -1,9 +1,18 @@ +import 'package:logging/logging.dart'; import 'package:sane/sane.dart'; import 'package:test/test.dart'; void main() { late Sane sane; + setUp(() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + // ignore: avoid_print + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + }); + test('can instantiate', () { sane = Sane(); }); @@ -25,7 +34,7 @@ void main() { }); test('can reinstiate with new instance', () { - final newSane = Sane(); + final newSane = Sane(Sane.mock()); expect(sane, isNot(newSane)); sane = newSane; });