diff --git a/packages/sane/lib/src/exceptions.dart b/packages/sane/lib/src/exceptions.dart index 2863818..1ff0ae1 100644 --- a/packages/sane/lib/src/exceptions.dart +++ b/packages/sane/lib/src/exceptions.dart @@ -186,3 +186,8 @@ final class SaneUnsupportedException extends SaneException { @override SANE_Status get _status => SANE_Status.STATUS_UNSUPPORTED; } + +/// SANE has been exited or the device has been closed. +final class SaneDisposedError extends StateError { + SaneDisposedError() : super('SANE has been exited'); +} diff --git a/packages/sane/lib/src/sane.dart b/packages/sane/lib/src/sane.dart index c951b80..b71ac63 100644 --- a/packages/sane/lib/src/sane.dart +++ b/packages/sane/lib/src/sane.dart @@ -14,12 +14,21 @@ import 'package:sane/src/utils.dart'; typedef AuthCallback = SaneCredentials Function(String resourceName); class Sane { + factory Sane() => _instance ??= Sane._(); + + Sane._(); + + static Sane? _instance; + bool _exited = false; final Map _nativeHandles = {}; + SANE_Handle _getNativeHandle(SaneHandle handle) => _nativeHandles[handle]!; Future init({ AuthCallback? authCallback, }) { + _checkIfExited(); + final completer = Completer(); void authCallbackAdapter( @@ -65,13 +74,19 @@ class Sane { } Future exit() { + if (_exited) return Future.value(); + final completer = Completer(); Future(() { + _exited = true; + dylib.sane_exit(); print('sane_exit()'); completer.complete(); + + _instance = null; }); return completer.future; @@ -80,6 +95,8 @@ class Sane { Future> getDevices({ required bool localOnly, }) { + _checkIfExited(); + final completer = Completer>(); Future(() { @@ -108,6 +125,8 @@ class Sane { } Future open(String deviceName) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -133,10 +152,14 @@ class Sane { } Future openDevice(SaneDevice device) { + _checkIfExited(); + return open(device.name); } Future close(SaneHandle handle) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -154,6 +177,8 @@ class Sane { SaneHandle handle, int index, ) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -175,6 +200,8 @@ class Sane { Future> getAllOptionDescriptors( SaneHandle handle, ) { + _checkIfExited(); + final completer = Completer>(); Future(() { @@ -201,6 +228,8 @@ class Sane { required SaneAction action, T? value, }) { + _checkIfExited(); + final completer = Completer>(); Future(() { @@ -393,6 +422,8 @@ class Sane { } Future getParameters(SaneHandle handle) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -416,6 +447,8 @@ class Sane { } Future start(SaneHandle handle) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -431,6 +464,8 @@ class Sane { } Future read(SaneHandle handle, int bufferSize) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -464,6 +499,8 @@ class Sane { } Future cancel(SaneHandle handle) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -477,6 +514,8 @@ class Sane { } Future setIOMode(SaneHandle handle, SaneIOMode mode) { + _checkIfExited(); + final completer = Completer(); Future(() { @@ -493,4 +532,9 @@ class Sane { return completer.future; } + + @pragma('vm:prefer-inline') + void _checkIfExited() { + if (_exited) throw SaneDisposedError(); + } } diff --git a/packages/sane/test/sane_singleton_test.dart b/packages/sane/test/sane_singleton_test.dart new file mode 100644 index 0000000..25e81ca --- /dev/null +++ b/packages/sane/test/sane_singleton_test.dart @@ -0,0 +1,36 @@ +import 'package:sane/sane.dart'; +import 'package:test/test.dart'; + +void main() { + late Sane sane; + + test('can instantiate', () { + sane = Sane(); + }); + + test('same instance on repeated instantiation', () { + final newSane = Sane(); + expect(sane, equals(newSane)); + }); + + test('can exit', () { + expect(sane.exit, returnsNormally); + }); + + test('throws upon use', () { + expect( + () => sane.getDevices(localOnly: true), + throwsA(isA()), + ); + }); + + test('can reinstiate with new instance', () { + final newSane = Sane(); + expect(sane, isNot(newSane)); + sane = newSane; + }); + + test('doesn\'t throw upon use', () { + expect(() => sane.getDevices(localOnly: true), returnsNormally); + }); +}