From 558a0d7bf06a37cc84091691e1362c5c66f04952 Mon Sep 17 00:00:00 2001 From: Craftplacer <22963120+Craftplacer@users.noreply.github.com> Date: Thu, 7 Nov 2024 03:29:02 +0100 Subject: [PATCH] Rearchitect library --- packages/sane/example/main.dart | 2 +- packages/sane/lib/src/bindings.g.dart | 11 + packages/sane/lib/src/impl/sane_dev.dart | 91 ++--- packages/sane/lib/src/impl/sane_native.dart | 266 ++++++-------- packages/sane/lib/src/impl/sane_sync.dart | 338 ++++++++---------- packages/sane/lib/src/isolate.dart | 103 ++++++ .../sane/lib/src/isolate_messages/cancel.dart | 7 +- .../sane/lib/src/isolate_messages/close.dart | 6 +- .../control_button_option.dart | 2 +- .../src/isolate_messages/control_option.dart | 2 +- .../get_all_option_descriptors.dart | 2 +- .../get_option_descriptor.dart | 2 +- .../src/isolate_messages/get_parameters.dart | 2 +- .../sane/lib/src/isolate_messages/open.dart | 2 +- .../sane/lib/src/isolate_messages/read.dart | 2 +- .../lib/src/isolate_messages/set_io_mode.dart | 21 -- .../sane/lib/src/isolate_messages/start.dart | 2 +- packages/sane/lib/src/sane.dart | 142 ++++++++ packages/sane/lib/src/structures.dart | 34 -- packages/sane/lib/src/type_conversion.dart | 35 +- packages/sane/pubspec.yaml | 4 + 21 files changed, 589 insertions(+), 487 deletions(-) create mode 100644 packages/sane/lib/src/isolate.dart delete mode 100644 packages/sane/lib/src/isolate_messages/set_io_mode.dart create mode 100644 packages/sane/lib/src/sane.dart diff --git a/packages/sane/example/main.dart b/packages/sane/example/main.dart index 5ff456b..3590758 100644 --- a/packages/sane/example/main.dart +++ b/packages/sane/example/main.dart @@ -12,7 +12,7 @@ void main(List args) async { print('${record.level.name}: ${record.time}: ${record.message}'); }); - final sane = SaneNative(sane: SaneDev()); + final sane = NativeSane(sane: SaneDev()); await sane.spawn(); await sane.init(); diff --git a/packages/sane/lib/src/bindings.g.dart b/packages/sane/lib/src/bindings.g.dart index c25efa9..6adf3ff 100644 --- a/packages/sane/lib/src/bindings.g.dart +++ b/packages/sane/lib/src/bindings.g.dart @@ -250,6 +250,17 @@ class LibSane { 'sane_strstatus'); late final _sane_strstatus = _sane_strstatusPtr.asFunction(); + + late final addresses = _SymbolAddresses(this); +} + +class _SymbolAddresses { + final LibSane _library; + + _SymbolAddresses(this._library); + + ffi.Pointer> + get sane_close => _library._sane_closePtr; } enum SANE_Status { diff --git a/packages/sane/lib/src/impl/sane_dev.dart b/packages/sane/lib/src/impl/sane_dev.dart index 8e39549..841f601 100644 --- a/packages/sane/lib/src/impl/sane_dev.dart +++ b/packages/sane/lib/src/impl/sane_dev.dart @@ -1,28 +1,16 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:logging/logging.dart'; import 'package:sane/sane.dart'; +import 'package:sane/src/sane.dart'; final _logger = Logger('sane.dev'); class SaneDev implements Sane { - @override - Future cancel(SaneHandle handle) { - return Future.delayed(const Duration(seconds: 1), () { - _logger.finest('sane_cancel()'); - }); - } - - @override - Future close(SaneHandle handle) { - return Future.delayed(const Duration(seconds: 1), () { - _logger.finest('sane_close()'); - }); - } - @override Future> controlBoolOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, bool? value, @@ -35,7 +23,7 @@ class SaneDev implements Sane { @override Future> controlButtonOption({ - required SaneHandle handle, + required int handle, required int index, }) { return Future.delayed(const Duration(seconds: 1), () { @@ -46,7 +34,7 @@ class SaneDev implements Sane { @override Future> controlFixedOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, double? value, @@ -59,7 +47,7 @@ class SaneDev implements Sane { @override Future> controlIntOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, int? value, @@ -72,7 +60,7 @@ class SaneDev implements Sane { @override Future> controlStringOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, String? value, @@ -84,15 +72,14 @@ class SaneDev implements Sane { } @override - Future exit() { + Future dispose() { return Future(() { _logger.finest('sane_exit()'); }); } @override - Future> getAllOptionDescriptors( - SaneHandle handle, + Future> getAllOptionDescriptors(int handle, ) { return Future.delayed(const Duration(seconds: 1), () { _logger.finest('sane_getAllOptionDescriptors()'); @@ -113,26 +100,19 @@ class SaneDev implements Sane { } @override - Future> getDevices({ + Future> getDevices({ required bool localOnly, }) { return Future.delayed(const Duration(seconds: 1), () { _logger.finest('sane_getDevices()'); return [ - for (var i = 0; i < 3; i++) - SaneDevice( - name: 'name $i', - vendor: 'Vendor$i', - model: 'Model$i', - type: 'Type$i', - ), + for (var i = 0; i < 3; i++) SaneDevDevice(i), ]; }); } @override - Future getOptionDescriptor( - SaneHandle handle, + Future getOptionDescriptor(int handle, int index, ) { return Future.delayed(const Duration(seconds: 1), () { @@ -152,7 +132,7 @@ class SaneDev implements Sane { } @override - Future getParameters(SaneHandle handle) { + Future getParameters(int handle) { return Future.delayed(const Duration(seconds: 1), () { _logger.finest('sane_getParameters()'); return SaneParameters( @@ -165,35 +145,35 @@ class SaneDev implements Sane { ); }); } +} - @override - Future init({ - AuthCallback? authCallback, - }) { - return Future(() { - _logger.finest('sane_init()'); - return 1; - }); - } +class SaneDevDevice implements SaneDevice { + const SaneDevDevice(this.index); + + final int index; @override - Future open(String deviceName) { + Future cancel() { return Future.delayed(const Duration(seconds: 1), () { - _logger.finest('sane_open()'); - return SaneHandle(deviceName: deviceName); + _logger.finest('sane_cancel()'); }); } @override - Future openDevice(SaneDevice device) { + Future close() { return Future.delayed(const Duration(seconds: 1), () { - _logger.finest('sane_openDevice()'); - return SaneHandle(deviceName: device.name); + _logger.finest('sane_close()'); }); } @override - Future read(SaneHandle handle, int bufferSize) { + String get model => 'Model $index'; + + @override + String get name => 'Name $index'; + + @override + Future read({required int bufferSize}) { return Future.delayed(const Duration(seconds: 1), () { _logger.finest('sane_read()'); return Uint8List.fromList([]); @@ -201,16 +181,15 @@ class SaneDev implements Sane { } @override - Future setIOMode(SaneHandle handle, SaneIOMode mode) { + Future start() { return Future.delayed(const Duration(seconds: 1), () { - _logger.finest('sane_setIOMode()'); + _logger.finest('sane_start()'); }); } @override - Future start(SaneHandle handle) { - return Future.delayed(const Duration(seconds: 1), () { - _logger.finest('sane_start()'); - }); - } + String get type => 'Type $index'; + + @override + String? get vendor => 'Vendor $index'; } diff --git a/packages/sane/lib/src/impl/sane_native.dart b/packages/sane/lib/src/impl/sane_native.dart index 65a674a..94a214f 100644 --- a/packages/sane/lib/src/impl/sane_native.dart +++ b/packages/sane/lib/src/impl/sane_native.dart @@ -1,126 +1,89 @@ -import 'dart:isolate'; +import 'dart:async'; import 'dart:typed_data'; -import 'package:sane/sane.dart'; +import 'package:sane/src/exceptions.dart'; +import 'package:sane/src/isolate.dart'; import 'package:sane/src/isolate_messages/cancel.dart'; import 'package:sane/src/isolate_messages/close.dart'; import 'package:sane/src/isolate_messages/control_button_option.dart'; import 'package:sane/src/isolate_messages/control_option.dart'; -import 'package:sane/src/isolate_messages/exception.dart'; import 'package:sane/src/isolate_messages/exit.dart'; import 'package:sane/src/isolate_messages/get_all_option_descriptors.dart'; import 'package:sane/src/isolate_messages/get_devices.dart'; import 'package:sane/src/isolate_messages/get_option_descriptor.dart'; import 'package:sane/src/isolate_messages/get_parameters.dart'; import 'package:sane/src/isolate_messages/init.dart'; -import 'package:sane/src/isolate_messages/interface.dart'; import 'package:sane/src/isolate_messages/open.dart'; import 'package:sane/src/isolate_messages/read.dart'; -import 'package:sane/src/isolate_messages/set_io_mode.dart'; import 'package:sane/src/isolate_messages/start.dart'; +import 'package:sane/src/sane.dart'; +import 'package:sane/src/structures.dart'; -class SaneNative implements Sane { - SaneNative({ - required Sane sane, - }) : _sane = sane; - - final Sane _sane; - - late final Isolate _isolate; - late final SendPort _sendPort; - - Future spawn() async { - final receivePort = ReceivePort(); - _isolate = await Isolate.spawn( - _isolateEntryPoint, - _IsolateEntryPointArgs( - mainSendPort: receivePort.sendPort, - sane: _sane, - ), - ); - _sendPort = await receivePort.first as SendPort; - } - - void kill() { - _isolate.kill(priority: Isolate.immediate); - } +class NativeSane implements Sane { + factory NativeSane() => _instance ??= NativeSane._(); - Future _sendMessage( - IsolateMessage message, - ) async { - final replyPort = ReceivePort(); + NativeSane._(); - _sendPort.send( - _IsolateMessageEnvelope( - replyPort: replyPort.sendPort, - message: message, - ), - ); + static NativeSane? _instance; - final response = await replyPort.first; - replyPort.close(); - - if (response is ExceptionResponse) { - Error.throwWithStackTrace( - response.exception, - response.stackTrace, - ); - } + bool get _disposed => _isolate?.exited == true; + SaneIsolate? _isolate; - return response as T; + Future _getIsolate() async { + if (_isolate?.exited == true) throw SaneDisposedError(); + return _isolate ??= await SaneIsolate.spawn(); } - @override Future init({ AuthCallback? authCallback, }) async { - final response = await _sendMessage(InitMessage()); + final isolate = await _getIsolate(); + final response = await isolate.sendMessage(InitMessage()); return response.versionCode; } @override - Future exit() async { - await _sendMessage(ExitMessage()); + Future dispose({bool force = false}) async { + if (force) { + _isolate?.kill(); + return; + } + + if (_disposed) return; + + await _isolate?.sendMessage(ExitMessage()); } @override Future> getDevices({ required bool localOnly, }) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( GetDevicesMessage(localOnly: localOnly), ); return response.devices; } - @override - Future open(String deviceName) async { - final response = await _sendMessage( + Future open(String deviceName) async { + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( OpenMessage(deviceName: deviceName), ); return response.handle; } - @override - Future openDevice(SaneDevice device) { + Future openDevice(SaneDevice device) { return open(device.name); } - @override - Future close(SaneHandle handle) async { - await _sendMessage( - CloseMessage(saneHandle: handle), - ); - } - - @override - Future getOptionDescriptor( - SaneHandle handle, + Future getOptionDescriptor(int handle, int index, ) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( GetOptionDescriptorMessage( saneHandle: handle, index: index, @@ -130,25 +93,24 @@ class SaneNative implements Sane { return response.optionDescriptor; } - @override - Future> getAllOptionDescriptors( - SaneHandle handle, + Future> getAllOptionDescriptors(int handle, ) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( GetAllOptionDescriptorsMessage(saneHandle: handle), ); return response.optionDescriptors; } - @override Future> controlBoolOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, bool? value, }) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( ControlValueOptionMessage( saneHandle: handle, index: index, @@ -160,14 +122,14 @@ class SaneNative implements Sane { return response.result; } - @override Future> controlIntOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, int? value, }) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( ControlValueOptionMessage( saneHandle: handle, index: index, @@ -179,14 +141,14 @@ class SaneNative implements Sane { return response.result; } - @override Future> controlFixedOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, double? value, }) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( ControlValueOptionMessage( saneHandle: handle, index: index, @@ -198,14 +160,14 @@ class SaneNative implements Sane { return response.result; } - @override Future> controlStringOption({ - required SaneHandle handle, + required int handle, required int index, required SaneAction action, String? value, }) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( ControlValueOptionMessage( saneHandle: handle, index: index, @@ -217,12 +179,12 @@ class SaneNative implements Sane { return response.result; } - @override Future> controlButtonOption({ - required SaneHandle handle, + required int handle, required int index, }) async { - final response = await _sendMessage( + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( ControlButtonOptionMessage( saneHandle: handle, index: index, @@ -232,9 +194,9 @@ class SaneNative implements Sane { return response.result; } - @override - Future getParameters(SaneHandle handle) async { - final response = await _sendMessage( + Future getParameters(int handle) async { + final isolate = await _getIsolate(); + final response = await isolate.sendMessage( GetParametersMessage( saneHandle: handle, ), @@ -243,80 +205,80 @@ class SaneNative implements Sane { return response.parameters; } - @override - Future start(SaneHandle handle) async { - await _sendMessage( + Future start(int handle) async { + final isolate = await _getIsolate(); + await isolate.sendMessage( StartMessage(saneHandle: handle), ); } +} + +class NativeSaneDevice implements SaneDevice { + NativeSaneDevice({ + required NativeSane sane, + required this.name, + required this.type, + required this.vendor, + required this.model, + }) : _sane = sane; + + final NativeSane _sane; + + bool _closed = false; + + int? _handlePointer; @override - Future read(SaneHandle handle, int bufferSize) async { - final response = await _sendMessage( - ReadMessage( - saneHandle: handle, - bufferSize: bufferSize, - ), - ); + final String name; - return response.bytes; - } + @override + final String type; @override - Future cancel(SaneHandle handle) async { - await _sendMessage( - CancelMessage(saneHandle: handle), - ); - } + final String? vendor; @override - Future setIOMode( - SaneHandle handle, - SaneIOMode ioMode, - ) async { - await _sendMessage( - SetIOModeMessage(saneHandle: handle, ioMode: ioMode), - ); + final String model; + + @override + Future cancel() async { + if (_closed) return; + + final isolate = _sane._isolate; + + if (isolate == null || _handlePointer == null) return; + + final message = CancelMessage(_handlePointer!); + await isolate.sendMessage(message); } -} -class _IsolateEntryPointArgs { - _IsolateEntryPointArgs({ - required this.mainSendPort, - required this.sane, - }); + @override + Future close() async { + if (_closed) return; - final SendPort mainSendPort; - final Sane sane; -} + _closed = true; -void _isolateEntryPoint(_IsolateEntryPointArgs args) { - final isolateReceivePort = ReceivePort(); - args.mainSendPort.send(isolateReceivePort.sendPort); - - final sane = args.sane; - isolateReceivePort.cast<_IsolateMessageEnvelope>().listen((envelope) async { - late IsolateResponse response; - - try { - response = await envelope.message.handle(sane); - } on SaneException catch (exception, stackTrace) { - response = ExceptionResponse( - exception: exception, - stackTrace: stackTrace, - ); - } + final isolate = _sane._isolate; - envelope.replyPort.send(response); - }); -} + if (isolate == null || _handlePointer == null) return; + + final message = CloseMessage(_handlePointer!); + await isolate.sendMessage(message); + } -class _IsolateMessageEnvelope { - _IsolateMessageEnvelope({ - required this.replyPort, - required this.message, - }); + @override + Future read({required int bufferSize}) async { + final isolate = await _sane._getIsolate(); + final response = await isolate.sendMessage( + ReadMessage( + bufferSize: bufferSize, + saneHandle: _handle, + ), + ); - final SendPort replyPort; - final IsolateMessage message; + return response.bytes; + } + + @override + FutureOr start() {} } diff --git a/packages/sane/lib/src/impl/sane_sync.dart b/packages/sane/lib/src/impl/sane_sync.dart index d72532c..13b2209 100644 --- a/packages/sane/lib/src/impl/sane_sync.dart +++ b/packages/sane/lib/src/impl/sane_sync.dart @@ -1,36 +1,27 @@ import 'dart:async'; import 'dart:ffi' as ffi; +import 'dart:ffi'; import 'dart:typed_data'; import 'package:ffi/ffi.dart' as ffi; +import 'package:sane/sane.dart'; import 'package:sane/src/bindings.g.dart'; import 'package:sane/src/dylib.dart'; -import 'package:sane/src/exceptions.dart'; import 'package:sane/src/extensions.dart'; import 'package:sane/src/logger.dart'; -import 'package:sane/src/structures.dart'; +import 'package:sane/src/sane.dart'; import 'package:sane/src/type_conversion.dart'; -import 'package:sane/src/utils.dart'; -typedef AuthCallback = SaneCredentials Function(String resourceName); +class SaneSync implements Sane { + factory SaneSync() => _instance ??= SaneSync._(); -class Sane { - factory Sane() => _instance ??= Sane._(); + SaneSync._(); - Sane._(); + static SaneSync? _instance; + bool _disposed = false; - static Sane? _instance; - bool _exited = false; - final Map _nativeHandles = {}; - - SANE_Handle _getNativeHandle(SaneHandle handle) => _nativeHandles[handle]!; - - Future init({ - AuthCallback? authCallback, - }) { - _checkIfExited(); - - final completer = Completer(); + int init({AuthCallback? authCallback}) { + _checkIfDisposed(); void authCallbackAdapter( SANE_String_Const resource, @@ -50,39 +41,40 @@ class Sane { } } - Future(() { - final versionCodePointer = ffi.calloc(); - final nativeAuthCallback = authCallback != null - ? ffi.NativeCallable.isolateLocal( - authCallbackAdapter, - ).nativeFunction - : ffi.nullptr; + final versionCodePointer = ffi.calloc(); + final nativeAuthCallback = authCallback != null + ? ffi.NativeCallable.isolateLocal( + authCallbackAdapter, + ).nativeFunction + : ffi.nullptr; + try { final status = dylib.sane_init(versionCodePointer, nativeAuthCallback); + logger.finest('sane_init() -> ${status.name}'); status.check(); final versionCode = versionCodePointer.value; + logger.finest( - 'SANE version: ${SaneUtils.version(versionCodePointer.value)}', + 'SANE version: ${SaneUtils.version(versionCode)}', ); + return versionCode; + } finally { ffi.calloc.free(versionCodePointer); ffi.calloc.free(nativeAuthCallback); - - completer.complete(versionCode); - }); - - return completer.future; + } } - Future exit() { - if (_exited) return Future.value(); + @override + Future dispose() { + if (_disposed) return Future.value(); final completer = Completer(); Future(() { - _exited = true; + _disposed = true; dylib.sane_exit(); logger.finest('sane_exit()'); @@ -95,99 +87,46 @@ class Sane { return completer.future; } - Future> getDevices({ - required bool localOnly, - }) { - _checkIfExited(); + @override + List getDevices({required bool localOnly}) { + _checkIfDisposed(); - final completer = Completer>(); + final deviceListPointer = + ffi.calloc>>(); - Future(() { - final deviceListPointer = - ffi.calloc>>(); + try { final status = dylib.sane_get_devices( deviceListPointer, - saneBoolFromDartBool(localOnly), + localOnly.asSaneBool, ); logger.finest('sane_get_devices() -> ${status.name}'); status.check(); - final devices = []; + final devices = []; + for (var i = 0; deviceListPointer.value[i] != ffi.nullptr; i++) { - final nativeDevice = deviceListPointer.value[i].ref; - devices.add(saneDeviceFromNative(nativeDevice)); + final device = deviceListPointer.value[i].ref; + devices.add(SyncSaneDevice(device)); } + return List.unmodifiable(devices); + } finally { ffi.calloc.free(deviceListPointer); - - completer.complete(devices); - }); - - return completer.future; - } - - Future open(String deviceName) { - _checkIfExited(); - - final completer = Completer(); - - Future(() { - final nativeHandlePointer = ffi.calloc(); - final deviceNamePointer = saneStringFromDartString(deviceName); - final status = dylib.sane_open(deviceNamePointer, nativeHandlePointer); - logger.finest('sane_open() -> ${status.name}'); - - status.check(); - - final handle = SaneHandle(deviceName: deviceName); - _nativeHandles.addAll({ - handle: nativeHandlePointer.value, - }); - - ffi.calloc.free(nativeHandlePointer); - ffi.calloc.free(deviceNamePointer); - - completer.complete(handle); - }); - - return completer.future; - } - - Future openDevice(SaneDevice device) { - _checkIfExited(); - - return open(device.name); - } - - Future close(SaneHandle handle) { - _checkIfExited(); - - final completer = Completer(); - - Future(() { - dylib.sane_close(_getNativeHandle(handle)); - _nativeHandles.remove(handle); - logger.finest('sane_close()'); - - completer.complete(); - }); - - return completer.future; + } } - Future getOptionDescriptor( - SaneHandle handle, + Future getOptionDescriptor(SANE_Handle handle, int index, ) { - _checkIfExited(); + _checkIfDisposed(); final completer = Completer(); Future(() { final optionDescriptorPointer = - dylib.sane_get_option_descriptor(_getNativeHandle(handle), index); + dylib.sane_get_option_descriptor(handle, index); final optionDescriptor = saneOptionDescriptorFromNative( optionDescriptorPointer.ref, index, @@ -201,10 +140,9 @@ class Sane { return completer.future; } - Future> getAllOptionDescriptors( - SaneHandle handle, + Future> getAllOptionDescriptors(SANE_Handle handle, ) { - _checkIfExited(); + _checkIfDisposed(); final completer = Completer>(); @@ -212,8 +150,7 @@ class Sane { final optionDescriptors = []; for (var i = 0; true; i++) { - final descriptorPointer = - dylib.sane_get_option_descriptor(_getNativeHandle(handle), i); + final descriptorPointer = dylib.sane_get_option_descriptor(handle, i); if (descriptorPointer == ffi.nullptr) break; optionDescriptors.add( saneOptionDescriptorFromNative(descriptorPointer.ref, i), @@ -227,18 +164,18 @@ class Sane { } Future> _controlOption({ - required SaneHandle handle, + required SANE_Handle handle, required int index, required SaneAction action, T? value, }) { - _checkIfExited(); + _checkIfDisposed(); final completer = Completer>(); Future(() { final optionDescriptor = saneOptionDescriptorFromNative( - dylib.sane_get_option_descriptor(_getNativeHandle(handle), index).ref, + dylib.sane_get_option_descriptor(handle, index).ref, index, ); final optionType = optionDescriptor.type; @@ -271,8 +208,7 @@ class Sane { switch (optionType) { case SaneOptionValueType.bool: if (value is! bool) continue invalid; - (valuePointer as ffi.Pointer).value = - saneBoolFromDartBool(value); + (valuePointer as ffi.Pointer).value = value.asSaneBool; break; case SaneOptionValueType.int: @@ -288,8 +224,8 @@ class Sane { case SaneOptionValueType.string: if (value is! String) continue invalid; - (valuePointer as ffi.Pointer).value = - saneStringFromDartString(value).value; + (valuePointer as ffi.Pointer).value = + value.toSaneString(); break; case SaneOptionValueType.button: @@ -305,7 +241,7 @@ class Sane { } final status = dylib.sane_control_option( - _getNativeHandle(handle), + handle, index, nativeSaneActionFromDart(action), valuePointer.cast(), @@ -360,7 +296,7 @@ class Sane { } Future> controlBoolOption({ - required SaneHandle handle, + required SANE_Handle handle, required int index, required SaneAction action, bool? value, @@ -374,7 +310,7 @@ class Sane { } Future> controlIntOption({ - required SaneHandle handle, + required SANE_Handle handle, required int index, required SaneAction action, int? value, @@ -388,7 +324,7 @@ class Sane { } Future> controlFixedOption({ - required SaneHandle handle, + required SANE_Handle handle, required int index, required SaneAction action, double? value, @@ -402,7 +338,7 @@ class Sane { } Future> controlStringOption({ - required SaneHandle handle, + required SANE_Handle handle, required int index, required SaneAction action, String? value, @@ -416,7 +352,7 @@ class Sane { } Future> controlButtonOption({ - required SaneHandle handle, + required SANE_Handle handle, required int index, }) { return _controlOption( @@ -427,15 +363,15 @@ class Sane { ); } - Future getParameters(SaneHandle handle) { - _checkIfExited(); + Future getParameters(SANE_Handle handle) { + _checkIfDisposed(); final completer = Completer(); Future(() { final nativeParametersPointer = ffi.calloc(); final status = dylib.sane_get_parameters( - _getNativeHandle(handle), + handle, nativeParametersPointer, ); logger.finest('sane_get_parameters() -> ${status.name}'); @@ -452,95 +388,123 @@ class Sane { return completer.future; } - Future start(SaneHandle handle) { - _checkIfExited(); + @pragma('vm:prefer-inline') + void _checkIfDisposed() { + if (_disposed) throw SaneDisposedError(); + } +} - final completer = Completer(); +class SyncSaneDevice implements SaneDevice, ffi.Finalizable { + factory SyncSaneDevice(SANE_Device device) { + final vendor = device.vendor.toDartString(); + return SyncSaneDevice._( + name: device.name.toDartString(), + vendor: vendor == 'Noname' ? null : vendor, + type: device.type.toDartString(), + model: device.model.toDartString(), + ); + } - Future(() { - final status = dylib.sane_start(_getNativeHandle(handle)); - logger.finest('sane_start() -> ${status.name}'); + SyncSaneDevice._({ + required this.name, + required this.vendor, + required this.model, + required this.type, + }); - status.check(); + static final _finalizer = ffi.NativeFinalizer(dylib.addresses.sane_close); - completer.complete(); - }); + SANE_Handle? _handle; - return completer.future; - } + bool _closed = false; - Future read(SaneHandle handle, int bufferSize) { - _checkIfExited(); + @override + final String name; - final completer = Completer(); + @override + final String type; - Future(() { - final bytesReadPointer = ffi.calloc(); - final bufferPointer = ffi.calloc(bufferSize); - - final status = dylib.sane_read( - _getNativeHandle(handle), - bufferPointer, - bufferSize, - bytesReadPointer, - ); - logger.finest('sane_read() -> ${status.name}'); + @override + final String? vendor; - status.check(); + @override + final String model; - final bytes = Uint8List.fromList( - List.generate( - bytesReadPointer.value, - (i) => (bufferPointer + i).value, - ), - ); + @override + void cancel() { + _checkIfDisposed(); - ffi.calloc.free(bytesReadPointer); - ffi.calloc.free(bufferPointer); + final handle = _handle; - completer.complete(bytes); - }); + if (handle == null) return; - return completer.future; + dylib.sane_cancel(handle); } - Future cancel(SaneHandle handle) { - _checkIfExited(); + SANE_Handle _open() { + final namePointer = name.toSaneString(); + final handlePointer = ffi.calloc.allocate( + ffi.sizeOf(), + ); - final completer = Completer(); + try { + dylib.sane_open(namePointer, handlePointer).check(); + final handle = handlePointer.value; + _finalizer.attach(this, handle); + return handle; + } finally { + ffi.calloc.free(namePointer); + ffi.calloc.free(handlePointer); + } + } - Future(() { - dylib.sane_cancel(_getNativeHandle(handle)); - logger.finest('sane_cancel()'); + @override + void close() { + if (_closed) return; - completer.complete(); - }); + _closed = true; - return completer.future; + if (_handle == null) return; + + _finalizer.detach(this); + dylib.sane_close(_handle!); } - Future setIOMode(SaneHandle handle, SaneIOMode mode) { - _checkIfExited(); + @override + Uint8List read({required int bufferSize}) { + _checkIfDisposed(); - final completer = Completer(); + final handle = _handle ??= _open(); - Future(() { - final status = dylib.sane_set_io_mode( - _getNativeHandle(handle), - saneBoolFromIOMode(mode), - ); - logger.finest('sane_set_io_mode() -> ${status.name}'); + final lengthPointer = ffi.calloc(); + final bufferPointer = ffi.calloc(bufferSize); - status.check(); + try { + dylib.sane_read(handle, bufferPointer, bufferSize, lengthPointer).check(); - completer.complete(); - }); + logger.finest('sane_read()'); - return completer.future; + final length = lengthPointer.value; + final buffer = bufferPointer.cast().asTypedList(length); + + return buffer; + } finally { + ffi.calloc.free(lengthPointer); + ffi.calloc.free(bufferPointer); + } + } + + @override + void start() { + _checkIfDisposed(); + + final handle = _handle ??= _open(); + + dylib.sane_start(handle).check(); } @pragma('vm:prefer-inline') - void _checkIfExited() { - if (_exited) throw SaneDisposedError(); + void _checkIfDisposed() { + if (_closed) throw SaneDisposedError(); } } diff --git a/packages/sane/lib/src/isolate.dart b/packages/sane/lib/src/isolate.dart new file mode 100644 index 0000000..25cd327 --- /dev/null +++ b/packages/sane/lib/src/isolate.dart @@ -0,0 +1,103 @@ +import 'dart:async'; +import 'dart:isolate'; + +import 'package:sane/src/exceptions.dart'; +import 'package:sane/src/impl/sane_sync.dart'; +import 'package:sane/src/isolate_messages/exception.dart'; +import 'package:sane/src/isolate_messages/interface.dart'; + +class SaneIsolate { + SaneIsolate._( + this._isolate, + this._sendPort, + this._exitReceivePort, + ) : _exited = false { + _exitReceivePort.listen((message) { + assert(message == null); + _exited = true; + }); + } + + final Isolate _isolate; + final SendPort _sendPort; + final ReceivePort _exitReceivePort; + + bool _exited; + + bool get exited => _exited; + + static Future spawn() async { + final receivePort = ReceivePort(); + final exitReceivePort = ReceivePort(); + + final isolate = await Isolate.spawn( + _entryPoint, + receivePort.sendPort, + onExit: exitReceivePort.sendPort, + ); + + final sendPort = await receivePort.first as SendPort; + return SaneIsolate._(isolate, sendPort, exitReceivePort); + } + + void kill() { + _isolate.kill(priority: Isolate.immediate); + } + + Future sendMessage( + IsolateMessage message, + ) async { + final replyPort = ReceivePort(); + + _sendPort.send( + _IsolateMessageEnvelope( + replyPort: replyPort.sendPort, + message: message, + ), + ); + + final response = await replyPort.first; + replyPort.close(); + + if (response is ExceptionResponse) { + Error.throwWithStackTrace( + response.exception, + response.stackTrace, + ); + } + + return response as T; + } +} + +void _entryPoint(SendPort sendPort) { + final sane = SaneSync(); + + final receivePort = ReceivePort(); + sendPort.send(receivePort.sendPort); + + receivePort.cast<_IsolateMessageEnvelope>().listen((envelope) async { + late IsolateResponse response; + + try { + response = await envelope.message.handle(sane); + } on SaneException catch (exception, stackTrace) { + response = ExceptionResponse( + exception: exception, + stackTrace: stackTrace, + ); + } + + envelope.replyPort.send(response); + }); +} + +class _IsolateMessageEnvelope { + _IsolateMessageEnvelope({ + required this.replyPort, + required this.message, + }); + + final SendPort replyPort; + final IsolateMessage message; +} diff --git a/packages/sane/lib/src/isolate_messages/cancel.dart b/packages/sane/lib/src/isolate_messages/cancel.dart index a4bb836..884062e 100644 --- a/packages/sane/lib/src/isolate_messages/cancel.dart +++ b/packages/sane/lib/src/isolate_messages/cancel.dart @@ -1,14 +1,13 @@ import 'package:sane/src/impl/sane_sync.dart'; import 'package:sane/src/isolate_messages/interface.dart'; -import 'package:sane/src/structures.dart'; class CancelMessage implements IsolateMessage { - CancelMessage({required this.saneHandle}); + CancelMessage(this.saneHandle); - final SaneHandle saneHandle; + final int saneHandle; @override - Future handle(Sane sane) async { + Future handle(SaneSync sane) async { await sane.cancel(saneHandle); return CancelResponse(); } diff --git a/packages/sane/lib/src/isolate_messages/close.dart b/packages/sane/lib/src/isolate_messages/close.dart index 028f854..c660976 100644 --- a/packages/sane/lib/src/isolate_messages/close.dart +++ b/packages/sane/lib/src/isolate_messages/close.dart @@ -2,12 +2,12 @@ import 'package:sane/sane.dart'; import 'package:sane/src/isolate_messages/interface.dart'; class CloseMessage implements IsolateMessage { - CloseMessage({required this.saneHandle}); + CloseMessage(this.saneHandle); - final SaneHandle saneHandle; + final int saneHandle; @override - Future handle(Sane sane) async { + Future handle(SaneSync sane) async { await sane.close(saneHandle); return CloseResponse(); } diff --git a/packages/sane/lib/src/isolate_messages/control_button_option.dart b/packages/sane/lib/src/isolate_messages/control_button_option.dart index 2f6c8ad..6875daf 100644 --- a/packages/sane/lib/src/isolate_messages/control_button_option.dart +++ b/packages/sane/lib/src/isolate_messages/control_button_option.dart @@ -9,7 +9,7 @@ class ControlButtonOptionMessage required this.index, }); - final SaneHandle saneHandle; + final int saneHandle; final int index; @override diff --git a/packages/sane/lib/src/isolate_messages/control_option.dart b/packages/sane/lib/src/isolate_messages/control_option.dart index d886e36..816e146 100644 --- a/packages/sane/lib/src/isolate_messages/control_option.dart +++ b/packages/sane/lib/src/isolate_messages/control_option.dart @@ -11,7 +11,7 @@ class ControlValueOptionMessage this.value, }); - final SaneHandle saneHandle; + final int saneHandle; final int index; final SaneAction action; final T? value; diff --git a/packages/sane/lib/src/isolate_messages/get_all_option_descriptors.dart b/packages/sane/lib/src/isolate_messages/get_all_option_descriptors.dart index e1c2c8e..74fdad8 100644 --- a/packages/sane/lib/src/isolate_messages/get_all_option_descriptors.dart +++ b/packages/sane/lib/src/isolate_messages/get_all_option_descriptors.dart @@ -6,7 +6,7 @@ class GetAllOptionDescriptorsMessage implements IsolateMessage { GetAllOptionDescriptorsMessage({required this.saneHandle}); - final SaneHandle saneHandle; + final int saneHandle; @override Future handle(Sane sane) async { diff --git a/packages/sane/lib/src/isolate_messages/get_option_descriptor.dart b/packages/sane/lib/src/isolate_messages/get_option_descriptor.dart index 8d6347d..677152a 100644 --- a/packages/sane/lib/src/isolate_messages/get_option_descriptor.dart +++ b/packages/sane/lib/src/isolate_messages/get_option_descriptor.dart @@ -9,7 +9,7 @@ class GetOptionDescriptorMessage required this.index, }); - final SaneHandle saneHandle; + final int saneHandle; final int index; @override diff --git a/packages/sane/lib/src/isolate_messages/get_parameters.dart b/packages/sane/lib/src/isolate_messages/get_parameters.dart index 009a81a..40f713b 100644 --- a/packages/sane/lib/src/isolate_messages/get_parameters.dart +++ b/packages/sane/lib/src/isolate_messages/get_parameters.dart @@ -5,7 +5,7 @@ import 'package:sane/src/structures.dart'; class GetParametersMessage implements IsolateMessage { GetParametersMessage({required this.saneHandle}); - final SaneHandle saneHandle; + final int saneHandle; @override Future handle(Sane sane) async { diff --git a/packages/sane/lib/src/isolate_messages/open.dart b/packages/sane/lib/src/isolate_messages/open.dart index 81f25e9..20f278e 100644 --- a/packages/sane/lib/src/isolate_messages/open.dart +++ b/packages/sane/lib/src/isolate_messages/open.dart @@ -18,5 +18,5 @@ class OpenMessage implements IsolateMessage { class OpenResponse implements IsolateResponse { OpenResponse({required this.handle}); - final SaneHandle handle; + final int handle; } diff --git a/packages/sane/lib/src/isolate_messages/read.dart b/packages/sane/lib/src/isolate_messages/read.dart index cef282d..f438ec7 100644 --- a/packages/sane/lib/src/isolate_messages/read.dart +++ b/packages/sane/lib/src/isolate_messages/read.dart @@ -10,7 +10,7 @@ class ReadMessage implements IsolateMessage { required this.bufferSize, }); - final SaneHandle saneHandle; + final int saneHandle; final int bufferSize; @override diff --git a/packages/sane/lib/src/isolate_messages/set_io_mode.dart b/packages/sane/lib/src/isolate_messages/set_io_mode.dart deleted file mode 100644 index 802caaa..0000000 --- a/packages/sane/lib/src/isolate_messages/set_io_mode.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:sane/src/impl/sane_sync.dart'; -import 'package:sane/src/isolate_messages/interface.dart'; -import 'package:sane/src/structures.dart'; - -class SetIOModeMessage implements IsolateMessage { - SetIOModeMessage({ - required this.saneHandle, - required this.ioMode, - }); - - final SaneHandle saneHandle; - final SaneIOMode ioMode; - - @override - Future handle(Sane sane) async { - await sane.setIOMode(saneHandle, ioMode); - return SetIOModeResponse(); - } -} - -class SetIOModeResponse implements IsolateResponse {} diff --git a/packages/sane/lib/src/isolate_messages/start.dart b/packages/sane/lib/src/isolate_messages/start.dart index 1212705..1713782 100644 --- a/packages/sane/lib/src/isolate_messages/start.dart +++ b/packages/sane/lib/src/isolate_messages/start.dart @@ -5,7 +5,7 @@ import 'package:sane/src/structures.dart'; class StartMessage implements IsolateMessage { StartMessage({required this.saneHandle}); - final SaneHandle saneHandle; + final int saneHandle; @override Future handle(Sane sane) async { diff --git a/packages/sane/lib/src/sane.dart b/packages/sane/lib/src/sane.dart new file mode 100644 index 0000000..268a1ca --- /dev/null +++ b/packages/sane/lib/src/sane.dart @@ -0,0 +1,142 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:sane/sane.dart'; + +typedef AuthCallback = SaneCredentials Function(String resourceName); + +abstract interface class Sane { + /// Instantiates a new asynchronous SANE instance. + /// + /// See also: + /// + /// - [Sane.sync] + factory Sane() => NativeSane(); + + /// Instantiates a new synchronous SANE instance. + factory Sane.sync() => SaneSync(); + + /// Disposes the SANE instance. + /// + /// Closes all device handles and all future calls are invalid. + /// + /// See also: + /// + /// - [`sane_exit`](https://sane-project.gitlab.io/standard/api.html#sane-exit) + void dispose(); + + /// Queries the list of devices that are available. + /// + /// This method can be called repeatedly to detect when new devices become + /// available. If argument [localOnly] is true, only local devices are + /// returned (devices directly attached to the machine that SANE is running + /// on). If it is `false`, the device list includes all remote devices that + /// are accessible to the SANE library. + /// + /// See also: + /// + /// - [`sane_get_devices`](https://sane-project.gitlab.io/standard/api.html#sane-get-devices) + FutureOr> getDevices({required bool localOnly}); +} + +/// Represents a SANE device. +/// +/// Devices can be retrieved using [Sane.getDevices]. +/// +/// See also: +/// +/// - [Device Descriptor Type](https://sane-project.gitlab.io/standard/api.html#device-descriptor-type) +abstract interface class SaneDevice { + /// The name of the device. + String get name; + + /// The type of the device. + /// + /// For a list of predefined types, see [SaneDeviceTypes]. + String get type; + + /// The vendor (manufacturer) of the device. + /// + /// Can be `null` for virtual devices that have no physical vendor associated. + String? get vendor; + + /// The model of the device. + String get model; + + /// Disposes the SANE device. Infers [cancel]. + /// + /// See also: + /// + /// - [`sane_close`](https://sane-project.gitlab.io/standard/api.html#sane-close) + FutureOr close(); + + /// Tries to cancel the currently pending operation of the device immediately + /// or as quickly as possible. + /// + /// See also: + /// + /// - [`sane_cancel`](https://sane-project.gitlab.io/standard/api.html#sane-cancel) + FutureOr cancel(); + + /// Reads image date from the device. + /// + /// Exceptions: + /// + /// - Throws [SaneCancelledException] if the operation was cancelled through + /// a call to [cancel]. + /// - Throws [SaneJammedException] if the document feeder is jammed. + /// - Throws [SaneNoDocumentsException] if the document feeder is out of + /// documents. + /// - Throws [SaneCoverOpenException] if the scanner cover is open. + /// - Throws [SaneIoException] if an error occurred while communicating with + /// the device. + /// - Throws [SaneNoMemoryException] if no memory is available. + /// - Throws [SaneAccessDeniedException] if access to the device has been + /// denied due to insufficient or invalid authentication. + /// + /// See also: + /// + /// - [`sane_read`](https://sane-project.gitlab.io/standard/api.html#sane-read) + FutureOr read({required int bufferSize}); + + /// Initiates acquisition of an image from the device. + /// + /// Exceptions: + /// + /// - Throws [SaneCancelledException] if the operation was cancelled through + /// a call to [cancel]. + /// - Throws [SaneDeviceBusyException] if the device is busy. The operation + /// should be later again. + /// - Throws [SaneJammedException] if the document feeder is jammed. + /// - Throws [SaneNoDocumentsException] if the document feeder is out of + /// documents. + /// - Throws [SaneCoverOpenException] if the scanner cover is open. + /// - Throws [SaneIoException] if an error occurred while communicating with + /// the device. + /// - Throws [SaneNoMemoryException] if no memory is available. + /// - Throws [SaneInvalidDataException] if the sane cannot be started with the + /// current set of options. The frontend should reload the option + /// descriptors. + /// + /// See also: + /// + /// - [`sane_start`](https://sane-project.gitlab.io/standard/api.html#sane-start) + FutureOr start(); +} + +/// Predefined device types for [SaneDevice.type]. +/// +/// See also: +/// +/// - [Predefined Device Information Strings](https://sane-project.gitlab.io/standard/api.html#vendor-names) +abstract final class SaneDeviceTypes { + static const filmScanner = 'film scanner'; + static const flatbedScanner = 'flatbed scanner'; + static const frameGrabber = 'frame grabber'; + static const handheldScanner = 'handheld scanner'; + static const multiFunctionPeripheral = 'multi-function peripheral'; + static const sheetfedScanner = 'sheetfed scanner'; + static const stillCamera = 'still camera'; + static const videoCamera = 'video camera'; + static const virtualDevice = 'virtual device'; +} diff --git a/packages/sane/lib/src/structures.dart b/packages/sane/lib/src/structures.dart index e199a96..580f4f2 100644 --- a/packages/sane/lib/src/structures.dart +++ b/packages/sane/lib/src/structures.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - class SaneCredentials { SaneCredentials({ required this.username, @@ -10,33 +8,6 @@ class SaneCredentials { final String password; } -class SaneDevice { - SaneDevice({ - required this.name, - required this.vendor, - required this.model, - required this.type, - }); - - final String name; - final String vendor; - final String model; - final String type; -} - -@immutable -class SaneHandle { - const SaneHandle({required this.deviceName}); - final String deviceName; - - @override - bool operator ==(Object other) => - other is SaneHandle && other.deviceName == deviceName; - - @override - int get hashCode => deviceName.hashCode; -} - enum SaneFrameFormat { gray, rgb, @@ -170,8 +141,3 @@ class SaneOptionResult { final T result; final List infos; } - -enum SaneIOMode { - nonBlocking, - blocking; -} diff --git a/packages/sane/lib/src/type_conversion.dart b/packages/sane/lib/src/type_conversion.dart index 0371910..2b36a47 100644 --- a/packages/sane/lib/src/type_conversion.dart +++ b/packages/sane/lib/src/type_conversion.dart @@ -4,15 +4,6 @@ import 'package:ffi/ffi.dart' as ffi; import 'package:sane/src/bindings.g.dart'; import 'package:sane/src/structures.dart'; -SaneDevice saneDeviceFromNative(SANE_Device device) { - return SaneDevice( - name: dartStringFromSaneString(device.name) ?? '', - vendor: dartStringFromSaneString(device.vendor) ?? '', - model: dartStringFromSaneString(device.model) ?? '', - type: dartStringFromSaneString(device.type) ?? '', - ); -} - SaneFrameFormat saneFrameFormatFromNative(SANE_Frame frame) { return switch (frame) { SANE_Frame.FRAME_GRAY => SaneFrameFormat.gray, @@ -185,13 +176,6 @@ List saneOptionInfoFromNative(int bitset) { return infos; } -DartSANE_Word saneBoolFromIOMode(SaneIOMode mode) { - return switch (mode) { - SaneIOMode.blocking => SANE_FALSE, - SaneIOMode.nonBlocking => SANE_TRUE, - }; -} - bool dartBoolFromSaneBool(int bool) { switch (bool) { case 0: @@ -203,17 +187,26 @@ bool dartBoolFromSaneBool(int bool) { } } -DartSANE_Word saneBoolFromDartBool(bool bool) { - return bool ? SANE_TRUE : SANE_FALSE; -} - String? dartStringFromSaneString(SANE_String_Const stringPointer) { if (stringPointer == ffi.nullptr) return null; return stringPointer.cast().toDartString(); } +@Deprecated('Use extension') SANE_String_Const saneStringFromDartString(String string) { - return string.toNativeUtf8().cast(); + return string.toSaneString(); +} + +extension SaneStringExtensions on SANE_String_Const { + String toDartString() => cast().toDartString(); +} + +extension StringExtensions on String { + SANE_String_Const toSaneString() => toNativeUtf8().cast(); +} + +extension BoolExtensions on bool { + DartSANE_Word get asSaneBool => this ? SANE_TRUE : SANE_FALSE; } const int _saneFixedScaleFactor = 1 << SANE_FIXED_SCALE_SHIFT; diff --git a/packages/sane/pubspec.yaml b/packages/sane/pubspec.yaml index fccbe9b..a836bec 100644 --- a/packages/sane/pubspec.yaml +++ b/packages/sane/pubspec.yaml @@ -25,6 +25,10 @@ ffigen: headers: entry-points: - "/usr/include/sane/sane.h" + functions: + symbol-address: + include: + - sane_close enums: member-rename: ".*":