openRead method

  1. @override
FileReadSession<Uint8List> openRead(
  1. String path, {
  2. int chunkSize = 64 * 1024,
  3. int start = 0,
})
override

Implementation

@override
FileReadSession<Uint8List> openRead(
  String path, {
  int chunkSize = 64 * 1024,
  int start = 0,
}) {
  if (path.isEmpty) {
    throw ArgumentError.value(path, 'path', 'path must not be empty');
  }
  if (chunkSize <= 0) {
    throw ArgumentError.value(
      chunkSize,
      'chunkSize',
      'chunkSize must be greater than zero',
    );
  }
  if (start < 0) {
    throw ArgumentError.value(start, 'start', 'start must not be negative');
  }

  if (!_forceNativeRead &&
      (Platform.isMacOS || Platform.isWindows || Platform.isLinux)) {
    return _openDesktopRead(path, chunkSize: chunkSize, start: start);
  }

  StreamSubscription<dynamic>? subscription;
  String? streamId;
  bool cancelled = false;
  bool closed = false;
  Future<void>? cancelFuture;

  late final StreamController<Uint8List> controller;
  late final Future<void> Function() cancelOnce;
  controller = StreamController<Uint8List>(
    onListen: () async {
      try {
        streamId = await methodChannel.invokeMethod<String>('startRead', {
          'path': path,
          'chunkSize': chunkSize,
          'start': start,
        });

        if (streamId == null || streamId!.isEmpty) {
          throw PlatformException(
            code: 'missing_stream_id',
            message: 'Native reader did not return a stream identifier.',
          );
        }

        if (cancelled) {
          await _cancelRead(streamId!);
          await _closeController(
            controller,
            alreadyClosed: () => closed,
            onClose: () => closed = true,
          );
          return;
        }

        subscription = EventChannel('$_readChannelPrefix/$streamId')
            .receiveBroadcastStream()
            .listen(
              (event) {
                if (event is Uint8List) {
                  controller.add(event);
                  return;
                }
                if (event is ByteData) {
                  controller.add(event.buffer.asUint8List());
                  return;
                }
                if (event is List && event.every((item) => item is int)) {
                  controller.add(Uint8List.fromList(event.cast<int>()));
                  return;
                }

                controller.addError(
                  PlatformException(
                    code: 'invalid_chunk',
                    message:
                        'Unexpected native chunk type: ${event.runtimeType}.',
                  ),
                );
              },
              onError: controller.addError,
              onDone: () async {
                await _closeController(
                  controller,
                  alreadyClosed: () => closed,
                  onClose: () => closed = true,
                );
              },
            );
      } catch (error, stackTrace) {
        controller.addError(error, stackTrace);
        await _closeController(
          controller,
          alreadyClosed: () => closed,
          onClose: () => closed = true,
        );
      }
    },
    onPause: () => subscription?.pause(),
    onResume: () => subscription?.resume(),
    onCancel: () => cancelOnce(),
  );

  cancelOnce = () {
    return cancelFuture ??= () async {
      cancelled = true;
      await subscription?.cancel();
      if (streamId != null && streamId!.isNotEmpty) {
        await _cancelRead(streamId!);
      }
      await _closeController(
        controller,
        alreadyClosed: () => closed,
        onClose: () => closed = true,
      );
    }();
  };

  return FileReadSession<Uint8List>(
    stream: controller.stream,
    onCancel: cancelOnce,
  );
}