blob: adcee85a69e03aae5ca02db535c2e89e1d584aa6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. 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:_mojo_services/mojo/files/types.mojom.dart' as types;
//
// Implementation of Directory, File, and RandomAccessFile for Mojo.
//
// Helper to convert from mojo:files error to OSError.
OSError _OSErrorFromError(types.Error error) {
assert(error != null);
return new OSError(error.toString(), error.toJson());
}
// All paths in mojo:files are relative to the root directory. This helper
// strips away any leading slashes.
String _ensurePathIsRelative(String path) {
while (path.startsWith('/')) {
// Trim off the leading '/'.
path = path.substring(1);
}
return path;
}
// The mojo implementation of dart:io does not support any synchronous
// file operations. This helper throws an unsupported error.
dynamic _onSyncOperation() {
throw new UnsupportedError(
"Synchronous operations are not supported by this embedder");
}
// Convert from mojo:files Timespec to DateTime.
DateTime _dateTimeFromTimespec(types.Timespec ts) {
if (ts == null) {
// Dawn of time.
return new DateTime.fromMillisecondsSinceEpoch(0);
}
int microseconds = ts.seconds * Duration.MICROSECONDS_PER_SECOND;
microseconds += ts.nanoseconds ~/ 1000;
return new DateTime.fromMicrosecondsSinceEpoch(microseconds);
}
// Convert from mojo:files FileType to FileSystemEntityType.
FileSystemEntityType _fileSystemEntityTypeFromFileType(types.FileType ft) {
if (ft == types.FileType.unknown) {
return FileSystemEntityType.NOT_FOUND;
} else if (ft == types.FileType.regularFile) {
return FileSystemEntityType.FILE;
} else if (ft == types.FileType.directory) {
return FileSystemEntityType.DIRECTORY;
}
throw new UnimplementedError();
return FileSystemEntityType.NOT_FOUND;
}
// Convert from dart:io FileMode to open flags.
int _openFlagsFromFileMode(FileMode fileMode) {
int flags = 0;
switch (fileMode) {
case FileMode.READ:
flags = types.kOpenFlagRead;
break;
case FileMode.WRITE:
flags = types.kOpenFlagRead |
types.kOpenFlagWrite |
types.kOpenFlagTruncate |
types.kOpenFlagCreate;
break;
case FileMode.APPEND:
flags = types.kOpenFlagRead |
types.kOpenFlagWrite |
types.kOpenFlagAppend |
types.kOpenFlagCreate;
break;
case FileMode.WRITE_ONLY:
flags = types.kOpenFlagWrite |
types.kOpenFlagTruncate |
types.kOpenFlagCreate;
break;
case FileMode.WRITE_ONLY_APPEND:
flags = types.kOpenFlagWrite |
types.kOpenFlagAppend |
types.kOpenFlagCreate;
break;
default:
throw new UnimplementedError();
}
return flags;
}
patch class _Directory {
// We start at the root of the file system.
static String _currentDirectoryPath = '/';
/* patch */ Future<Directory> create({bool recursive: false}) async {
if (recursive) {
return exists().then((exists) {
if (exists) return this;
if (path != parent.path) {
return parent.create(recursive: true).then((_) {
return create();
});
} else {
return create();
}
});
}
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags =
types.kOpenFlagRead | types.kOpenFlagWrite | types.kOpenFlagCreate;
var response =
await rootDirectory.responseOrError(
rootDirectory.ptr.openDirectory(_ensurePathIsRelative(path),
null,
flags));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
return this;
}
/* patch */ void createSync({bool recursive: false}) => _onSyncOperation();
/* patch */ Future<Directory> createTemp([String prefix]) async {
DirectoryProxy rootDirectory = await _getRootDirectory();
// Create directory and fail if it already exists.
int flags = types.kOpenFlagRead | types.kOpenFlagWrite |
types.kOpenFlagCreate | types.kOpenFlagExclusive;
String tempPath = '$path/$prefix';
while (true) {
var response =
await rootDirectory.responseOrError(
rootDirectory.ptr.openDirectory(tempPath, null, flags));
if (response.error == types.Error.ok) {
// Success.
break;
}
// Alter the path and try again.
// TODO(johnmccutchan): Append a randomly generated character.
tempPath = tempPath + 'a';
}
return new Directory(tempPath);
}
/* patch */ Directory createTempSync([String prefix]) => _onSyncOperation();
/* patch */ Future<bool> exists() async {
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags = types.kOpenFlagRead | types.kOpenFlagWrite;
var response =
await await rootDirectory.responseOrError(
rootDirectory.ptr.openDirectory(_ensurePathIsRelative(path),
null,
flags));
// If we can open it, it exists.
return response.error == types.Error.ok;
}
/* patch */ bool existsSync() => _onSyncOperation();
/* patch */ Stream<FileSystemEntity> list({bool recursive: false,
bool followLinks: true}) {
_DirectoryLister directoryLister = new _DirectoryLister(path, recursive);
StreamController streamController = new StreamController();
directoryLister.list(streamController);
return streamController.stream;
}
/* patch */ List listSync({bool recursive: false,
bool followLinks: true}) {
return _onSyncOperation();
}
/* patch */ Future<FileStat> stat() {
return FileStat.stat(path);
}
/* patch */ FileStat statSync() => _onSyncOperation();
/* patch */ Future<Directory> rename(String newPath) async {
DirectoryProxy rootDirectory = await _getRootDirectory();
var response = await rootDirectory.responseOrError(
rootDirectory.ptr.rename(_ensurePathIsRelative(path),
_ensurePathIsRelative(newPath)));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
return new Directory(newPath);
}
/* patch */ Directory renameSync(String newPath) => _onSyncOperation();
/* patch */ static _current() {
return _currentDirectoryPath;
}
/* patch */ static _setCurrent(path) {
_currentDirectoryPath = path;
}
/* patch */ static _createTemp(String path) {
throw new UnimplementedError();
}
/* patch */ static String _systemTemp() {
return 'tmp';
}
/* patch */ static _exists(String path) {
throw new UnimplementedError();
}
/* patch */ static _create(String path) {
throw new UnimplementedError();
}
/* patch */ static _deleteNative(String path, bool recursive) {
throw new UnimplementedError();
}
/* patch */ static _rename(String path, String newPath) {
throw new UnimplementedError();
}
/* patch */ static List _list(String path, bool recursive, bool followLinks) {
throw new UnimplementedError();
}
}
class _DirectoryLister {
final String _path;
final bool _recursive;
final List<String> _directoriesToList = new List<String>();
_DirectoryLister(this._path, this._recursive);
list(StreamController streamController) async {
_directoriesToList.add(_path);
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags = types.kOpenFlagRead | types.kOpenFlagWrite;
while (_directoriesToList.length > 0) {
// Remove head.
String path = _directoriesToList.removeAt(0);
// Open directory.
DirectoryProxy directory = new DirectoryProxy.unbound();
var response =
await rootDirectory.responseOrError(
rootDirectory.ptr.openDirectory(_ensurePathIsRelative(path),
directory,
flags));
if (response.error != types.Error.ok) {
// Skip if we can't open it.
continue;
}
// Read contents.
var readResponse = await directory.responseOrError(directory.ptr.read());
// We are done with the directory now.
directory.close(immediate: true);
if (readResponse.error != types.Error.ok) {
// Skip if we can't read it.
continue;
}
List<types.DirectoryEntry> directoryContents =
readResponse.directoryContents;
for (types.DirectoryEntry entry in directoryContents) {
String childPath = '$path/${entry.name}';
if (entry.type == types.FileType.directory) {
if (_recursive) {
if ((entry.name != '.') && (entry.name != '..')) {
_directoriesToList.add(childPath);
}
}
streamController.add(new Directory(childPath));
} else {
streamController.add(new File(childPath));
}
}
}
streamController.close();
}
}
patch class _File {
/* patch */ Future<bool> exists() async {
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags = types.kOpenFlagRead;
var response =
await rootDirectory.responseOrError(
rootDirectory.ptr.openFile(_ensurePathIsRelative(path),
null,
flags));
// If we can open it, it exists.
return response.error == types.Error.ok;
}
/* patch */ bool existsSync() => _onSyncOperation();
/* patch */ FileStat statSync() => _onSyncOperation();
/* patch */ Future<File> create({bool recursive: false}) async {
if (recursive) {
// Create any parent directories.
await parent.create(recursive: true);
}
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags = types.kOpenFlagWrite | types.kOpenFlagCreate;
var response =
await rootDirectory.responseOrError(
rootDirectory.ptr.openFile(_ensurePathIsRelative(path),
null,
flags));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
return this;
}
/* patch */ void createSync({bool recursive: false}) => _onSyncOperation();
/* patch */ Future<File> rename(String newPath) async {
DirectoryProxy rootDirectory = await _getRootDirectory();
var response = await rootDirectory.responseOrError(
rootDirectory.ptr.rename(_ensurePathIsRelative(path),
_ensurePathIsRelative(newPath)));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
return new File(newPath);
}
/* patch */ File renameSync(String newPath) => _onSyncOperation();
/* patch */ Future<File> copy(String newPath) async {
File copyFile = new File(newPath);
Stream<List<int>> input = openRead();
IOSink output = copyFile.openWrite();
// Copy contents.
await output.addStream(input);
// Close.
await output.close();
return copyFile;
}
/* patch */ File copySync(String newPath) => _onSyncOperation();
/* patch */ Future<RandomAccessFile> open(
{FileMode mode: FileMode.READ}) async {
DirectoryProxy rootDirectory = await _getRootDirectory();
FileProxy file = new FileProxy.unbound();
var response = await rootDirectory.responseOrError(
rootDirectory.ptr.openFile(_ensurePathIsRelative(path),
file,
_openFlagsFromFileMode(mode)));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
// We use the raw mojo handle as our fd.
final int fd = file.impl.endpoint.handle.h;
// Construct the RandomAccessFile using the original constructor.
_RandomAccessFile raf = new _RandomAccessFile(fd, path);
// Hook up our proxy.
raf._proxy = file;
return raf;
}
/* patch */ Future<int> length() async {
FileStat fileStat = await FileStat.stat(path);
return fileStat.size;
}
/* patch */ int lengthSync() => _onSyncOperation();
/* patch */ Future<DateTime> lastModified() async {
FileStat fileStat = await FileStat.stat(path);
return fileStat.modified;
}
/* patch */ DateTime lastModifiedSync() => _onSyncOperation();
/* patch */ RandomAccessFile openSync({FileMode mode: FileMode.READ}) {
return _onSyncOperation();
}
/* patch */ Stream<List<int>> openRead([int start, int end]) {
return new _FileStream(path, start, end);
}
/* patch */ IOSink openWrite({FileMode mode: FileMode.WRITE,
Encoding encoding: UTF8}) {
if (mode != FileMode.WRITE &&
mode != FileMode.APPEND &&
mode != FileMode.WRITE_ONLY &&
mode != FileMode.WRITE_ONLY_APPEND) {
throw new ArgumentError('Invalid file mode for this operation');
}
var consumer = new _FileStreamConsumer(this, mode);
return new IOSink(consumer, encoding: encoding);
}
/* patch */ Future<List<int>> readAsBytes() async {
RandomAccessFile raf = await open();
int length = await raf.length();
var bytes = await raf.read(length);
await raf.close();
return bytes;
}
/* patch */ List<int> readAsBytesSync() => _onSyncOperation();
/* patch */ String readAsStringSync({Encoding encoding: UTF8}) {
return _onSyncOperation();
}
/* patch */ List<String> readAsLinesSync({Encoding encoding: UTF8}) {
return _onSyncOperation();
}
/* patch */ Future<File> writeAsBytes(List<int> bytes,
{FileMode mode: FileMode.WRITE,
bool flush: false}) async {
RandomAccessFile raf = await open(mode: mode);
await raf.writeFrom(bytes, 0, bytes.length);
await raf.close();
return this;
}
/* patch */ void writeAsBytesSync(List<int> bytes,
{FileMode mode: FileMode.WRITE,
bool flush: false}) {
_onSyncOperation();
}
/* patch */ void writeAsStringSync(String contents,
{FileMode mode: FileMode.WRITE,
Encoding encoding: UTF8,
bool flush: false}) {
_onSyncOperation();
}
/* patch */ static _exists(String path) {
throw new UnimplementedError();
}
/* patch */ static _create(String path) {
throw new UnimplementedError();
}
/* patch */ static _createLink(String path, String target) {
throw new UnimplementedError();
}
/* patch */ static _linkTarget(String path) {
throw new UnimplementedError();
}
/* patch */ static _deleteNative(String path) {
throw new UnimplementedError();
}
/* patch */ static _deleteLinkNative(String path) {
throw new UnimplementedError();
}
/* patch */ static _rename(String oldPath, String newPath) {
throw new UnimplementedError();
}
/* patch */ static _renameLink(String oldPath, String newPath) {
throw new UnimplementedError();
}
/* patch */ static _copy(String oldPath, String newPath) {
throw new UnimplementedError();
}
/* patch */ static _lengthFromPath(String path) {
throw new UnimplementedError();
}
/* patch */ static _lastModified(String path) {
throw new UnimplementedError();
}
/* patch */ static _open(String path, int mode) {
throw new UnimplementedError();
}
/* patch */ static int _openStdio(int fd) {
throw new UnimplementedError();
}
}
patch class FileStat {
/* patch */ static FileStat statSync(String path) {
return _onSyncOperation();
}
/* patch */ static Future<FileStat> stat(String path) async {
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags = types.kOpenFlagRead | types.kOpenFlagWrite;
DirectoryProxy directory = new DirectoryProxy.unbound();
var response =
await await rootDirectory.responseOrError(
rootDirectory.ptr.openDirectory(_ensurePathIsRelative(path),
directory,
flags));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
var statResponse = await directory.responseOrError(directory.ptr.stat());
// We are done with the directory now.
directory.close(immediate: true);
if (statResponse.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
types.FileInformation fileInformation = statResponse.fileInformation;
DateTime modified = _dateTimeFromTimespec(fileInformation.mtime);
DateTime accessed = _dateTimeFromTimespec(fileInformation.atime);
int size = fileInformation.size;
const userReadWriteExecutableUnixMode = 0x1c0;
FileSystemEntityType fset =
_fileSystemEntityTypeFromFileType(fileInformation.type);
return new FileStat._internal(modified,
modified,
accessed,
fset,
userReadWriteExecutableUnixMode,
size);
}
/* patch */ static _statSync(String path) {
_onSyncOperation();
}
}
patch class FileSystemEntity {
/* patch */ Future<String> resolveSymbolicLinks() {
// TODO(johnmccutchan): Canonicalize path before returning.
return path;
}
/* patch */ String resolveSymbolicLinksSync() {
return _onSyncOperation();
}
/* patch */ Future<FileSystemEntity> delete({bool recursive: false}) async {
DirectoryProxy rootDirectory = await _getRootDirectory();
int flags = recursive ? types.kDeleteFlagRecursive : 0;
var response = await rootDirectory.responseOrError(
rootDirectory.ptr.delete(_ensurePathIsRelative(path), flags));
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
return this;
}
/* patch */ void deleteSync({bool recursive: false}) {
_onSyncOperation();
}
/* patch */ Stream<FileSystemEvent> watch({int events: FileSystemEvent.ALL,
bool recursive: false}) {
throw new UnsupportedError(
"File system watch is not supported by this embedder");
}
/* patch */ static Future<bool> identical(String path1, String path2) {
// TODO(johnmccutchan): Canonicalize paths before comparing.
return path1 == path2;
}
/* patch */ static bool identicalSync(String path1, String path2) {
return _onSyncOperation();
}
/* patch */ static Future<FileSystemEntityType> type(
String path, {bool followLinks: true}) async {
FileStat fs = await FileStat.stat(path);
return fs.type;
}
/* patch */ static FileSystemEntityType typeSync(
String path, {bool followLinks: true}) {
return _onSyncOperation();
}
/* patch */ static _getType(String path, bool followLinks) {
throw new UnimplementedError();
}
/* patch */ static _identical(String path1, String path2) {
throw new UnimplementedError();
}
/* patch */ static _resolveSymbolicLinks(String path) {
throw new UnimplementedError();
}
}
patch class _Link {
}
patch class _RandomAccessFileOps {
/* patch */ factory _RandomAccessFileOps(int pointer)
=> new _RandomAccessFileOpsImpl(pointer);
}
class _RandomAccessFileOpsImpl implements _RandomAccessFileOps {
int _pointer;
_RandomAccessFileOpsImpl(this._pointer);
int getPointer() => _pointer;
int close() => throw new UnimplementedError();
readByte() => throw new UnimplementedError();
read(int bytes) => throw new UnimplementedError();
readInto(List<int> buffer, int start, int end)
=> throw new UnimplementedError();
writeByte(int value) => throw new UnimplementedError();
writeFrom(List<int> buffer, int start, int end)
=> throw new UnimplementedError();
position() => throw new UnimplementedError();
setPosition(int position) => throw new UnimplementedError();
truncate(int length) => throw new UnimplementedError();
length() => throw new UnimplementedError();
flush() => throw new UnimplementedError();
lock(int lock, int start, int end) => throw new UnimplementedError();
}
patch class _RandomAccessFile {
FileProxy _proxy;
void _ensureProxy() {
if (_proxy == null) {
throw new StateError("_RandomAccessFile has a null proxy.");
}
}
void _handleError(dynamic response) {
if (response.error != types.Error.ok) {
throw _OSErrorFromError(response.error);
}
}
/* patch */ Future<RandomAccessFile> close() async {
_ensureProxy();
await _proxy.responseOrError(_proxy.ptr.close());
await _proxy.close(immediate: true);
_proxy = null;
closed = true;
_maybePerformCleanup();
return this;
}
/* patch */ void closeSync() {
_onSyncOperation();
}
/* patch */ Future<int> readByte() async {
_ensureProxy();
var response = await _proxy.responseOrError(
_proxy.ptr.read(1, 0, types.Whence.fromCurrent));
_handleError(response);
_resourceInfo.addRead(response.bytesRead.length);
if (response.bytesRead.length == 0) {
throw new FileSystemException("readByte failed.");
}
return response.bytesRead[0];
}
/* patch */ int readByteSync() {
return _onSyncOperation();
}
/* patch */ Future<List<int>> read(int bytes) async {
if (bytes is !int) {
throw new ArgumentError(bytes);
}
_ensureProxy();
var response = await _proxy.responseOrError(
_proxy.ptr.read(bytes, 0, types.Whence.fromCurrent));
_handleError(response);
_resourceInfo.addRead(response.bytesRead.length);
return response.bytesRead;
}
/* patch */ List<int> readSync(int bytes) {
return _onSyncOperation();
}
/* patch */ Future<int> readInto(List<int> buffer,
[int start = 0, int end]) async {
if (buffer is !List ||
(start != null && start is !int) ||
(end != null && end is !int)) {
throw new ArgumentError();
}
end = RangeError.checkValidRange(start, end, buffer.length);
_ensureProxy();
if (end == start) {
return 0;
}
int length = end - start;
var response = await _proxy.responseOrError(
_proxy.ptr.read(length, 0, types.Whence.fromCurrent));
_handleError(response);
int read = response.bytesRead.length;
_resourceInfo.addRead(read);
buffer.setRange(start, start + read, response.bytesRead);
return read;
}
/* patch */ int readIntoSync(List<int> buffer, [int start = 0, int end]) {
return _onSyncOperation();
}
/* patch */ Future<RandomAccessFile> writeByte(int value) async {
if (value is !int) {
throw new ArgumentError(value);
}
_ensureProxy();
var response = await _proxy.responseOrError(
_proxy.ptr.write([value], 0, types.Whence.fromCurrent));
_handleError(response);
assert(response.numBytesWritten == 1);
_resourceInfo.addWrite(response.numBytesWritten);
return this;
}
/* patch */ int writeByteSync(int value) {
return _onSyncOperation();
}
/* patch */ Future<RandomAccessFile> writeFrom(
List<int> buffer, [int start = 0, int end]) async {
if ((buffer is !List) ||
(start != null && start is !int) ||
(end != null && end is !int)) {
throw new ArgumentError("Invalid arguments to writeFrom");
}
end = RangeError.checkValidRange(start, end, buffer.length);
_ensureProxy();
if (end == start) {
return this;
}
_BufferAndStart result;
final int length = end - start;
result = _ensureFastAndSerializableByteData(buffer, start, end);
if (result.start != 0) {
// Slow path where we copy the contents of buffer into a new buffer
// so that the data we want to write starts at the beginning of the
// buffer.
final buffer = new Uint8List(length);
buffer.setRange(0, length, result.buffer, start);
// Replace the buffer in result.
result.buffer = buffer;
result.start = 0;
}
assert(result.start == 0);
var response = await _proxy.responseOrError(
_proxy.ptr.write(result.buffer, 0, types.Whence.fromCurrent));
_handleError(response);
_resourceInfo.addWrite(response.numBytesWritten);
return this;
}
/* patch */ void writeFromSync(List<int> buffer, [int start = 0, int end]) {
_onSyncOperation();
}
/* patch */ void writeStringSync(String string, {Encoding encoding: UTF8}) {
_onSyncOperation();
}
/* patch */ Future<int> position() async {
_ensureProxy();
var response = await _proxy.responseOrError(_proxy.ptr.tell());
_handleError(response);
return response.position;
}
/* patch */ int positionSync() {
_onSyncOperation();
}
/* patch */ Future<RandomAccessFile> setPosition(int position) async {
if (position is !int) {
throw new ArgumentError(position);
}
_ensureProxy();
var response = await _proxy.responseOrError(
_proxy.ptr.seek(position, types.Whence.fromStart));
_handleError(response);
return this;
}
/* patch */ void setPositionSync(int position) {
_onSyncOperation();
}
/* patch */ Future<RandomAccessFile> truncate(int length) async {
if (length is !int) {
throw new ArgumentError(length);
}
_ensureProxy();
var response = await _proxy.responseOrError(_proxy.ptr.truncate(length));
_handleError(response);
}
/* patch */ void truncateSync(int length) {
_onSyncOperation();
}
/* patch */ Future<int> length() async {
_ensureProxy();
var response = await _proxy.responseOrError(_proxy.ptr.stat());
_handleError(response);
return response.fileInformation.size;
}
/* patch */ int lengthSync() {
_onSyncOperation();
}
/* patch */ Future<RandomAccessFile> flush() {
return this;
}
/* patch */ void flushSync() {
_onSyncOperation();
}
/* patch */ Future<RandomAccessFile> lock(
[FileLock mode = FileLock.EXCLUSIVE, int start = 0, int end = -1]) {
throw new UnsupportedError(
"File locking is not supported by this embedder");
}
/* patch */ Future<RandomAccessFile> unlock([int start = 0, int end = -1]) {
throw new UnsupportedError(
"File locking is not supported by this embedder");
}
/* patch */ void lockSync(
[FileLock mode = FileLock.EXCLUSIVE, int start = 0, int end]) {
_onSyncOperation();
}
/* patch */ void unlockSync([int start = 0, int end]) {
_onSyncOperation();
}
}