blob: 82b89c2b4de14659ebd73f70b7a4ec991a8e941e [file] [log] [blame] [edit]
// Copyright (c) 2012, 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.
part of "dart:io";
class _Directory extends FileSystemEntity implements Directory {
final String _path;
final Uint8List _rawPath;
_Directory(String path)
: _path = _checkNotNull(path, "path"),
_rawPath = FileSystemEntity._toUtf8Array(path);
_Directory.fromRawPath(Uint8List rawPath)
: _rawPath = FileSystemEntity._toNullTerminatedUtf8Array(
_checkNotNull(rawPath, "rawPath"),
),
_path = FileSystemEntity._toStringFromUtf8Array(rawPath);
String get path => _path;
external static _current(_Namespace namespace);
external static _setCurrent(_Namespace namespace, Uint8List rawPath);
external static _createTemp(_Namespace namespace, Uint8List rawPath);
external static String _systemTemp(_Namespace namespace);
external static _exists(_Namespace namespace, Uint8List rawPath);
external static _create(_Namespace namespace, Uint8List rawPath);
external static _deleteNative(
_Namespace namespace,
Uint8List rawPath,
bool recursive,
);
external static _rename(
_Namespace namespace,
Uint8List rawPath,
String newPath,
);
external static void _fillWithDirectoryListing(
_Namespace namespace,
List<FileSystemEntity> list,
Uint8List rawPath,
bool recursive,
bool followLinks,
);
static Directory get current {
var result = _current(_Namespace._namespace);
if (result is OSError) {
throw FileSystemException._fromOSError(
result,
"Getting current working directory failed",
"",
);
}
return _Directory(result);
}
static void set current(Object? path) {
var _rawPath = switch (path) {
// For our internal Directory implementation, go ahead and use the raw
// path.
_Directory d => d._rawPath,
// Fall back to the String-based path.
Directory d => FileSystemEntity._toUtf8Array(d.path),
String s => FileSystemEntity._toUtf8Array(s),
_ => throw ArgumentError(
'${Error.safeToString(path)} is not a String or'
' Directory',
),
};
if (!_EmbedderConfig._mayChdir) {
throw UnsupportedError(
"This embedder disallows setting Directory.current",
);
}
var result = _setCurrent(_Namespace._namespace, _rawPath);
if (result is ArgumentError) throw result;
if (result is OSError) {
throw FileSystemException._fromOSError(
result,
"Setting current working directory failed",
path.toString(),
);
}
}
Uri get uri {
return Uri.directory(path);
}
Future<bool> exists() {
return _File._dispatchWithNamespace(_IOService.directoryExists, [
null,
_rawPath,
]).then((response) {
_checkForErrorResponse(response, "Exists failed", path);
return response == 1;
});
}
bool existsSync() {
var result = _exists(_Namespace._namespace, _rawPath);
if (result is OSError) {
throw FileSystemException("Exists failed", path, result);
}
return (result == 1);
}
Directory get absolute => Directory(_absolutePath);
Future<Directory> create({bool recursive = false}) {
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();
}
});
} else {
return _File._dispatchWithNamespace(_IOService.directoryCreate, [
null,
_rawPath,
]).then((response) {
_checkForErrorResponse(response, "Creation failed", path);
return this;
});
}
}
void createSync({bool recursive = false}) {
if (recursive) {
if (existsSync()) return;
if (path != parent.path) {
parent.createSync(recursive: true);
}
}
var result = _create(_Namespace._namespace, _rawPath);
if (result is OSError) {
throw FileSystemException._fromOSError(result, "Creation failed", path);
}
}
static Directory get systemTemp =>
Directory(_systemTemp(_Namespace._namespace));
Future<Directory> createTemp([String? prefix]) {
prefix ??= '';
if (path == '') {
throw ArgumentError(
"Directory.createTemp called with an empty path. "
"To use the system temp directory, use Directory.systemTemp",
);
}
String fullPrefix;
// FIXME(bkonyi): here we're using `path` directly, which might cause
// issues if it is not UTF-8 encoded.
if (path.endsWith('/') || (Platform.isWindows && path.endsWith('\\'))) {
fullPrefix = "$path$prefix";
} else {
fullPrefix = "$path${Platform.pathSeparator}$prefix";
}
return _File._dispatchWithNamespace(_IOService.directoryCreateTemp, [
null,
FileSystemEntity._toUtf8Array(fullPrefix),
]).then((response) {
_checkForErrorResponse(
response,
"Creation of temporary directory failed",
path,
);
return Directory(response as String);
});
}
Directory createTempSync([String? prefix]) {
prefix ??= '';
if (path == '') {
throw ArgumentError(
"Directory.createTemp called with an empty path. "
"To use the system temp directory, use Directory.systemTemp",
);
}
String fullPrefix;
// FIXME(bkonyi): here we're using `path` directly, which might cause
// issues if it is not UTF-8 encoded.
if (path.endsWith('/') || (Platform.isWindows && path.endsWith('\\'))) {
fullPrefix = "$path$prefix";
} else {
fullPrefix = "$path${Platform.pathSeparator}$prefix";
}
var result = _createTemp(
_Namespace._namespace,
FileSystemEntity._toUtf8Array(fullPrefix),
);
if (result is OSError) {
throw FileSystemException._fromOSError(
result,
"Creation of temporary directory failed",
fullPrefix,
);
}
return Directory(result);
}
Future<Directory> _delete({bool recursive = false}) {
return _File._dispatchWithNamespace(_IOService.directoryDelete, [
null,
_rawPath,
recursive,
]).then((response) {
_checkForErrorResponse(response, "Deletion failed", path);
return this;
});
}
void _deleteSync({bool recursive = false}) {
var result = _deleteNative(_Namespace._namespace, _rawPath, recursive);
if (result is OSError) {
throw FileSystemException._fromOSError(result, "Deletion failed", path);
}
}
Future<Directory> rename(String newPath) {
return _File._dispatchWithNamespace(_IOService.directoryRename, [
null,
_rawPath,
newPath,
]).then((response) {
_checkForErrorResponse(response, "Rename failed", path);
return Directory(newPath);
});
}
Directory renameSync(String newPath) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(newPath, "newPath");
var result = _rename(_Namespace._namespace, _rawPath, newPath);
if (result is OSError) {
throw FileSystemException._fromOSError(result, "Rename failed", path);
}
return Directory(newPath);
}
Stream<FileSystemEntity> list({
bool recursive = false,
bool followLinks = true,
}) {
return _AsyncDirectoryLister(
// FIXME(bkonyi): here we're using `path` directly, which might cause issues
// if it is not UTF-8 encoded.
FileSystemEntity._toUtf8Array(
FileSystemEntity._ensureTrailingPathSeparators(path),
),
recursive,
followLinks,
).stream;
}
List<FileSystemEntity> listSync({
bool recursive = false,
bool followLinks = true,
}) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(recursive, "recursive");
ArgumentError.checkNotNull(followLinks, "followLinks");
var result = <FileSystemEntity>[];
_fillWithDirectoryListing(
_Namespace._namespace,
result,
// FIXME(bkonyi): here we're using `path` directly, which might cause issues
// if it is not UTF-8 encoded.
FileSystemEntity._toUtf8Array(
FileSystemEntity._ensureTrailingPathSeparators(path),
),
recursive,
followLinks,
);
return result;
}
String toString() => "Directory: '$path'";
// TODO(40614): Remove once non-nullability is sound.
static T _checkNotNull<T>(T t, String name) {
ArgumentError.checkNotNull(t, name);
return t;
}
}
abstract class _AsyncDirectoryListerOps {
external factory _AsyncDirectoryListerOps._(int pointer);
int? _getPointer();
}
class _AsyncDirectoryLister {
static const int listFile = 0;
static const int listDirectory = 1;
static const int listLink = 2;
static const int listError = 3;
static const int listDone = 4;
static const int responseType = 0;
static const int responsePath = 1;
static const int responseComplete = 1;
static const int responseError = 2;
final Uint8List rawPath;
final bool recursive;
final bool followLinks;
final controller = StreamController<FileSystemEntity>(sync: true);
bool canceled = false;
bool nextRunning = false;
bool closed = false;
_AsyncDirectoryListerOps? _ops;
Completer closeCompleter = Completer();
_AsyncDirectoryLister(this.rawPath, this.recursive, this.followLinks) {
controller
..onListen = onListen
..onResume = onResume
..onCancel = onCancel;
}
// WARNING:
// Calling this function will increase the reference count on the native
// object that implements the async directory lister operations. It should
// only be called to pass the pointer to the IO Service, which will decrement
// the reference count when it is finished with it.
int? _pointer() {
return _ops?._getPointer();
}
Stream<FileSystemEntity> get stream => controller.stream;
void onListen() {
_File._dispatchWithNamespace(_IOService.directoryListStart, [
null,
rawPath,
recursive,
followLinks,
]).then((response) {
if (response is int) {
_ops = _AsyncDirectoryListerOps._(response);
next();
} else if (response is Error) {
controller.addError(response, response.stackTrace);
close();
} else {
error(response as List<Object?>);
close();
}
});
}
void onResume() {
if (!nextRunning) {
next();
}
}
Future onCancel() {
canceled = true;
// If we are active, but not requesting, close.
if (!nextRunning) {
close();
}
return closeCompleter.future;
}
void next() {
if (canceled) {
close();
return;
}
if (controller.isPaused || nextRunning) {
return;
}
var pointer = _pointer();
if (pointer == null) {
return;
}
nextRunning = true;
_IOService._dispatch(_IOService.directoryListNext, [pointer]).then((
result,
) {
nextRunning = false;
if (result is List) {
next();
assert(result.length % 2 == 0);
for (int i = 0; i < result.length; i++) {
assert(i % 2 == 0);
switch (result[i++]) {
case listFile:
controller.add(File.fromRawPath(result[i]));
break;
case listDirectory:
controller.add(Directory.fromRawPath(result[i]));
break;
case listLink:
controller.add(Link.fromRawPath(result[i]));
break;
case listError:
error(result[i]);
break;
case listDone:
canceled = true;
return;
}
}
} else {
controller.addError(FileSystemException("Internal error"));
}
});
}
void _cleanup() {
controller.close();
closeCompleter.complete();
_ops = null;
}
void close() {
if (closed) {
return;
}
if (nextRunning) {
return;
}
closed = true;
var pointer = _pointer();
if (pointer == null) {
_cleanup();
} else {
_IOService._dispatch(_IOService.directoryListStop, [
pointer,
]).whenComplete(_cleanup);
}
}
void error(List<Object?> message) {
var errorResponseInfo = message[responseError]! as List<Object?>;
var errorType = errorResponseInfo[_errorResponseErrorType];
if (errorType == _illegalArgumentResponse) {
controller.addError(ArgumentError());
} else if (errorType == _osErrorResponse) {
var err = OSError(
errorResponseInfo[_osErrorResponseMessage] as String,
errorResponseInfo[_osErrorResponseErrorCode] as int,
);
var errorPath = message[responsePath];
if (errorPath == null) {
errorPath = utf8.decode(rawPath, allowMalformed: true);
} else if (errorPath is Uint8List) {
errorPath = utf8.decode(errorPath, allowMalformed: true);
}
controller.addError(
FileSystemException._fromOSError(
err,
"Directory listing failed",
errorPath as String,
),
);
} else {
controller.addError(FileSystemException("Internal error"));
}
}
}