blob: 8ee89d2c00841ac56a22eb3032d79fc30465f01b [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 interface 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.
///
/// The created link will point to the path at [target], whether that path
/// exists or not.
///
/// 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. Windows treats links to files and links to
/// directories as different and non-interchangable kinds of links.
/// Each link is either a file-link or a directory-link, and the type is
/// chosen when the link is created, and the link then counts as either a
/// file or directory for most purposes. Different Win32 API calls are
/// used to manipulate each. For example, the `DeleteFile` function is
/// used to delete links to files, and `RemoveDirectory` must be used to
/// delete links to directories.
///
/// The created Windows symbolic link will match the type of the [target],
/// if [target] exists, otherwise a file-link is created. The type of the
/// created link will not change if [target] is later replaced by something
/// of a different type, but then the link will not be resolvable by
/// [resolveSymbolicLinks].
///
/// 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});
/// Creates a symbolic link in the file system.
///
/// The created link will point to the path at [target], whether that path
/// exists or not.
///
/// If the link path already exists, an exception will be thrown.
///
/// 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. Windows treats links to files and links to
/// directories as different and non-interchangable kinds of links.
/// Each link is either a file-link or a directory-link, and the type is
/// chosen when the link is created, and the link then counts as either a
/// file or directory for most purposes. Different Win32 API calls are
/// used to manipulate each. For example, the `DeleteFile` function is
/// used to delete links to files, and `RemoveDirectory` must be used to
/// delete links to directories.
///
/// The created Windows symbolic link will match the type of the [target],
/// if [target] exists, otherwise a file-link is created. The type of the
/// created link will not change if [target] is later replaced by something
/// of a different type, but then the link will not be resolvable by
/// [resolveSymbolicLinks].
///
/// 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 an existing link.
///
/// Deletes the existing link at [path] and uses [createSync] to create a new
/// link to [target]. Throws [PathNotFoundException] if the original link
/// does not exist or any [FileSystemException] that [deleteSync] or
/// [createSync] can throw.
void updateSync(String target);
/// Updates an existing link.
///
/// Deletes the existing link at [path] and creates a new link to [target],
/// using [create].
///
/// Returns a future which completes with this `Link` if successful,
/// and with a [PathNotFoundException] if there is no existing link at [path],
/// or with any [FileSystemException] that [delete] or [create] can throw.
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 file or link, that entity is removed
/// first. If [newPath] identifies an existing directory then the future
/// completes with a [FileSystemException].
Future<Link> rename(String newPath);
/// Synchronously renames this link.
///
/// Returns a [Link] instance for the renamed link.
///
/// If [newPath] identifies an existing file or link, that entity is removed
/// first. If [newPath] identifies an existing directory then
/// [FileSystemException] is thrown.
Link renameSync(String newPath);
/// Deletes this [Link].
///
/// If [recursive] is `false`:
///
/// * If [path] corresponds to a link then that path is deleted. Otherwise,
/// [delete] completes with a [FileSystemException].
///
/// If [recursive] is `true`:
///
/// * The [FileSystemEntity] at [path] is deleted regardless of type. If
/// [path] corresponds to a file or link, then that file or link is
/// deleted. If [path] corresponds to a directory, then it and all
/// sub-directories and files in those directories are deleted. Links
/// are not followed when deleting recursively. Only the link is deleted,
/// not its target. This behavior allows [delete] to be used to
/// unconditionally delete any file system object.
///
/// If this [Link] cannot be deleted, then [delete] completes with a
/// [FileSystemException].
Future<FileSystemEntity> delete({bool recursive = false});
/// Synchronously deletes this [Link].
///
/// If [recursive] is `false`:
///
/// * If [path] corresponds to a link then that path is deleted. Otherwise,
/// [delete] throws a [FileSystemException].
///
/// If [recursive] is `true`:
///
/// * The [FileSystemEntity] at [path] is deleted regardless of type. If
/// [path] corresponds to a file or link, then that file or link is
/// deleted. If [path] corresponds to a directory, then it and all
/// sub-directories and files in those directories are deleted. Links
/// are not followed when deleting recursively. Only the link is deleted,
/// not its target. This behavior allows [delete] to be used to
/// unconditionally delete any file system object.
///
/// If this [Link] cannot be deleted, then [delete] throws a
/// [FileSystemException].
void deleteSync({bool recursive = false});
/// A [Link] instance whose path is the absolute path to this [Link].
///
/// 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) {
_checkForErrorResponse(
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) {
_checkForErrorResponse(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) {
_checkForErrorResponse(
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) {
_checkForErrorResponse(response, "Cannot get target of link", path);
return response as String;
});
}
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 FileSystemException._fromOSError(result, msg, path);
}
}
}