| 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 | 
|---|