diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3cc7ee8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024, Mgenware (Liu YuanYuan) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c63fec --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# random_access_source + +Shared interfaces for random access data + +## Usage + +This library defines a common interface for random access data. + +```dart +/// Base class for random access sources. +abstract class RandomAccessSource { + /// Gets the length of the source. + Future length(); + + /// Reads a byte from the source. + Future readByte(); + + /// Reads an array of bytes from the source. + Future read(int length); + + /// Gets the current position in the source. + Future position(); + + /// Sets the current position in the source. + Future setPosition(int position); + + /// Closes the source. + Future close(); +} +``` + +Implementations: + +- `BytesRASource` for `Uint8List` data +- `RandomAccessFileRASource` for `RandomAccessFile` data diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/example/random_access_source_example.dart b/example/random_access_source_example.dart new file mode 100644 index 0000000..b7f7f7b --- /dev/null +++ b/example/random_access_source_example.dart @@ -0,0 +1,12 @@ +import 'dart:typed_data'; + +import 'package:random_access_source/random_access_source.dart'; + +void main() async { + final source = BytesRASource(Uint8List.fromList([1, 2, 3, 4, 5])); + print(await source.length()); + print(await source.readByte()); + print(await source.read(2)); + print(await source.position()); + await source.close(); +} diff --git a/lib/random_access_source.dart b/lib/random_access_source.dart new file mode 100644 index 0000000..615b127 --- /dev/null +++ b/lib/random_access_source.dart @@ -0,0 +1,4 @@ +/// Shared interfaces for random access data. +library; + +export 'src/random_access_source_base.dart'; diff --git a/lib/src/random_access_source_base.dart b/lib/src/random_access_source_base.dart new file mode 100644 index 0000000..f354bdd --- /dev/null +++ b/lib/src/random_access_source_base.dart @@ -0,0 +1,108 @@ +import 'dart:io'; +import 'dart:typed_data'; + +/// Base class for random access sources. +abstract class RandomAccessSource { + /// Gets the length of the source. + Future length(); + + /// Reads a byte from the source. + Future readByte(); + + /// Reads an array of bytes from the source. + Future read(int length); + + /// Gets the current position in the source. + Future position(); + + /// Sets the current position in the source. + Future setPosition(int position); + + /// Closes the source. + Future close(); +} + +class BytesRASource extends RandomAccessSource { + final Uint8List _bytes; + int _position = 0; + + BytesRASource(this._bytes); + + @override + Future length() async { + return _bytes.length; + } + + @override + Future readByte() async { + if (_position >= _bytes.length) { + return -1; + } + return _bytes[_position++]; + } + + @override + Future read(int length) async { + if (_position >= _bytes.length) { + return Uint8List(0); + } + final end = _position + length; + final result = _bytes.sublist(_position, end.clamp(0, _bytes.length)); + _position = end; + return result; + } + + @override + Future position() async { + return _position; + } + + @override + Future setPosition(int position) async { + _position = position; + } + + @override + Future close() async {} +} + +class RandomAccessFileRASource extends RandomAccessSource { + final RandomAccessFile _file; + + RandomAccessFileRASource(this._file); + + static Future open(String path) async { + final file = await File(path).open(); + return RandomAccessFileRASource(file); + } + + @override + Future length() async { + return _file.length(); + } + + @override + Future readByte() async { + return _file.readByte(); + } + + @override + Future read(int length) async { + return _file.read(length); + } + + @override + Future position() async { + return _file.position(); + } + + @override + Future setPosition(int position) async { + await _file.setPosition(position); + } + + @override + Future close() async { + await _file.close(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..9403505 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,16 @@ +name: random_access_source +description: Shared interfaces for random access data. +version: 1.0.0 +repository: https://github.com/flutter-cavalry/random_access_source + +environment: + sdk: ^3.4.4 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 + tmp_path: ^1.3.1 diff --git a/test/bytes_test.dart b/test/bytes_test.dart new file mode 100644 index 0000000..aa7dfc2 --- /dev/null +++ b/test/bytes_test.dart @@ -0,0 +1,45 @@ +import 'dart:typed_data'; + +import 'package:random_access_source/random_access_source.dart'; +import 'package:test/test.dart'; + +Future _bytesSource() async { + return BytesRASource(Uint8List.fromList([1, 2, 3, 4, 5])); +} + +void main() { + test('Length', () async { + final src = await _bytesSource(); + expect(await src.length(), 5); + await src.close(); + }); + + test('ReadByte', () async { + final src = await _bytesSource(); + expect(await src.readByte(), 1); + expect(await src.readByte(), 2); + expect(await src.readByte(), 3); + expect(await src.readByte(), 4); + expect(await src.readByte(), 5); + expect(await src.readByte(), -1); + await src.close(); + }); + + test('Read', () async { + final src = await _bytesSource(); + expect(await src.read(2), Uint8List.fromList([1, 2])); + expect(await src.read(2), Uint8List.fromList([3, 4])); + expect(await src.read(2), Uint8List.fromList([5])); + expect(await src.read(2), Uint8List(0)); + await src.close(); + }); + + test('Position', () async { + final src = await _bytesSource(); + expect(await src.position(), 0); + await src.setPosition(2); + expect(await src.position(), 2); + expect(await src.readByte(), 3); + await src.close(); + }); +} diff --git a/test/raf_test.dart b/test/raf_test.dart new file mode 100644 index 0000000..5980ff7 --- /dev/null +++ b/test/raf_test.dart @@ -0,0 +1,54 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:random_access_source/random_access_source.dart'; +import 'package:test/test.dart'; +import 'package:tmp_path/tmp_path.dart'; + +String? _tmpFile; + +Future _rafSource() async { + if (_tmpFile == null) { + final tmp = tmpPath(); + await File(tmp).writeAsBytes([1, 2, 3, 4, 5]); + _tmpFile = tmp; + } + return RandomAccessFileRASource.open(_tmpFile!); +} + +void main() { + test('Length', () async { + final src = await _rafSource(); + expect(await src.length(), 5); + await src.close(); + }); + + test('ReadByte', () async { + final src = await _rafSource(); + expect(await src.readByte(), 1); + expect(await src.readByte(), 2); + expect(await src.readByte(), 3); + expect(await src.readByte(), 4); + expect(await src.readByte(), 5); + expect(await src.readByte(), -1); + await src.close(); + }); + + test('Read', () async { + final src = await _rafSource(); + expect(await src.read(2), Uint8List.fromList([1, 2])); + expect(await src.read(2), Uint8List.fromList([3, 4])); + expect(await src.read(2), Uint8List.fromList([5])); + expect(await src.read(2), Uint8List(0)); + await src.close(); + }); + + test('Position', () async { + final src = await _rafSource(); + expect(await src.position(), 0); + await src.setPosition(2); + expect(await src.position(), 2); + expect(await src.readByte(), 3); + await src.close(); + }); +}