OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of dart.io; |
| 6 |
| 7 /** |
| 8 * [Link] objects are references to filesystem links. |
| 9 * |
| 10 */ |
| 11 abstract class Link implements FileSystemEntity { |
| 12 /** |
| 13 * Creates a Link object. |
| 14 */ |
| 15 factory Link(String path) => new _Link(path); |
| 16 |
| 17 /** |
| 18 * Creates a [Link] object. |
| 19 * |
| 20 * If [path] is a relative path, it will be interpreted relative to the |
| 21 * current working directory (see [Directory.current]), when used. |
| 22 * |
| 23 * If [path] is an absolute path, it will be immune to changes to the |
| 24 * current working directory. |
| 25 */ |
| 26 factory Link.fromUri(Uri uri) => new Link(uri.toFilePath()); |
| 27 |
| 28 /** |
| 29 * Creates a symbolic link. Returns a [:Future<Link>:] that completes with |
| 30 * the link when it has been created. If the link exists, |
| 31 * the future will complete with an error. |
| 32 * |
| 33 * If [recursive] is false, the default, the link is created |
| 34 * only if all directories in its path exist. |
| 35 * If [recursive] is true, all non-existing path |
| 36 * components are created. The directories in the path of [target] are |
| 37 * not affected, unless they are also in [path]. |
| 38 * |
| 39 * On the Windows platform, this will only work with directories, and the |
| 40 * target directory must exist. The link will be created as a Junction. |
| 41 * Only absolute links will be created, and relative paths to the target |
| 42 * will be converted to absolute paths by joining them with the path of the |
| 43 * directory the link is contained in. |
| 44 * |
| 45 * On other platforms, the posix symlink() call is used to make a symbolic |
| 46 * link containing the string [target]. If [target] is a relative path, |
| 47 * it will be interpreted relative to the directory containing the link. |
| 48 */ |
| 49 Future<Link> create(String target, {bool recursive: false}); |
| 50 |
| 51 /** |
| 52 * Synchronously create the link. Calling [createSync] on an existing link |
| 53 * will throw an exception. |
| 54 * |
| 55 * If [recursive] is false, the default, the link is created only if all |
| 56 * directories in its path exist. If [recursive] is true, all |
| 57 * non-existing path components are created. The directories in |
| 58 * the path of [target] are not affected, unless they are also in [path]. |
| 59 * |
| 60 * On the Windows platform, this will only work with directories, and the |
| 61 * target directory must exist. The link will be created as a Junction. |
| 62 * Only absolute links will be created, and relative paths to the target |
| 63 * will be converted to absolute paths. |
| 64 * |
| 65 * On other platforms, the posix symlink() call is used to make a symbolic |
| 66 * link containing the string [target]. If [target] is a relative path, |
| 67 * it will be interpreted relative to the directory containing the link. |
| 68 */ |
| 69 void createSync(String target, {bool recursive: false}); |
| 70 |
| 71 /** |
| 72 * Synchronously updates the link. Calling [updateSync] on a non-existing link |
| 73 * will throw an exception. |
| 74 * |
| 75 * On the Windows platform, this will only work with directories, and the |
| 76 * target directory must exist. |
| 77 */ |
| 78 void updateSync(String target); |
| 79 |
| 80 /** |
| 81 * Updates the link. Returns a [:Future<Link>:] that completes with the |
| 82 * link when it has been updated. Calling [update] on a non-existing link |
| 83 * will complete its returned future with an exception. |
| 84 * |
| 85 * On the Windows platform, this will only work with directories, and the |
| 86 * target directory must exist. |
| 87 */ |
| 88 Future<Link> update(String target); |
| 89 |
| 90 Future<String> resolveSymbolicLinks(); |
| 91 |
| 92 String resolveSymbolicLinksSync(); |
| 93 |
| 94 /** |
| 95 * Renames this link. Returns a `Future<Link>` that completes |
| 96 * with a [Link] instance for the renamed link. |
| 97 * |
| 98 * If [newPath] identifies an existing link, that link is |
| 99 * replaced. If [newPath] identifies an existing file or directory, |
| 100 * the operation fails and the future completes with an exception. |
| 101 */ |
| 102 Future<Link> rename(String newPath); |
| 103 |
| 104 /** |
| 105 * Synchronously renames this link. Returns a [Link] |
| 106 * instance for the renamed link. |
| 107 * |
| 108 * If [newPath] identifies an existing link, that link is |
| 109 * replaced. If [newPath] identifies an existing file or directory |
| 110 * the operation fails and an exception is thrown. |
| 111 */ |
| 112 Link renameSync(String newPath); |
| 113 |
| 114 /** |
| 115 * Returns a [Link] instance whose path is the absolute path to [this]. |
| 116 * |
| 117 * The absolute path is computed by prefixing |
| 118 * a relative path with the current working directory, and returning |
| 119 * an absolute path unchanged. |
| 120 */ |
| 121 Link get absolute; |
| 122 |
| 123 /** |
| 124 * Gets the target of the link. Returns a future that completes with |
| 125 * the path to the target. |
| 126 * |
| 127 * If the returned target is a relative path, it is relative to the |
| 128 * directory containing the link. |
| 129 * |
| 130 * If the link does not exist, or is not a link, the future completes with |
| 131 * a FileSystemException. |
| 132 */ |
| 133 Future<String> target(); |
| 134 |
| 135 /** |
| 136 * Synchronously gets the target of the link. Returns the path to the target. |
| 137 * |
| 138 * If the returned target is a relative path, it is relative to the |
| 139 * directory containing the link. |
| 140 * |
| 141 * If the link does not exist, or is not a link, throws a FileSystemException. |
| 142 */ |
| 143 String targetSync(); |
| 144 } |
| 145 |
| 146 |
| 147 class _Link extends FileSystemEntity implements Link { |
| 148 final String path; |
| 149 |
| 150 _Link(this.path) { |
| 151 if (path is! String) { |
| 152 throw new ArgumentError('${Error.safeToString(path)} ' |
| 153 'is not a String'); |
| 154 } |
| 155 } |
| 156 |
| 157 String toString() => "Link: '$path'"; |
| 158 |
| 159 Future<bool> exists() => FileSystemEntity.isLink(path); |
| 160 |
| 161 bool existsSync() => FileSystemEntity.isLinkSync(path); |
| 162 |
| 163 Link get absolute => new Link(_absolutePath); |
| 164 |
| 165 Future<FileStat> stat() => FileStat.stat(path); |
| 166 |
| 167 FileStat statSync() => FileStat.statSync(path); |
| 168 |
| 169 Future<Link> create(String target, {bool recursive: false}) { |
| 170 if (Platform.isWindows) { |
| 171 target = _makeWindowsLinkTarget(target); |
| 172 } |
| 173 var result = recursive ? parent.create(recursive: true) |
| 174 : new Future.value(null); |
| 175 return result |
| 176 .then((_) => _IOService._dispatch(_FILE_CREATE_LINK, [path, target])) |
| 177 .then((response) { |
| 178 if (_isErrorResponse(response)) { |
| 179 throw _exceptionFromResponse( |
| 180 response, "Cannot create link to target '$target'", path); |
| 181 } |
| 182 return this; |
| 183 }); |
| 184 } |
| 185 |
| 186 void createSync(String target, {bool recursive: false}) { |
| 187 if (recursive) { |
| 188 parent.createSync(recursive: true); |
| 189 } |
| 190 if (Platform.isWindows) { |
| 191 target = _makeWindowsLinkTarget(target); |
| 192 } |
| 193 var result = _File._createLink(path, target); |
| 194 throwIfError(result, "Cannot create link", path); |
| 195 } |
| 196 |
| 197 // Put target into the form "\??\C:\my\target\dir". |
| 198 String _makeWindowsLinkTarget(String target) { |
| 199 Uri base = new Uri.file('${Directory.current.path}\\'); |
| 200 Uri link = new Uri.file(path); |
| 201 Uri destination = new Uri.file(target); |
| 202 String result = base.resolveUri(link).resolveUri(destination).toFilePath(); |
| 203 if (result.length > 3 && result[1] == ':' && result[2] == '\\') { |
| 204 return '\\??\\$result'; |
| 205 } else { |
| 206 throw new FileSystemException( |
| 207 'Target $result of Link.create on Windows cannot be converted' + |
| 208 ' to start with a drive letter. Unexpected error.'); |
| 209 } |
| 210 } |
| 211 |
| 212 void updateSync(String target) { |
| 213 // TODO(12414): Replace with atomic update, where supported by platform. |
| 214 // Atomically changing a link can be done by creating the new link, with |
| 215 // a different name, and using the rename() posix call to move it to |
| 216 // the old name atomically. |
| 217 deleteSync(); |
| 218 createSync(target); |
| 219 } |
| 220 |
| 221 Future<Link> update(String target) { |
| 222 // TODO(12414): Replace with atomic update, where supported by platform. |
| 223 // Atomically changing a link can be done by creating the new link, with |
| 224 // a different name, and using the rename() posix call to move it to |
| 225 // the old name atomically. |
| 226 return delete().then((_) => create(target)); |
| 227 } |
| 228 |
| 229 Future<Link> _delete({bool recursive: false}) { |
| 230 if (recursive) { |
| 231 return new Directory(path).delete(recursive: true).then((_) => this); |
| 232 } |
| 233 return _IOService._dispatch(_FILE_DELETE_LINK, [path]).then((response) { |
| 234 if (_isErrorResponse(response)) { |
| 235 throw _exceptionFromResponse(response, "Cannot delete link", path); |
| 236 } |
| 237 return this; |
| 238 }); |
| 239 } |
| 240 |
| 241 void _deleteSync({bool recursive: false}) { |
| 242 if (recursive) { |
| 243 return new Directory(path).deleteSync(recursive: true); |
| 244 } |
| 245 var result = _File._deleteLinkNative(path); |
| 246 throwIfError(result, "Cannot delete link", path); |
| 247 } |
| 248 |
| 249 Future<Link> rename(String newPath) { |
| 250 return _IOService._dispatch(_FILE_RENAME_LINK, [path, newPath]) |
| 251 .then((response) { |
| 252 if (_isErrorResponse(response)) { |
| 253 throw _exceptionFromResponse( |
| 254 response, "Cannot rename link to '$newPath'", path); |
| 255 } |
| 256 return new Link(newPath); |
| 257 }); |
| 258 } |
| 259 |
| 260 Link renameSync(String newPath) { |
| 261 var result = _File._renameLink(path, newPath); |
| 262 throwIfError(result, "Cannot rename link '$path' to '$newPath'"); |
| 263 return new Link(newPath); |
| 264 } |
| 265 |
| 266 Future<String> target() { |
| 267 return _IOService._dispatch(_FILE_LINK_TARGET, [path]).then((response) { |
| 268 if (_isErrorResponse(response)) { |
| 269 throw _exceptionFromResponse( |
| 270 response, "Cannot get target of link", path); |
| 271 } |
| 272 return response; |
| 273 }); |
| 274 } |
| 275 |
| 276 String targetSync() { |
| 277 var result = _File._linkTarget(path); |
| 278 throwIfError(result, "Cannot read link", path); |
| 279 return result; |
| 280 } |
| 281 |
| 282 static throwIfError(Object result, String msg, [String path = ""]) { |
| 283 if (result is OSError) { |
| 284 throw new FileSystemException(msg, path, result); |
| 285 } |
| 286 } |
| 287 |
| 288 bool _isErrorResponse(response) { |
| 289 return response is List && response[0] != _SUCCESS_RESPONSE; |
| 290 } |
| 291 |
| 292 _exceptionFromResponse(response, String message, String path) { |
| 293 assert(_isErrorResponse(response)); |
| 294 switch (response[_ERROR_RESPONSE_ERROR_TYPE]) { |
| 295 case _ILLEGAL_ARGUMENT_RESPONSE: |
| 296 return new ArgumentError(); |
| 297 case _OSERROR_RESPONSE: |
| 298 var err = new OSError(response[_OSERROR_RESPONSE_MESSAGE], |
| 299 response[_OSERROR_RESPONSE_ERROR_CODE]); |
| 300 return new FileSystemException(message, path, err); |
| 301 default: |
| 302 return new Exception("Unknown error"); |
| 303 } |
| 304 } |
| 305 } |
OLD | NEW |