| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:convert'; |
| import 'dart:math' as math show min; |
| import 'dart:typed_data'; |
| |
| import 'package:file/src/common.dart' as common; |
| import 'package:file/src/io.dart' as io; |
| |
| import 'memory_file.dart'; |
| import 'node.dart'; |
| import 'utils.dart' as utils; |
| |
| /// A [MemoryFileSystem]-backed implementation of [io.RandomAccessFile]. |
| class MemoryRandomAccessFile implements io.RandomAccessFile { |
| /// Constructs a [MemoryRandomAccessFile]. |
| /// |
| /// This should be used only by [MemoryFile.open] or [MemoryFile.openSync]. |
| MemoryRandomAccessFile(this.path, this._node, this._mode) { |
| switch (_mode) { |
| case io.FileMode.read: |
| break; |
| case io.FileMode.write: |
| case io.FileMode.writeOnly: |
| truncateSync(0); |
| break; |
| case io.FileMode.append: |
| case io.FileMode.writeOnlyAppend: |
| _position = lengthSync(); |
| break; |
| default: |
| // [FileMode] provides no way of retrieving its value or name. |
| throw UnimplementedError('Unsupported FileMode'); |
| } |
| } |
| |
| @override |
| final String path; |
| |
| final FileNode _node; |
| final io.FileMode _mode; |
| |
| bool _isOpen = true; |
| int _position = 0; |
| |
| /// Whether an asynchronous operation is pending. |
| /// |
| /// See [_asyncWrapper] for details. |
| bool get _asyncOperationPending => __asyncOperationPending; |
| |
| set _asyncOperationPending(bool value) { |
| assert(__asyncOperationPending != value); |
| __asyncOperationPending = value; |
| } |
| |
| bool __asyncOperationPending = false; |
| |
| /// Throws a [io.FileSystemException] if an operation is attempted on a file |
| /// that is not open. |
| void _checkOpen() { |
| if (!_isOpen) { |
| throw io.FileSystemException('File closed', path); |
| } |
| } |
| |
| /// Throws a [io.FileSystemException] if attempting to read from a file that |
| /// has not been opened for reading. |
| void _checkReadable(String operation) { |
| switch (_mode) { |
| case io.FileMode.read: |
| case io.FileMode.write: |
| case io.FileMode.append: |
| return; |
| case io.FileMode.writeOnly: |
| case io.FileMode.writeOnlyAppend: |
| default: |
| throw io.FileSystemException( |
| '$operation failed', path, common.badFileDescriptor(path).osError); |
| } |
| } |
| |
| /// Throws a [io.FileSystemException] if attempting to read from a file that |
| /// has not been opened for writing. |
| void _checkWritable(String operation) { |
| if (utils.isWriteMode(_mode)) { |
| return; |
| } |
| |
| throw io.FileSystemException( |
| '$operation failed', path, common.badFileDescriptor(path).osError); |
| } |
| |
| /// Throws a [io.FileSystemException] if attempting to perform an operation |
| /// while an asynchronous operation is already in progress. |
| /// |
| /// See [_asyncWrapper] for details. |
| void _checkAsync() { |
| if (_asyncOperationPending) { |
| throw io.FileSystemException( |
| 'An async operation is currently pending', path); |
| } |
| } |
| |
| /// Wraps a synchronous function to make it appear asynchronous. |
| /// |
| /// [_asyncOperationPending], [_checkAsync], and [_asyncWrapper] are used to |
| /// mimic [RandomAccessFile]'s enforcement that only one asynchronous |
| /// operation is pending for a [RandomAccessFile] instance. Since |
| /// [MemoryFileSystem]-based classes are likely to be used in tests, fidelity |
| /// is important to catch errors that might occur in production. |
| /// |
| /// [_asyncWrapper] does not call [f] directly since setting and unsetting |
| /// [_asyncOperationPending] synchronously would not be meaningful. We |
| /// instead execute [f] through a [Future.delayed] callback to better simulate |
| /// asynchrony. |
| Future<R> _asyncWrapper<R>(R Function() f) async { |
| _checkAsync(); |
| |
| _asyncOperationPending = true; |
| try { |
| return await Future<R>.delayed( |
| Duration.zero, |
| () { |
| // Temporarily reset [_asyncOpPending] in case [f]'s has its own |
| // checks for pending asynchronous operations. |
| _asyncOperationPending = false; |
| try { |
| return f(); |
| } finally { |
| _asyncOperationPending = true; |
| } |
| }, |
| ); |
| } finally { |
| _asyncOperationPending = false; |
| } |
| } |
| |
| @override |
| Future<void> close() async => _asyncWrapper(closeSync); |
| |
| @override |
| void closeSync() { |
| _checkOpen(); |
| _isOpen = false; |
| } |
| |
| @override |
| Future<io.RandomAccessFile> flush() async { |
| await _asyncWrapper(flushSync); |
| return this; |
| } |
| |
| @override |
| void flushSync() { |
| _checkOpen(); |
| _checkAsync(); |
| } |
| |
| @override |
| Future<int> length() => _asyncWrapper(lengthSync); |
| |
| @override |
| int lengthSync() { |
| _checkOpen(); |
| _checkAsync(); |
| return _node.size; |
| } |
| |
| @override |
| Future<io.RandomAccessFile> lock([ |
| io.FileLock mode = io.FileLock.exclusive, |
| int start = 0, |
| int end = -1, |
| ]) async { |
| await _asyncWrapper(() => lockSync(mode, start, end)); |
| return this; |
| } |
| |
| @override |
| void lockSync([ |
| io.FileLock mode = io.FileLock.exclusive, |
| int start = 0, |
| int end = -1, |
| ]) { |
| _checkOpen(); |
| _checkAsync(); |
| // TODO(jamesderlin): Implement, https://github.com/google/file.dart/issues/140 |
| throw UnimplementedError('TODO'); |
| } |
| |
| @override |
| Future<int> position() => _asyncWrapper(positionSync); |
| |
| @override |
| int positionSync() { |
| _checkOpen(); |
| _checkAsync(); |
| return _position; |
| } |
| |
| @override |
| Future<Uint8List> read(int bytes) => _asyncWrapper(() => readSync(bytes)); |
| |
| @override |
| Uint8List readSync(int bytes) { |
| _checkOpen(); |
| _checkAsync(); |
| _checkReadable('read'); |
| // TODO(jamesderlin): Check for integer overflow. |
| final int end = math.min(_position + bytes, lengthSync()); |
| final Uint8List copy = _node.content.sublist(_position, end); |
| _position = end; |
| return copy; |
| } |
| |
| @override |
| Future<int> readByte() => _asyncWrapper(readByteSync); |
| |
| @override |
| int readByteSync() { |
| _checkOpen(); |
| _checkAsync(); |
| _checkReadable('readByte'); |
| |
| if (_position >= lengthSync()) { |
| return -1; |
| } |
| return _node.content[_position++]; |
| } |
| |
| @override |
| Future<int> readInto(List<int> buffer, [int start = 0, int? end]) => |
| _asyncWrapper(() => readIntoSync(buffer, start, end)); |
| |
| @override |
| int readIntoSync(List<int> buffer, [int start = 0, int? end]) { |
| _checkOpen(); |
| _checkAsync(); |
| _checkReadable('readInto'); |
| |
| end = RangeError.checkValidRange(start, end, buffer.length); |
| |
| final int length = lengthSync(); |
| int i; |
| for (i = start; i < end && _position < length; i += 1, _position += 1) { |
| buffer[i] = _node.content[_position]; |
| } |
| return i - start; |
| } |
| |
| @override |
| Future<io.RandomAccessFile> setPosition(int position) async { |
| await _asyncWrapper(() => setPositionSync(position)); |
| return this; |
| } |
| |
| @override |
| void setPositionSync(int position) { |
| _checkOpen(); |
| _checkAsync(); |
| |
| if (position < 0) { |
| throw io.FileSystemException( |
| 'setPosition failed', path, common.invalidArgument(path).osError); |
| } |
| |
| // Empirical testing indicates that setting the position to be beyond the |
| // end of the file is legal and will zero-fill upon the next write. |
| _position = position; |
| } |
| |
| @override |
| Future<io.RandomAccessFile> truncate(int length) async { |
| await _asyncWrapper(() => truncateSync(length)); |
| return this; |
| } |
| |
| @override |
| void truncateSync(int length) { |
| _checkOpen(); |
| _checkAsync(); |
| |
| if (length < 0 || !utils.isWriteMode(_mode)) { |
| throw io.FileSystemException( |
| 'truncate failed', path, common.invalidArgument(path).osError); |
| } |
| |
| final int oldLength = lengthSync(); |
| if (length < oldLength) { |
| _node.truncate(length); |
| |
| // [_position] is intentionally left untouched to match the observed |
| // behavior of [RandomAccessFile]. |
| } else if (length > oldLength) { |
| _node.write(Uint8List(length - oldLength)); |
| } |
| assert(lengthSync() == length); |
| } |
| |
| @override |
| Future<io.RandomAccessFile> unlock([int start = 0, int end = -1]) async { |
| await _asyncWrapper(() => unlockSync(start, end)); |
| return this; |
| } |
| |
| @override |
| void unlockSync([int start = 0, int end = -1]) { |
| _checkOpen(); |
| _checkAsync(); |
| // TODO(jamesderlin): Implement, https://github.com/google/file.dart/issues/140 |
| throw UnimplementedError('TODO'); |
| } |
| |
| @override |
| Future<io.RandomAccessFile> writeByte(int value) async { |
| await _asyncWrapper(() => writeByteSync(value)); |
| return this; |
| } |
| |
| @override |
| int writeByteSync(int value) { |
| _checkOpen(); |
| _checkAsync(); |
| _checkWritable('writeByte'); |
| |
| // [Uint8List] will truncate values to 8-bits automatically, so we don't |
| // need to check [value]. |
| |
| int length = lengthSync(); |
| if (_position >= length) { |
| // If [_position] is out of bounds, [RandomAccessFile] zero-fills the |
| // file. |
| truncateSync(_position + 1); |
| length = lengthSync(); |
| } |
| assert(_position < length); |
| _node.content[_position++] = value; |
| |
| // Despite what the documentation states, [RandomAccessFile.writeByteSync] |
| // always seems to return 1, even if we had to extend the file for an out of |
| // bounds write. See https://github.com/dart-lang/sdk/issues/42298. |
| return 1; |
| } |
| |
| @override |
| Future<io.RandomAccessFile> writeFrom( |
| List<int> buffer, [ |
| int start = 0, |
| int? end, |
| ]) async { |
| await _asyncWrapper(() => writeFromSync(buffer, start, end)); |
| return this; |
| } |
| |
| @override |
| void writeFromSync(List<int> buffer, [int start = 0, int? end]) { |
| _checkOpen(); |
| _checkAsync(); |
| _checkWritable('writeFrom'); |
| |
| end = RangeError.checkValidRange(start, end, buffer.length); |
| |
| final int writeByteCount = end - start; |
| final int endPosition = _position + writeByteCount; |
| |
| if (endPosition > lengthSync()) { |
| truncateSync(endPosition); |
| } |
| |
| _node.content.setRange(_position, endPosition, buffer, start); |
| _position = endPosition; |
| } |
| |
| @override |
| Future<io.RandomAccessFile> writeString( |
| String string, { |
| Encoding encoding = utf8, |
| }) async { |
| await _asyncWrapper(() => writeStringSync(string, encoding: encoding)); |
| return this; |
| } |
| |
| @override |
| void writeStringSync(String string, {Encoding encoding = utf8}) { |
| writeFromSync(encoding.encode(string)); |
| } |
| } |