| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /// Helper functionality to make working with IO easier. |
| 6 * Helper functionality to make working with IO easier. | |
| 7 */ | |
| 8 library io; | 6 library io; |
| 9 | 7 |
| 10 import 'dart:io'; | 8 import 'dart:io'; |
| 11 import 'dart:isolate'; | 9 import 'dart:isolate'; |
| 12 import 'dart:json'; | 10 import 'dart:json'; |
| 13 import 'dart:uri'; | 11 import 'dart:uri'; |
| 14 | 12 |
| 15 import '../../pkg/path/lib/path.dart' as path; | 13 import '../../pkg/path/lib/path.dart' as path; |
| 16 import 'log.dart' as log; | 14 import 'log.dart' as log; |
| 17 import 'utils.dart'; | 15 import 'utils.dart'; |
| 18 | 16 |
| 19 bool _isGitInstalledCache; | 17 bool _isGitInstalledCache; |
| 20 | 18 |
| 21 /// The cached Git command. | 19 /// The cached Git command. |
| 22 String _gitCommandCache; | 20 String _gitCommandCache; |
| 23 | 21 |
| 24 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 22 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
| 25 | 23 |
| 26 /** | 24 /// Joins a number of path string parts into a single path. Handles |
| 27 * Joins a number of path string parts into a single path. Handles | 25 /// platform-specific path separators. Parts can be [String], [Directory], or |
| 28 * platform-specific path separators. Parts can be [String], [Directory], or | 26 /// [File] objects. |
| 29 * [File] objects. | |
| 30 */ | |
| 31 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { | 27 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { |
| 32 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] | 28 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] |
| 33 .map((part) => part == null ? null : _getPath(part)); | 29 .map((part) => part == null ? null : _getPath(part)); |
| 34 | 30 |
| 35 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], | 31 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], |
| 36 parts[6], parts[7]); | 32 parts[6], parts[7]); |
| 37 } | 33 } |
| 38 | 34 |
| 39 /// Gets the basename, the file name without any leading directory path, for | 35 /// Gets the basename, the file name without any leading directory path, for |
| 40 /// [file], which can either be a [String], [File], or [Directory]. | 36 /// [file], which can either be a [String], [File], or [Directory]. |
| 41 String basename(file) => path.basename(_getPath(file)); | 37 String basename(file) => path.basename(_getPath(file)); |
| 42 | 38 |
| 43 /// Gets the the leading directory path for [file], which can either be a | 39 /// Gets the the leading directory path for [file], which can either be a |
| 44 /// [String], [File], or [Directory]. | 40 /// [String], [File], or [Directory]. |
| 45 String dirname(file) => path.dirname(_getPath(file)); | 41 String dirname(file) => path.dirname(_getPath(file)); |
| 46 | 42 |
| 47 /// Splits [entry] into its individual components. | 43 /// Splits [entry] into its individual components. |
| 48 List<String> splitPath(entry) => path.split(_getPath(entry)); | 44 List<String> splitPath(entry) => path.split(_getPath(entry)); |
| 49 | 45 |
| 50 /// Returns whether or not [entry] is nested somewhere within [dir]. This just | 46 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
| 51 /// performs a path comparison; it doesn't look at the actual filesystem. | 47 /// performs a path comparison; it doesn't look at the actual filesystem. |
| 52 bool isBeneath(entry, dir) { | 48 bool isBeneath(entry, dir) { |
| 53 var relative = relativeTo(entry, dir); | 49 var relative = relativeTo(entry, dir); |
| 54 return !path.isAbsolute(relative) && splitPath(relative)[0] != '..'; | 50 return !path.isAbsolute(relative) && splitPath(relative)[0] != '..'; |
| 55 } | 51 } |
| 56 | 52 |
| 57 /// Returns the path to [target] from [base]. | 53 /// Returns the path to [target] from [base]. |
| 58 String relativeTo(target, base) => path.relative(target, from: base); | 54 String relativeTo(target, base) => path.relative(target, from: base); |
| 59 | 55 |
| 60 /** | 56 /// Asynchronously determines if [path], which can be a [String] file path, a |
| 61 * Asynchronously determines if [path], which can be a [String] file path, a | 57 /// [File], or a [Directory] exists on the file system. Returns a [Future] that |
| 62 * [File], or a [Directory] exists on the file system. Returns a [Future] that | 58 /// completes with the result. |
| 63 * completes with the result. | |
| 64 */ | |
| 65 Future<bool> exists(path) { | 59 Future<bool> exists(path) { |
| 66 path = _getPath(path); | 60 path = _getPath(path); |
| 67 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { | 61 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { |
| 68 return results[0] || results[1]; | 62 return results[0] || results[1]; |
| 69 }); | 63 }); |
| 70 } | 64 } |
| 71 | 65 |
| 72 /** | 66 /// Asynchronously determines if [file], which can be a [String] file path or a |
| 73 * Asynchronously determines if [file], which can be a [String] file path or a | 67 /// [File], exists on the file system. Returns a [Future] that completes with |
| 74 * [File], exists on the file system. Returns a [Future] that completes with | 68 /// the result. |
| 75 * the result. | |
| 76 */ | |
| 77 Future<bool> fileExists(file) { | 69 Future<bool> fileExists(file) { |
| 78 var path = _getPath(file); | 70 var path = _getPath(file); |
| 79 return log.ioAsync("Seeing if file $path exists.", | 71 return log.ioAsync("Seeing if file $path exists.", |
| 80 new File(path).exists(), | 72 new File(path).exists(), |
| 81 (exists) => "File $path ${exists ? 'exists' : 'does not exist'}."); | 73 (exists) => "File $path ${exists ? 'exists' : 'does not exist'}."); |
| 82 } | 74 } |
| 83 | 75 |
| 84 /** | 76 /// Reads the contents of the text file [file], which can either be a [String] |
| 85 * Reads the contents of the text file [file], which can either be a [String] or | 77 /// or a [File]. |
| 86 * a [File]. | |
| 87 */ | |
| 88 Future<String> readTextFile(file) { | 78 Future<String> readTextFile(file) { |
| 89 var path = _getPath(file); | 79 var path = _getPath(file); |
| 90 return log.ioAsync("Reading text file $path.", | 80 return log.ioAsync("Reading text file $path.", |
| 91 new File(path).readAsString(Encoding.UTF_8), | 81 new File(path).readAsString(Encoding.UTF_8), |
| 92 (contents) { | 82 (contents) { |
| 93 // Sanity check: don't spew a huge file. | 83 // Sanity check: don't spew a huge file. |
| 94 if (contents.length < 1024 * 1024) { | 84 if (contents.length < 1024 * 1024) { |
| 95 return "Read $path. Contents:\n$contents"; | 85 return "Read $path. Contents:\n$contents"; |
| 96 } else { | 86 } else { |
| 97 return "Read ${contents.length} characters from $path."; | 87 return "Read ${contents.length} characters from $path."; |
| 98 } | 88 } |
| 99 }); | 89 }); |
| 100 } | 90 } |
| 101 | 91 |
| 102 /** | 92 /// Creates [file] (which can either be a [String] or a [File]), and writes |
| 103 * Creates [file] (which can either be a [String] or a [File]), and writes | 93 /// [contents] to it. Completes when the file is written and closed. |
| 104 * [contents] to it. Completes when the file is written and closed. | 94 /// |
| 105 * | 95 /// If [dontLogContents] is true, the contents of the file will never be logged. |
| 106 * If [dontLogContents] is true, the contents of the file will never be logged. | |
| 107 */ | |
| 108 Future<File> writeTextFile(file, String contents, {dontLogContents: false}) { | 96 Future<File> writeTextFile(file, String contents, {dontLogContents: false}) { |
| 109 var path = _getPath(file); | 97 var path = _getPath(file); |
| 110 file = new File(path); | 98 file = new File(path); |
| 111 | 99 |
| 112 // Sanity check: don't spew a huge file. | 100 // Sanity check: don't spew a huge file. |
| 113 log.io("Writing ${contents.length} characters to text file $path."); | 101 log.io("Writing ${contents.length} characters to text file $path."); |
| 114 if (!dontLogContents && contents.length < 1024 * 1024) { | 102 if (!dontLogContents && contents.length < 1024 * 1024) { |
| 115 log.fine("Contents:\n$contents"); | 103 log.fine("Contents:\n$contents"); |
| 116 } | 104 } |
| 117 | 105 |
| 118 return file.open(FileMode.WRITE).chain((opened) { | 106 return file.open(FileMode.WRITE).chain((opened) { |
| 119 return opened.writeString(contents).chain((ignore) { | 107 return opened.writeString(contents).chain((ignore) { |
| 120 return opened.close().transform((_) { | 108 return opened.close().transform((_) { |
| 121 log.fine("Wrote text file $path."); | 109 log.fine("Wrote text file $path."); |
| 122 return file; | 110 return file; |
| 123 }); | 111 }); |
| 124 }); | 112 }); |
| 125 }); | 113 }); |
| 126 } | 114 } |
| 127 | 115 |
| 128 /** | 116 /// Asynchronously deletes [file], which can be a [String] or a [File]. Returns |
| 129 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a | 117 /// a [Future] that completes when the deletion is done. |
| 130 * [Future] that completes when the deletion is done. | |
| 131 */ | |
| 132 Future<File> deleteFile(file) { | 118 Future<File> deleteFile(file) { |
| 133 var path = _getPath(file); | 119 var path = _getPath(file); |
| 134 return log.ioAsync("delete file $path", | 120 return log.ioAsync("delete file $path", |
| 135 new File(path).delete()); | 121 new File(path).delete()); |
| 136 } | 122 } |
| 137 | 123 |
| 138 /// Writes [stream] to a new file at [path], which may be a [String] or a | 124 /// Writes [stream] to a new file at [path], which may be a [String] or a |
| 139 /// [File]. Will replace any file already at that path. Completes when the file | 125 /// [File]. Will replace any file already at that path. Completes when the file |
| 140 /// is done being written. | 126 /// is done being written. |
| 141 Future<File> createFileFromStream(InputStream stream, path) { | 127 Future<File> createFileFromStream(InputStream stream, path) { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 168 log.fine("Got error after stream was closed: $error"); | 154 log.fine("Got error after stream was closed: $error"); |
| 169 } | 155 } |
| 170 } | 156 } |
| 171 | 157 |
| 172 stream.onError = completeError; | 158 stream.onError = completeError; |
| 173 outputStream.onError = completeError; | 159 outputStream.onError = completeError; |
| 174 | 160 |
| 175 return completer.future; | 161 return completer.future; |
| 176 } | 162 } |
| 177 | 163 |
| 178 /** | 164 /// Creates a directory [dir]. Returns a [Future] that completes when the |
| 179 * Creates a directory [dir]. Returns a [Future] that completes when the | 165 /// directory is created. |
| 180 * directory is created. | |
| 181 */ | |
| 182 Future<Directory> createDir(dir) { | 166 Future<Directory> createDir(dir) { |
| 183 dir = _getDirectory(dir); | 167 dir = _getDirectory(dir); |
| 184 return log.ioAsync("create directory ${dir.path}", | 168 return log.ioAsync("create directory ${dir.path}", |
| 185 dir.create()); | 169 dir.create()); |
| 186 } | 170 } |
| 187 | 171 |
| 188 /** | 172 /// Ensures that [path] and all its parent directories exist. If they don't |
| 189 * Ensures that [path] and all its parent directories exist. If they don't | 173 /// exist, creates them. Returns a [Future] that completes once all the |
| 190 * exist, creates them. Returns a [Future] that completes once all the | 174 /// directories are created. |
| 191 * directories are created. | |
| 192 */ | |
| 193 Future<Directory> ensureDir(path) { | 175 Future<Directory> ensureDir(path) { |
| 194 path = _getPath(path); | 176 path = _getPath(path); |
| 195 log.fine("Ensuring directory $path exists."); | 177 log.fine("Ensuring directory $path exists."); |
| 196 if (path == '.') return new Future.immediate(new Directory('.')); | 178 if (path == '.') return new Future.immediate(new Directory('.')); |
| 197 | 179 |
| 198 return dirExists(path).chain((exists) { | 180 return dirExists(path).chain((exists) { |
| 199 if (exists) { | 181 if (exists) { |
| 200 log.fine("Directory $path already exists."); | 182 log.fine("Directory $path already exists."); |
| 201 return new Future.immediate(new Directory(path)); | 183 return new Future.immediate(new Directory(path)); |
| 202 } | 184 } |
| (...skipping 12 matching lines...) Expand all Loading... |
| 215 | 197 |
| 216 completer.complete(_getDirectory(path)); | 198 completer.complete(_getDirectory(path)); |
| 217 return true; | 199 return true; |
| 218 }); | 200 }); |
| 219 future.then(completer.complete); | 201 future.then(completer.complete); |
| 220 return completer.future; | 202 return completer.future; |
| 221 }); | 203 }); |
| 222 }); | 204 }); |
| 223 } | 205 } |
| 224 | 206 |
| 225 /** | 207 /// Creates a temp directory whose name will be based on [dir] with a unique |
| 226 * Creates a temp directory whose name will be based on [dir] with a unique | 208 /// suffix appended to it. If [dir] is not provided, a temp directory will be |
| 227 * suffix appended to it. If [dir] is not provided, a temp directory will be | 209 /// created in a platform-dependent temporary location. Returns a [Future] that |
| 228 * created in a platform-dependent temporary location. Returns a [Future] that | 210 /// completes when the directory is created. |
| 229 * completes when the directory is created. | |
| 230 */ | |
| 231 Future<Directory> createTempDir([dir = '']) { | 211 Future<Directory> createTempDir([dir = '']) { |
| 232 dir = _getDirectory(dir); | 212 dir = _getDirectory(dir); |
| 233 return log.ioAsync("create temp directory ${dir.path}", | 213 return log.ioAsync("create temp directory ${dir.path}", |
| 234 dir.createTemp()); | 214 dir.createTemp()); |
| 235 } | 215 } |
| 236 | 216 |
| 237 /** | 217 /// Asynchronously recursively deletes [dir], which can be a [String] or a |
| 238 * Asynchronously recursively deletes [dir], which can be a [String] or a | 218 /// [Directory]. Returns a [Future] that completes when the deletion is done. |
| 239 * [Directory]. Returns a [Future] that completes when the deletion is done. | |
| 240 */ | |
| 241 Future<Directory> deleteDir(dir) { | 219 Future<Directory> deleteDir(dir) { |
| 242 dir = _getDirectory(dir); | 220 dir = _getDirectory(dir); |
| 243 | 221 |
| 244 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", | 222 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", |
| 245 dir.delete(recursive: true))); | 223 dir.delete(recursive: true))); |
| 246 } | 224 } |
| 247 | 225 |
| 248 /// Asynchronously lists the contents of [dir], which can be a [String] | 226 /// Asynchronously lists the contents of [dir], which can be a [String] |
| 249 /// directory path or a [Directory]. If [recursive] is `true`, lists | 227 /// directory path or a [Directory]. If [recursive] is `true`, lists |
| 250 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is | 228 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 308 return Futures.wait(children).transform((childContents) { | 286 return Futures.wait(children).transform((childContents) { |
| 309 contents.addAll(flatten(childContents)); | 287 contents.addAll(flatten(childContents)); |
| 310 return contents; | 288 return contents; |
| 311 }); | 289 }); |
| 312 }); | 290 }); |
| 313 } | 291 } |
| 314 | 292 |
| 315 return doList(_getDirectory(dir), new Set<String>()); | 293 return doList(_getDirectory(dir), new Set<String>()); |
| 316 } | 294 } |
| 317 | 295 |
| 318 /** | 296 /// Asynchronously determines if [dir], which can be a [String] directory path |
| 319 * Asynchronously determines if [dir], which can be a [String] directory path | 297 /// or a [Directory], exists on the file system. Returns a [Future] that |
| 320 * or a [Directory], exists on the file system. Returns a [Future] that | 298 /// completes with the result. |
| 321 * completes with the result. | |
| 322 */ | |
| 323 Future<bool> dirExists(dir) { | 299 Future<bool> dirExists(dir) { |
| 324 dir = _getDirectory(dir); | 300 dir = _getDirectory(dir); |
| 325 return log.ioAsync("Seeing if directory ${dir.path} exists.", | 301 return log.ioAsync("Seeing if directory ${dir.path} exists.", |
| 326 dir.exists(), | 302 dir.exists(), |
| 327 (exists) => "Directory ${dir.path} " | 303 (exists) => "Directory ${dir.path} " |
| 328 "${exists ? 'exists' : 'does not exist'}."); | 304 "${exists ? 'exists' : 'does not exist'}."); |
| 329 } | 305 } |
| 330 | 306 |
| 331 /** | 307 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
| 332 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 308 /// new empty directory will be created. Returns a [Future] that completes when |
| 333 * new empty directory will be created. Returns a [Future] that completes when | 309 /// the new clean directory is created. |
| 334 * the new clean directory is created. | |
| 335 */ | |
| 336 Future<Directory> cleanDir(dir) { | 310 Future<Directory> cleanDir(dir) { |
| 337 return dirExists(dir).chain((exists) { | 311 return dirExists(dir).chain((exists) { |
| 338 if (exists) { | 312 if (exists) { |
| 339 // Delete it first. | 313 // Delete it first. |
| 340 return deleteDir(dir).chain((_) => createDir(dir)); | 314 return deleteDir(dir).chain((_) => createDir(dir)); |
| 341 } else { | 315 } else { |
| 342 // Just create it. | 316 // Just create it. |
| 343 return createDir(dir); | 317 return createDir(dir); |
| 344 } | 318 } |
| 345 }); | 319 }); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 380 | 354 |
| 381 // Wait a bit and try again. | 355 // Wait a bit and try again. |
| 382 log.fine("Operation failed, retrying (attempt $attempts)."); | 356 log.fine("Operation failed, retrying (attempt $attempts)."); |
| 383 return sleep(500).chain(makeAttempt); | 357 return sleep(500).chain(makeAttempt); |
| 384 }); | 358 }); |
| 385 } | 359 } |
| 386 | 360 |
| 387 return makeAttempt(null); | 361 return makeAttempt(null); |
| 388 } | 362 } |
| 389 | 363 |
| 390 /** | 364 /// Creates a new symlink that creates an alias from [from] to [to], both of |
| 391 * Creates a new symlink that creates an alias from [from] to [to], both of | 365 /// which can be a [String], [File], or [Directory]. Returns a [Future] which |
| 392 * which can be a [String], [File], or [Directory]. Returns a [Future] which | 366 /// completes to the symlink file (i.e. [to]). |
| 393 * completes to the symlink file (i.e. [to]). | |
| 394 */ | |
| 395 Future<File> createSymlink(from, to) { | 367 Future<File> createSymlink(from, to) { |
| 396 from = _getPath(from); | 368 from = _getPath(from); |
| 397 to = _getPath(to); | 369 to = _getPath(to); |
| 398 | 370 |
| 399 log.fine("Create symlink $from -> $to."); | 371 log.fine("Create symlink $from -> $to."); |
| 400 | 372 |
| 401 var command = 'ln'; | 373 var command = 'ln'; |
| 402 var args = ['-s', from, to]; | 374 var args = ['-s', from, to]; |
| 403 | 375 |
| 404 if (Platform.operatingSystem == 'windows') { | 376 if (Platform.operatingSystem == 'windows') { |
| 405 // Call mklink on Windows to create an NTFS junction point. Only works on | 377 // Call mklink on Windows to create an NTFS junction point. Only works on |
| 406 // Vista or later. (Junction points are available earlier, but the "mklink" | 378 // Vista or later. (Junction points are available earlier, but the "mklink" |
| 407 // command is not.) I'm using a junction point (/j) here instead of a soft | 379 // command is not.) I'm using a junction point (/j) here instead of a soft |
| 408 // link (/d) because the latter requires some privilege shenanigans that | 380 // link (/d) because the latter requires some privilege shenanigans that |
| 409 // I'm not sure how to specify from the command line. | 381 // I'm not sure how to specify from the command line. |
| 410 command = 'mklink'; | 382 command = 'mklink'; |
| 411 args = ['/j', to, from]; | 383 args = ['/j', to, from]; |
| 412 } | 384 } |
| 413 | 385 |
| 414 return runProcess(command, args).transform((result) { | 386 return runProcess(command, args).transform((result) { |
| 415 // TODO(rnystrom): Check exit code and output? | 387 // TODO(rnystrom): Check exit code and output? |
| 416 return new File(to); | 388 return new File(to); |
| 417 }); | 389 }); |
| 418 } | 390 } |
| 419 | 391 |
| 420 /** | 392 /// Creates a new symlink that creates an alias from the `lib` directory of |
| 421 * Creates a new symlink that creates an alias from the `lib` directory of | 393 /// package [from] to [to], both of which can be a [String], [File], or |
| 422 * package [from] to [to], both of which can be a [String], [File], or | 394 /// [Directory]. Returns a [Future] which completes to the symlink file (i.e. |
| 423 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. | 395 /// [to]). If [from] does not have a `lib` directory, this shows a warning if |
| 424 * [to]). If [from] does not have a `lib` directory, this shows a warning if | 396 /// appropriate and then does nothing. |
| 425 * appropriate and then does nothing. | |
| 426 */ | |
| 427 Future<File> createPackageSymlink(String name, from, to, | 397 Future<File> createPackageSymlink(String name, from, to, |
| 428 {bool isSelfLink: false}) { | 398 {bool isSelfLink: false}) { |
| 429 // See if the package has a "lib" directory. | 399 // See if the package has a "lib" directory. |
| 430 from = join(from, 'lib'); | 400 from = join(from, 'lib'); |
| 431 return dirExists(from).chain((exists) { | 401 return dirExists(from).chain((exists) { |
| 432 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); | 402 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); |
| 433 if (exists) return createSymlink(from, to); | 403 if (exists) return createSymlink(from, to); |
| 434 | 404 |
| 435 // It's OK for the self link (i.e. the root package) to not have a lib | 405 // It's OK for the self link (i.e. the root package) to not have a lib |
| 436 // directory since it may just be a leaf application that only has | 406 // directory since it may just be a leaf application that only has |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 517 }; | 487 }; |
| 518 | 488 |
| 519 stream.onError = (e) { | 489 stream.onError = (e) { |
| 520 removeCallbacks(); | 490 removeCallbacks(); |
| 521 completer.completeException(e, stackTrace); | 491 completer.completeException(e, stackTrace); |
| 522 }; | 492 }; |
| 523 | 493 |
| 524 return completer.future; | 494 return completer.future; |
| 525 } | 495 } |
| 526 | 496 |
| 527 /** | 497 /// Takes all input from [source] and writes it to [sink]. |
| 528 * Takes all input from [source] and writes it to [sink]. | 498 /// |
| 529 * | 499 /// Returns a future that completes when [source] is closed. |
| 530 * Returns a future that completes when [source] is closed. | |
| 531 */ | |
| 532 Future pipeInputToInput(InputStream source, ListInputStream sink) { | 500 Future pipeInputToInput(InputStream source, ListInputStream sink) { |
| 533 var completer = new Completer(); | 501 var completer = new Completer(); |
| 534 source.onClosed = () { | 502 source.onClosed = () { |
| 535 sink.markEndOfStream(); | 503 sink.markEndOfStream(); |
| 536 completer.complete(null); | 504 completer.complete(null); |
| 537 }; | 505 }; |
| 538 source.onData = () { | 506 source.onData = () { |
| 539 // Even if the sink is closed and we aren't going to do anything with more | 507 // Even if the sink is closed and we aren't going to do anything with more |
| 540 // data, we still need to drain it from source to work around issue 7218. | 508 // data, we still need to drain it from source to work around issue 7218. |
| 541 var data = source.read(); | 509 var data = source.read(); |
| 542 try { | 510 try { |
| 543 if (!sink.closed) sink.write(data); | 511 if (!sink.closed) sink.write(data); |
| 544 } on StreamException catch (e, stackTrace) { | 512 } on StreamException catch (e, stackTrace) { |
| 545 // Ignore an exception to work around issue 4222. | 513 // Ignore an exception to work around issue 4222. |
| 546 log.io("Writing to an unclosed ListInputStream caused exception $e\n" | 514 log.io("Writing to an unclosed ListInputStream caused exception $e\n" |
| 547 "$stackTrace"); | 515 "$stackTrace"); |
| 548 } | 516 } |
| 549 }; | 517 }; |
| 550 // TODO(nweiz): propagate this error to the sink. See issue 3657. | 518 // TODO(nweiz): propagate this error to the sink. See issue 3657. |
| 551 source.onError = (e) { throw e; }; | 519 source.onError = (e) { throw e; }; |
| 552 return completer.future; | 520 return completer.future; |
| 553 } | 521 } |
| 554 | 522 |
| 555 /** | 523 /// Buffers all input from an InputStream and returns it as a future. |
| 556 * Buffers all input from an InputStream and returns it as a future. | |
| 557 */ | |
| 558 Future<List<int>> consumeInputStream(InputStream stream) { | 524 Future<List<int>> consumeInputStream(InputStream stream) { |
| 559 if (stream.closed) return new Future.immediate(<int>[]); | 525 if (stream.closed) return new Future.immediate(<int>[]); |
| 560 | 526 |
| 561 // TODO(nweiz): remove this when issue 4061 is fixed. | 527 // TODO(nweiz): remove this when issue 4061 is fixed. |
| 562 var stackTrace; | 528 var stackTrace; |
| 563 try { | 529 try { |
| 564 throw ""; | 530 throw ""; |
| 565 } catch (_, localStackTrace) { | 531 } catch (_, localStackTrace) { |
| 566 stackTrace = localStackTrace; | 532 stackTrace = localStackTrace; |
| 567 } | 533 } |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 687 if (environment != null) { | 653 if (environment != null) { |
| 688 options.environment = new Map.from(Platform.environment); | 654 options.environment = new Map.from(Platform.environment); |
| 689 environment.forEach((key, value) => options.environment[key] = value); | 655 environment.forEach((key, value) => options.environment[key] = value); |
| 690 } | 656 } |
| 691 | 657 |
| 692 log.process(executable, args); | 658 log.process(executable, args); |
| 693 | 659 |
| 694 return fn(executable, args, options); | 660 return fn(executable, args, options); |
| 695 } | 661 } |
| 696 | 662 |
| 697 /** | 663 /// Wraps [input] to provide a timeout. If [input] completes before |
| 698 * Wraps [input] to provide a timeout. If [input] completes before | 664 /// [milliseconds] have passed, then the return value completes in the same way. |
| 699 * [milliseconds] have passed, then the return value completes in the same way. | 665 /// However, if [milliseconds] pass before [input] has completed, it completes |
| 700 * However, if [milliseconds] pass before [input] has completed, it completes | 666 /// with a [TimeoutException] with [description] (which should be a fragment |
| 701 * with a [TimeoutException] with [description] (which should be a fragment | 667 /// describing the action that timed out). |
| 702 * describing the action that timed out). | 668 /// |
| 703 * | 669 /// Note that timing out will not cancel the asynchronous operation behind |
| 704 * Note that timing out will not cancel the asynchronous operation behind | 670 /// [input]. |
| 705 * [input]. | |
| 706 */ | |
| 707 Future timeout(Future input, int milliseconds, String description) { | 671 Future timeout(Future input, int milliseconds, String description) { |
| 708 var completer = new Completer(); | 672 var completer = new Completer(); |
| 709 var timer = new Timer(milliseconds, (_) { | 673 var timer = new Timer(milliseconds, (_) { |
| 710 if (completer.future.isComplete) return; | 674 if (completer.future.isComplete) return; |
| 711 completer.completeException(new TimeoutException( | 675 completer.completeException(new TimeoutException( |
| 712 'Timed out while $description.')); | 676 'Timed out while $description.')); |
| 713 }); | 677 }); |
| 714 input.handleException((e) { | 678 input.handleException((e) { |
| 715 if (completer.future.isComplete) return false; | 679 if (completer.future.isComplete) return false; |
| 716 timer.cancel(); | 680 timer.cancel(); |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 796 | 760 |
| 797 future.handleException((err) { | 761 future.handleException((err) { |
| 798 // If the process failed, they probably don't have it. | 762 // If the process failed, they probably don't have it. |
| 799 completer.complete(false); | 763 completer.complete(false); |
| 800 return true; | 764 return true; |
| 801 }); | 765 }); |
| 802 | 766 |
| 803 return completer.future; | 767 return completer.future; |
| 804 } | 768 } |
| 805 | 769 |
| 806 /** | 770 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
| 807 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 771 /// directory or a path. Returns whether or not the extraction was successful. |
| 808 * directory or a path. Returns whether or not the extraction was successful. | |
| 809 */ | |
| 810 Future<bool> extractTarGz(InputStream stream, destination) { | 772 Future<bool> extractTarGz(InputStream stream, destination) { |
| 811 destination = _getPath(destination); | 773 destination = _getPath(destination); |
| 812 | 774 |
| 813 log.fine("Extracting .tar.gz stream to $destination."); | 775 log.fine("Extracting .tar.gz stream to $destination."); |
| 814 | 776 |
| 815 if (Platform.operatingSystem == "windows") { | 777 if (Platform.operatingSystem == "windows") { |
| 816 return _extractTarGzWindows(stream, destination); | 778 return _extractTarGzWindows(stream, destination); |
| 817 } | 779 } |
| 818 | 780 |
| 819 var completer = new Completer<int>(); | 781 var completer = new Completer<int>(); |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 965 // stderr. We don't want to show that since it's meaningless. | 927 // stderr. We don't want to show that since it's meaningless. |
| 966 // TODO(rnystrom): Should log this and display it if an actual error | 928 // TODO(rnystrom): Should log this and display it if an actual error |
| 967 // occurs. | 929 // occurs. |
| 968 consumeInputStream(process.stderr); | 930 consumeInputStream(process.stderr); |
| 969 return pipeInputToInput(process.stdout, stream); | 931 return pipeInputToInput(process.stdout, stream); |
| 970 }); | 932 }); |
| 971 }); | 933 }); |
| 972 return stream; | 934 return stream; |
| 973 } | 935 } |
| 974 | 936 |
| 975 /** | 937 /// Exception thrown when an operation times out. |
| 976 * Exception thrown when an operation times out. | |
| 977 */ | |
| 978 class TimeoutException implements Exception { | 938 class TimeoutException implements Exception { |
| 979 final String message; | 939 final String message; |
| 980 | 940 |
| 981 const TimeoutException(this.message); | 941 const TimeoutException(this.message); |
| 982 | 942 |
| 983 String toString() => message; | 943 String toString() => message; |
| 984 } | 944 } |
| 985 | 945 |
| 986 /** | 946 /// Contains the results of invoking a [Process] and waiting for it to complete. |
| 987 * Contains the results of invoking a [Process] and waiting for it to complete. | |
| 988 */ | |
| 989 class PubProcessResult { | 947 class PubProcessResult { |
| 990 final List<String> stdout; | 948 final List<String> stdout; |
| 991 final List<String> stderr; | 949 final List<String> stderr; |
| 992 final int exitCode; | 950 final int exitCode; |
| 993 | 951 |
| 994 const PubProcessResult(this.stdout, this.stderr, this.exitCode); | 952 const PubProcessResult(this.stdout, this.stderr, this.exitCode); |
| 995 | 953 |
| 996 bool get success => exitCode == 0; | 954 bool get success => exitCode == 0; |
| 997 } | 955 } |
| 998 | 956 |
| 999 /** | 957 /// Gets the path string for [entry], which can either already be a path string, |
| 1000 * Gets the path string for [entry], which can either already be a path string, | 958 /// or be a [File] or [Directory]. Allows working generically with "file-like" |
| 1001 * or be a [File] or [Directory]. Allows working generically with "file-like" | 959 /// objects. |
| 1002 * objects. | |
| 1003 */ | |
| 1004 String _getPath(entry) { | 960 String _getPath(entry) { |
| 1005 if (entry is String) return entry; | 961 if (entry is String) return entry; |
| 1006 if (entry is File) return entry.name; | 962 if (entry is File) return entry.name; |
| 1007 if (entry is Directory) return entry.path; | 963 if (entry is Directory) return entry.path; |
| 1008 throw 'Entry $entry is not a supported type.'; | 964 throw 'Entry $entry is not a supported type.'; |
| 1009 } | 965 } |
| 1010 | 966 |
| 1011 /** | 967 /// Gets a [Directory] for [entry], which can either already be one, or be a |
| 1012 * Gets a [Directory] for [entry], which can either already be one, or be a | 968 /// [String]. |
| 1013 * [String]. | |
| 1014 */ | |
| 1015 Directory _getDirectory(entry) { | 969 Directory _getDirectory(entry) { |
| 1016 if (entry is Directory) return entry; | 970 if (entry is Directory) return entry; |
| 1017 return new Directory(entry); | 971 return new Directory(entry); |
| 1018 } | 972 } |
| 1019 | 973 |
| 1020 /** | 974 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 1021 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | |
| 1022 */ | |
| 1023 Uri _getUri(uri) { | 975 Uri _getUri(uri) { |
| 1024 if (uri is Uri) return uri; | 976 if (uri is Uri) return uri; |
| 1025 return new Uri.fromString(uri); | 977 return new Uri.fromString(uri); |
| 1026 } | 978 } |
| OLD | NEW |