blob: 79f865638da568cab80412bbba7675595a609857 [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;
/**
* [Link] objects are 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. Returns a [:Future<Link>:] that completes with
* the link when it has been created. If the link 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 path
* components are created. 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 path components are created. 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] instance for the renamed link.
*
* If [newPath] identifies an existing link, that link is
* replaced. 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
* replaced. If [newPath] identifies an existing file or directory
* the operation fails and an exception is thrown.
*/
Link renameSync(String newPath);
/**
* Returns 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, and 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 {
String _path;
Uint8List _rawPath;
_Link(String path) {
ArgumentError.checkNotNull(path, '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 => new Link.fromRawPath(_rawAbsolutePath);
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");
}
}
}