blob: c7320eab93bb1e5ed5113a29e9aa9a9885ba2ade [file] [log] [blame]
// Copyright (c) 2013, 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;
/// References to filesystem links.
@pragma("vm:entry-point")
abstract class Link implements FileSystemEntity {
/// Creates a Link object.
@pragma("vm:entry-point")
factory Link(String path) {
final IOOverrides? overrides = IOOverrides.current;
if (overrides == null) {
return new _Link(path);
}
return overrides.createLink(path);
}
@pragma("vm:entry-point")
factory Link.fromRawPath(Uint8List rawPath) {
// TODO(bkonyi): handle overrides
return new _Link.fromRawPath(rawPath);
}
/// Creates a [Link] object.
///
/// If [path] is a relative path, it will be interpreted relative to the
/// current working directory (see [Directory.current]), when used.
///
/// If [path] is an absolute path, it will be immune to changes to the
/// current working directory.
factory Link.fromUri(Uri uri) => new Link(uri.toFilePath());
/// Creates a symbolic link in the file system.
///
/// Returns a `Future<Link>` that completes with
/// the link when it has been created. If the link path already exists,
/// the future will complete with an error.
///
/// If [recursive] is `false`, the default, the link is created
/// only if all directories in its path exist.
/// If [recursive] is `true`, all non-existing parent paths
/// are created first. The directories in the path of [target] are
/// not affected, unless they are also in [path].
///
/// On the Windows platform, this call will create a true symbolic link
/// instead of a Junction. In order to create a symbolic link on Windows, Dart
/// must be run in Administrator mode or the system must have Developer Mode
/// enabled, otherwise a [FileSystemException] will be raised with
/// `ERROR_PRIVILEGE_NOT_HELD` set as the errno when this call is made.
///
/// On other platforms, the POSIX `symlink()` call is used to make a symbolic
/// link containing the string [target]. If [target] is a relative path,
/// it will be interpreted relative to the directory containing the link.
Future<Link> create(String target, {bool recursive = false});
/// Synchronously create the link. Calling [createSync] on an existing link
/// will throw an exception.
///
/// If [recursive] is `false`, the default, the link is created only if all
/// directories in its path exist. If [recursive] is `true`, all
/// non-existing parent paths are created first. The directories in
/// the path of [target] are not affected, unless they are also in [path].
///
/// On the Windows platform, this call will create a true symbolic link
/// instead of a Junction. In order to create a symbolic link on Windows, Dart
/// must be run in Administrator mode or the system must have Developer Mode
/// enabled, otherwise a [FileSystemException] will be raised with
/// `ERROR_PRIVILEGE_NOT_HELD` set as the errno when this call is made.
///
/// On other platforms, the POSIX `symlink()` call is used to make a symbolic
/// link containing the string [target]. If [target] is a relative path,
/// it will be interpreted relative to the directory containing the link.
void createSync(String target, {bool recursive = false});
/// Synchronously updates the link.
///
/// Calling [updateSync] on a non-existing link will throw an exception.
void updateSync(String target);
/// Updates the link.
///
/// Returns a `Future<Link>` that completes with the
/// link when it has been updated. Calling [update] on a non-existing link
/// will complete its returned future with an exception.
Future<Link> update(String target);
Future<String> resolveSymbolicLinks();
String resolveSymbolicLinksSync();
/// Renames this link.
///
/// Returns a `Future<Link>` that completes with a [Link]
/// for the renamed link.
///
/// If [newPath] identifies an existing link, that link is
/// removed first. If [newPath] identifies an existing file or directory,
/// the operation fails and the future completes with an exception.
Future<Link> rename(String newPath);
/// Synchronously renames this link.
///
/// Returns a [Link] instance for the renamed link.
///
/// If [newPath] identifies an existing link, that link is
/// removed first. If [newPath] identifies an existing file or directory
/// the operation fails and an exception is thrown.
Link renameSync(String newPath);
/// A [Link] instance whose path is the absolute path to [this].
///
/// The absolute path is computed by prefixing
/// a relative path with the current working directory, or returning
/// an absolute path unchanged.
Link get absolute;
/// Gets the target of the link.
///
/// Returns a future that completes with the path to the target.
///
/// If the returned target is a relative path, it is relative to the
/// directory containing the link.
///
/// If the link does not exist, or is not a link, the future completes with
/// a [FileSystemException].
Future<String> target();
/// Synchronously gets the target of the link.
///
/// Returns the path to the target.
///
/// If the returned target is a relative path, it is relative to the
/// directory containing the link.
///
/// If the link does not exist, or is not a link,
/// throws a [FileSystemException].
String targetSync();
}
class _Link extends FileSystemEntity implements Link {
final String _path;
final Uint8List _rawPath;
_Link(String path)
: _path = path,
_rawPath = FileSystemEntity._toUtf8Array(path);
_Link.fromRawPath(Uint8List rawPath)
: _rawPath = FileSystemEntity._toNullTerminatedUtf8Array(rawPath),
_path = FileSystemEntity._toStringFromUtf8Array(rawPath);
String get path => _path;
String toString() => "Link: '$path'";
Future<bool> exists() => FileSystemEntity._isLinkRaw(_rawPath);
bool existsSync() => FileSystemEntity._isLinkRawSync(_rawPath);
Link get absolute => isAbsolute ? this : _Link(_absolutePath);
Future<Link> create(String target, {bool recursive = false}) {
var result =
recursive ? parent.create(recursive: true) : new Future.value(null);
return result
.then((_) => _File._dispatchWithNamespace(
_IOService.fileCreateLink, [null, _rawPath, target]))
.then((response) {
if (_isErrorResponse(response)) {
throw _exceptionFromResponse(
response, "Cannot create link to target '$target'", path);
}
return this;
});
}
void createSync(String target, {bool recursive = false}) {
if (recursive) {
parent.createSync(recursive: true);
}
var result = _File._createLink(_Namespace._namespace, _rawPath, target);
throwIfError(result, "Cannot create link", path);
}
void updateSync(String target) {
// TODO(12414): Replace with atomic update, where supported by platform.
// Atomically changing a link can be done by creating the new link, with
// a different name, and using the rename() posix call to move it to
// the old name atomically.
deleteSync();
createSync(target);
}
Future<Link> update(String target) {
// TODO(12414): Replace with atomic update, where supported by platform.
// Atomically changing a link can be done by creating the new link, with
// a different name, and using the rename() posix call to move it to
// the old name atomically.
return delete().then<Link>((_) => create(target));
}
Future<Link> _delete({bool recursive = false}) {
if (recursive) {
return new Directory.fromRawPath(_rawPath)
.delete(recursive: true)
.then((_) => this);
}
return _File._dispatchWithNamespace(
_IOService.fileDeleteLink, [null, _rawPath]).then((response) {
if (_isErrorResponse(response)) {
throw _exceptionFromResponse(response, "Cannot delete link", path);
}
return this;
});
}
void _deleteSync({bool recursive = false}) {
if (recursive) {
return new Directory.fromRawPath(_rawPath).deleteSync(recursive: true);
}
var result = _File._deleteLinkNative(_Namespace._namespace, _rawPath);
throwIfError(result, "Cannot delete link", path);
}
Future<Link> rename(String newPath) {
return _File._dispatchWithNamespace(
_IOService.fileRenameLink, [null, _rawPath, newPath]).then((response) {
if (_isErrorResponse(response)) {
throw _exceptionFromResponse(
response, "Cannot rename link to '$newPath'", path);
}
return new Link(newPath);
});
}
Link renameSync(String newPath) {
var result = _File._renameLink(_Namespace._namespace, _rawPath, newPath);
throwIfError(result, "Cannot rename link '$path' to '$newPath'");
return new Link(newPath);
}
Future<String> target() {
return _File._dispatchWithNamespace(
_IOService.fileLinkTarget, [null, _rawPath]).then((response) {
if (_isErrorResponse(response)) {
throw _exceptionFromResponse(
response, "Cannot get target of link", path);
}
return response;
});
}
String targetSync() {
var result = _File._linkTarget(_Namespace._namespace, _rawPath);
throwIfError(result, "Cannot read link", path);
return result;
}
static throwIfError(Object? result, String msg, [String path = ""]) {
if (result is OSError) {
throw new FileSystemException(msg, path, result);
}
}
bool _isErrorResponse(response) {
return response is List && response[0] != _successResponse;
}
_exceptionFromResponse(response, String message, String path) {
assert(_isErrorResponse(response));
switch (response[_errorResponseErrorType]) {
case _illegalArgumentResponse:
return new ArgumentError();
case _osErrorResponse:
var err = new OSError(response[_osErrorResponseMessage],
response[_osErrorResponseErrorCode]);
return new FileSystemException(message, path, err);
default:
return new Exception("Unknown error");
}
}
}