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 /** |
6 * Helper functionality to make working with IO easier. | 6 * Helper functionality to make working with IO easier. |
7 */ | 7 */ |
8 library io; | 8 library io; |
9 | 9 |
10 import 'dart:io'; | 10 import 'dart:io'; |
11 import 'dart:isolate'; | 11 import 'dart:isolate'; |
12 import 'dart:uri'; | 12 import 'dart:uri'; |
13 | 13 |
14 // TODO(nweiz): Make this import better. | 14 // TODO(nweiz): Make this import better. |
15 import '../../pkg/http/lib/http.dart' as http; | 15 import '../../pkg/http/lib/http.dart' as http; |
| 16 import 'utils.dart'; |
16 import 'curl_client.dart'; | 17 import 'curl_client.dart'; |
17 import 'log.dart' as log; | |
18 import 'utils.dart'; | |
19 | 18 |
20 bool _isGitInstalledCache; | 19 bool _isGitInstalledCache; |
21 | 20 |
22 /// The cached Git command. | 21 /// The cached Git command. |
23 String _gitCommandCache; | 22 String _gitCommandCache; |
24 | 23 |
25 /** Gets the current working directory. */ | 24 /** Gets the current working directory. */ |
26 String get currentWorkingDir => new File('.').fullPathSync(); | 25 String get currentWorkingDir => new File('.').fullPathSync(); |
27 | 26 |
28 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 27 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
29 | 28 |
30 /** | 29 /** |
| 30 * Prints the given string to `stderr` on its own line. |
| 31 */ |
| 32 void printError(value) { |
| 33 stderr.writeString(value.toString()); |
| 34 stderr.writeString('\n'); |
| 35 } |
| 36 |
| 37 |
| 38 /** |
31 * Joins a number of path string parts into a single path. Handles | 39 * Joins a number of path string parts into a single path. Handles |
32 * platform-specific path separators. Parts can be [String], [Directory], or | 40 * platform-specific path separators. Parts can be [String], [Directory], or |
33 * [File] objects. | 41 * [File] objects. |
34 */ | 42 */ |
35 String join(part1, [part2, part3, part4]) { | 43 String join(part1, [part2, part3, part4]) { |
36 final parts = _sanitizePath(part1).split('/'); | 44 final parts = _sanitizePath(part1).split('/'); |
37 | 45 |
38 for (final part in [part2, part3, part4]) { | 46 for (final part in [part2, part3, part4]) { |
39 if (part == null) continue; | 47 if (part == null) continue; |
40 | 48 |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 return results[0] || results[1]; | 115 return results[0] || results[1]; |
108 }); | 116 }); |
109 } | 117 } |
110 | 118 |
111 /** | 119 /** |
112 * Asynchronously determines if [file], which can be a [String] file path or a | 120 * Asynchronously determines if [file], which can be a [String] file path or a |
113 * [File], exists on the file system. Returns a [Future] that completes with | 121 * [File], exists on the file system. Returns a [Future] that completes with |
114 * the result. | 122 * the result. |
115 */ | 123 */ |
116 Future<bool> fileExists(file) { | 124 Future<bool> fileExists(file) { |
117 var path = _getPath(file); | 125 return new File(_getPath(file)).exists(); |
118 return log.ioAsync("Seeing if file $path exists.", | |
119 new File(path).exists(), | |
120 (exists) => "File $path ${exists ? 'exists' : 'does not exist'}."); | |
121 } | 126 } |
122 | 127 |
123 /** | 128 /** |
124 * Reads the contents of the text file [file], which can either be a [String] or | 129 * Reads the contents of the text file [file], which can either be a [String] or |
125 * a [File]. | 130 * a [File]. |
126 */ | 131 */ |
127 Future<String> readTextFile(file) { | 132 Future<String> readTextFile(file) { |
128 var path = _getPath(file); | 133 return new File(_getPath(file)).readAsString(Encoding.UTF_8); |
129 return log.ioAsync("Reading text file $path.", | |
130 new File(path).readAsString(Encoding.UTF_8), | |
131 (contents) { | |
132 // Sanity check: don't spew a huge file. | |
133 if (contents.length < 1024 * 1024) { | |
134 return "Read $path. Contents:\n$contents"; | |
135 } else { | |
136 return "Read ${contents.length} characters from $path."; | |
137 } | |
138 }); | |
139 } | 134 } |
140 | 135 |
141 /** | 136 /** |
142 * Creates [file] (which can either be a [String] or a [File]), and writes | 137 * Creates [file] (which can either be a [String] or a [File]), and writes |
143 * [contents] to it. Completes when the file is written and closed. | 138 * [contents] to it. Completes when the file is written and closed. |
144 */ | 139 */ |
145 Future<File> writeTextFile(file, String contents) { | 140 Future<File> writeTextFile(file, String contents) { |
146 var path = _getPath(file); | 141 file = new File(_getPath(file)); |
147 file = new File(path); | |
148 | |
149 // Sanity check: don't spew a huge file. | |
150 log.io("Writing ${contents.length} characters to text file $path."); | |
151 if (contents.length < 1024 * 1024) { | |
152 log.fine("Contents:\n$contents"); | |
153 } | |
154 | |
155 return file.open(FileMode.WRITE).chain((opened) { | 142 return file.open(FileMode.WRITE).chain((opened) { |
156 return opened.writeString(contents).chain((ignore) { | 143 return opened.writeString(contents).chain((ignore) { |
157 return opened.close().transform((_) { | 144 return opened.close().transform((ignore) => file); |
158 log.fine("Wrote text file $path."); | |
159 return file; | |
160 }); | |
161 }); | 145 }); |
162 }); | 146 }); |
163 } | 147 } |
164 | 148 |
165 /** | 149 /** |
166 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a | 150 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a |
167 * [Future] that completes when the deletion is done. | 151 * [Future] that completes when the deletion is done. |
168 */ | 152 */ |
169 Future<File> deleteFile(file) { | 153 Future<File> deleteFile(file) { |
170 var path = _getPath(file); | 154 return new File(_getPath(file)).delete(); |
171 return log.ioAsync("delete file $path", | |
172 new File(path).delete()); | |
173 } | 155 } |
174 | 156 |
175 /// Writes [stream] to a new file at [path], which may be a [String] or a | 157 /// Writes [stream] to a new file at [path], which may be a [String] or a |
176 /// [File]. Will replace any file already at that path. Completes when the file | 158 /// [File]. Will replace any file already at that path. Completes when the file |
177 /// is done being written. | 159 /// is done being written. |
178 Future<File> createFileFromStream(InputStream stream, path) { | 160 Future<File> createFileFromStream(InputStream stream, path) { |
179 path = _getPath(path); | 161 path = _getPath(path); |
180 | 162 |
181 log.io("Creating $path from stream."); | |
182 | |
183 var completer = new Completer<File>(); | 163 var completer = new Completer<File>(); |
184 var file = new File(path); | 164 var file = new File(path); |
185 var outputStream = file.openOutputStream(); | 165 var outputStream = file.openOutputStream(); |
186 stream.pipe(outputStream); | 166 stream.pipe(outputStream); |
187 | 167 |
188 outputStream.onClosed = () { | 168 outputStream.onClosed = () { |
189 log.fine("Created $path from stream."); | |
190 completer.complete(file); | 169 completer.complete(file); |
191 }; | 170 }; |
192 | 171 |
193 // TODO(nweiz): remove this when issue 4061 is fixed. | 172 // TODO(nweiz): remove this when issue 4061 is fixed. |
194 var stackTrace; | 173 var stackTrace; |
195 try { | 174 try { |
196 throw ""; | 175 throw ""; |
197 } catch (_, localStackTrace) { | 176 } catch (_, localStackTrace) { |
198 stackTrace = localStackTrace; | 177 stackTrace = localStackTrace; |
199 } | 178 } |
200 | 179 |
201 completeError(error) { | 180 completeError(error) { |
202 if (!completer.isComplete) { | 181 if (!completer.isComplete) completer.completeException(error, stackTrace); |
203 completer.completeException(error, stackTrace); | |
204 } else { | |
205 log.fine("Got error after stream was closed: $error"); | |
206 } | |
207 } | 182 } |
208 | 183 |
209 stream.onError = completeError; | 184 stream.onError = completeError; |
210 outputStream.onError = completeError; | 185 outputStream.onError = completeError; |
211 | 186 |
212 return completer.future; | 187 return completer.future; |
213 } | 188 } |
214 | 189 |
215 /** | 190 /** |
216 * Creates a directory [dir]. Returns a [Future] that completes when the | 191 * Creates a directory [dir]. Returns a [Future] that completes when the |
217 * directory is created. | 192 * directory is created. |
218 */ | 193 */ |
219 Future<Directory> createDir(dir) { | 194 Future<Directory> createDir(dir) { |
220 dir = _getDirectory(dir); | 195 dir = _getDirectory(dir); |
221 return log.ioAsync("create directory ${dir.path}", | 196 return dir.create(); |
222 dir.create()); | |
223 } | 197 } |
224 | 198 |
225 /** | 199 /** |
226 * Ensures that [path] and all its parent directories exist. If they don't | 200 * Ensures that [path] and all its parent directories exist. If they don't |
227 * exist, creates them. Returns a [Future] that completes once all the | 201 * exist, creates them. Returns a [Future] that completes once all the |
228 * directories are created. | 202 * directories are created. |
229 */ | 203 */ |
230 Future<Directory> ensureDir(path) { | 204 Future<Directory> ensureDir(path) { |
231 path = _getPath(path); | 205 path = _getPath(path); |
232 log.fine("Ensuring directory $path exists."); | |
233 if (path == '.') return new Future.immediate(new Directory('.')); | 206 if (path == '.') return new Future.immediate(new Directory('.')); |
234 | 207 |
235 return dirExists(path).chain((exists) { | 208 return dirExists(path).chain((exists) { |
236 if (exists) { | 209 if (exists) return new Future.immediate(new Directory(path)); |
237 log.fine("Directory $path already exists."); | |
238 return new Future.immediate(new Directory(path)); | |
239 } | |
240 | |
241 return ensureDir(dirname(path)).chain((_) { | 210 return ensureDir(dirname(path)).chain((_) { |
242 var completer = new Completer<Directory>(); | 211 var completer = new Completer<Directory>(); |
243 var future = createDir(path); | 212 var future = createDir(path); |
244 future.handleException((error) { | 213 future.handleException((error) { |
245 if (error is! DirectoryIOException) return false; | 214 if (error is! DirectoryIOException) return false; |
246 // Error 17 means the directory already exists (or 183 on Windows). | 215 // Error 17 means the directory already exists (or 183 on Windows). |
247 if (error.osError.errorCode != 17 && | 216 if (error.osError.errorCode != 17 && |
248 error.osError.errorCode != 183) { | 217 error.osError.errorCode != 183) return false; |
249 log.fine("Got 'already exists' error when creating directory."); | |
250 return false; | |
251 } | |
252 | 218 |
253 completer.complete(_getDirectory(path)); | 219 completer.complete(_getDirectory(path)); |
254 return true; | 220 return true; |
255 }); | 221 }); |
256 future.then(completer.complete); | 222 future.then(completer.complete); |
257 return completer.future; | 223 return completer.future; |
258 }); | 224 }); |
259 }); | 225 }); |
260 } | 226 } |
261 | 227 |
262 /** | 228 /** |
263 * Creates a temp directory whose name will be based on [dir] with a unique | 229 * Creates a temp directory whose name will be based on [dir] with a unique |
264 * suffix appended to it. If [dir] is not provided, a temp directory will be | 230 * suffix appended to it. If [dir] is not provided, a temp directory will be |
265 * created in a platform-dependent temporary location. Returns a [Future] that | 231 * created in a platform-dependent temporary location. Returns a [Future] that |
266 * completes when the directory is created. | 232 * completes when the directory is created. |
267 */ | 233 */ |
268 Future<Directory> createTempDir([dir = '']) { | 234 Future<Directory> createTempDir([dir = '']) { |
269 dir = _getDirectory(dir); | 235 dir = _getDirectory(dir); |
270 return log.ioAsync("create temp directory ${dir.path}", | 236 return dir.createTemp(); |
271 dir.createTemp()); | |
272 } | 237 } |
273 | 238 |
274 /** | 239 /** |
275 * Asynchronously recursively deletes [dir], which can be a [String] or a | 240 * Asynchronously recursively deletes [dir], which can be a [String] or a |
276 * [Directory]. Returns a [Future] that completes when the deletion is done. | 241 * [Directory]. Returns a [Future] that completes when the deletion is done. |
277 */ | 242 */ |
278 Future<Directory> deleteDir(dir) { | 243 Future<Directory> deleteDir(dir) { |
279 dir = _getDirectory(dir); | 244 dir = _getDirectory(dir); |
280 return log.ioAsync("delete directory ${dir.path}", | 245 return dir.delete(recursive: true); |
281 dir.delete(recursive: true)); | |
282 } | 246 } |
283 | 247 |
284 /** | 248 /** |
285 * Asynchronously lists the contents of [dir], which can be a [String] directory | 249 * Asynchronously lists the contents of [dir], which can be a [String] directory |
286 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents | 250 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
287 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and | 251 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and |
288 * directories beginning with `.` (defaults to `false`). | 252 * directories beginning with `.` (defaults to `false`). |
289 */ | 253 */ |
290 Future<List<String>> listDir(dir, | 254 Future<List<String>> listDir(dir, |
291 {bool recursive: false, bool includeHiddenFiles: false}) { | 255 {bool recursive: false, bool includeHiddenFiles: false}) { |
292 final completer = new Completer<List<String>>(); | 256 final completer = new Completer<List<String>>(); |
293 final contents = <String>[]; | 257 final contents = <String>[]; |
294 | 258 |
295 dir = _getDirectory(dir); | 259 dir = _getDirectory(dir); |
296 log.io("Listing directory ${dir.path}."); | |
297 var lister = dir.list(recursive: recursive); | 260 var lister = dir.list(recursive: recursive); |
298 | 261 |
299 lister.onDone = (done) { | 262 lister.onDone = (done) { |
300 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile | 263 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
301 // aren't guaranteed to be called in a certain order. So far, they seem to. | 264 // aren't guaranteed to be called in a certain order. So far, they seem to. |
302 if (done) { | 265 if (done) completer.complete(contents); |
303 log.fine("Listed directory ${dir.path}:\n" | |
304 "${Strings.join(contents, '\n')}"); | |
305 completer.complete(contents); | |
306 } | |
307 }; | 266 }; |
308 | 267 |
309 // TODO(nweiz): remove this when issue 4061 is fixed. | 268 // TODO(nweiz): remove this when issue 4061 is fixed. |
310 var stackTrace; | 269 var stackTrace; |
311 try { | 270 try { |
312 throw ""; | 271 throw ""; |
313 } catch (_, localStackTrace) { | 272 } catch (_, localStackTrace) { |
314 stackTrace = localStackTrace; | 273 stackTrace = localStackTrace; |
315 } | 274 } |
316 | 275 |
(...skipping 10 matching lines...) Expand all Loading... |
327 return completer.future; | 286 return completer.future; |
328 } | 287 } |
329 | 288 |
330 /** | 289 /** |
331 * Asynchronously determines if [dir], which can be a [String] directory path | 290 * Asynchronously determines if [dir], which can be a [String] directory path |
332 * or a [Directory], exists on the file system. Returns a [Future] that | 291 * or a [Directory], exists on the file system. Returns a [Future] that |
333 * completes with the result. | 292 * completes with the result. |
334 */ | 293 */ |
335 Future<bool> dirExists(dir) { | 294 Future<bool> dirExists(dir) { |
336 dir = _getDirectory(dir); | 295 dir = _getDirectory(dir); |
337 return log.ioAsync("Seeing if directory ${dir.path} exists.", | 296 return dir.exists(); |
338 dir.exists(), | |
339 (exists) => "Directory ${dir.path} " | |
340 "${exists ? 'exists' : 'does not exist'}."); | |
341 } | 297 } |
342 | 298 |
343 /** | 299 /** |
344 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 300 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
345 * new empty directory will be created. Returns a [Future] that completes when | 301 * new empty directory will be created. Returns a [Future] that completes when |
346 * the new clean directory is created. | 302 * the new clean directory is created. |
347 */ | 303 */ |
348 Future<Directory> cleanDir(dir) { | 304 Future<Directory> cleanDir(dir) { |
349 return dirExists(dir).chain((exists) { | 305 return dirExists(dir).chain((exists) { |
350 if (exists) { | 306 if (exists) { |
351 // Delete it first. | 307 // Delete it first. |
352 return deleteDir(dir).chain((_) => createDir(dir)); | 308 return deleteDir(dir).chain((_) => createDir(dir)); |
353 } else { | 309 } else { |
354 // Just create it. | 310 // Just create it. |
355 return createDir(dir); | 311 return createDir(dir); |
356 } | 312 } |
357 }); | 313 }); |
358 } | 314 } |
359 | 315 |
360 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with | 316 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with |
361 /// the destination directory. | 317 /// the destination directory. |
362 Future<Directory> renameDir(from, String to) { | 318 Future<Directory> renameDir(from, String to) { |
363 from = _getDirectory(from); | 319 from = _getDirectory(from); |
364 log.io("Renaming directory ${from.path} to $to."); | |
365 | 320 |
366 if (Platform.operatingSystem != 'windows') { | 321 if (Platform.operatingSystem != 'windows') return from.rename(to); |
367 return from.rename(to).transform((dir) { | |
368 log.fine("Renamed directory ${from.path} to $to."); | |
369 return dir; | |
370 }); | |
371 } | |
372 | 322 |
373 // On Windows, we sometimes get failures where the directory is still in use | 323 // On Windows, we sometimes get failures where the directory is still in use |
374 // when we try to move it. To be a bit more resilient, we wait and retry a | 324 // when we try to move it. To be a bit more resilient, we wait and retry a |
375 // few times. | 325 // few times. |
376 var attempts = 0; | 326 var attempts = 0; |
377 attemptRename(_) { | 327 attemptRename(_) { |
378 attempts++; | 328 attempts++; |
379 return from.rename(to).transform((dir) { | 329 return from.rename(to).transformException((e) { |
380 log.fine("Renamed directory ${from.path} to $to."); | |
381 return dir; | |
382 }).transformException((e) { | |
383 if (attempts >= 10) { | 330 if (attempts >= 10) { |
384 throw 'Could not move directory "${from.path}" to "$to". Gave up ' | 331 throw 'Could not move directory "${from.path}" to "$to". Gave up ' |
385 'after $attempts attempts.'; | 332 'after $attempts attempts.'; |
386 } | 333 } |
387 | 334 |
388 // Wait a bit and try again. | 335 // Wait a bit and try again. |
389 log.fine("Rename ${from.path} failed, retrying (attempt $attempts)."); | |
390 return sleep(500).chain(attemptRename); | 336 return sleep(500).chain(attemptRename); |
391 }); | 337 }); |
392 | 338 |
393 return from; | 339 return from; |
394 } | 340 } |
395 | 341 |
396 return attemptRename(null); | 342 return attemptRename(null); |
397 } | 343 } |
398 | 344 |
399 /** | 345 /** |
400 * Creates a new symlink that creates an alias from [from] to [to], both of | 346 * Creates a new symlink that creates an alias from [from] to [to], both of |
401 * which can be a [String], [File], or [Directory]. Returns a [Future] which | 347 * which can be a [String], [File], or [Directory]. Returns a [Future] which |
402 * completes to the symlink file (i.e. [to]). | 348 * completes to the symlink file (i.e. [to]). |
403 */ | 349 */ |
404 Future<File> createSymlink(from, to) { | 350 Future<File> createSymlink(from, to) { |
405 from = _getPath(from); | 351 from = _getPath(from); |
406 to = _getPath(to); | 352 to = _getPath(to); |
407 | 353 |
408 log.fine("Create symlink $from -> $to."); | |
409 | |
410 var command = 'ln'; | 354 var command = 'ln'; |
411 var args = ['-s', from, to]; | 355 var args = ['-s', from, to]; |
412 | 356 |
413 if (Platform.operatingSystem == 'windows') { | 357 if (Platform.operatingSystem == 'windows') { |
414 // Call mklink on Windows to create an NTFS junction point. Only works on | 358 // Call mklink on Windows to create an NTFS junction point. Only works on |
415 // Vista or later. (Junction points are available earlier, but the "mklink" | 359 // Vista or later. (Junction points are available earlier, but the "mklink" |
416 // command is not.) I'm using a junction point (/j) here instead of a soft | 360 // command is not.) I'm using a junction point (/j) here instead of a soft |
417 // link (/d) because the latter requires some privilege shenanigans that | 361 // link (/d) because the latter requires some privilege shenanigans that |
418 // I'm not sure how to specify from the command line. | 362 // I'm not sure how to specify from the command line. |
419 command = 'mklink'; | 363 command = 'mklink'; |
(...skipping 11 matching lines...) Expand all Loading... |
431 * package [from] to [to], both of which can be a [String], [File], or | 375 * package [from] to [to], both of which can be a [String], [File], or |
432 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. | 376 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. |
433 * [to]). If [from] does not have a `lib` directory, this shows a warning if | 377 * [to]). If [from] does not have a `lib` directory, this shows a warning if |
434 * appropriate and then does nothing. | 378 * appropriate and then does nothing. |
435 */ | 379 */ |
436 Future<File> createPackageSymlink(String name, from, to, | 380 Future<File> createPackageSymlink(String name, from, to, |
437 {bool isSelfLink: false}) { | 381 {bool isSelfLink: false}) { |
438 // See if the package has a "lib" directory. | 382 // See if the package has a "lib" directory. |
439 from = join(from, 'lib'); | 383 from = join(from, 'lib'); |
440 return dirExists(from).chain((exists) { | 384 return dirExists(from).chain((exists) { |
441 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); | |
442 if (exists) return createSymlink(from, to); | 385 if (exists) return createSymlink(from, to); |
443 | 386 |
444 // It's OK for the self link (i.e. the root package) to not have a lib | 387 // It's OK for the self link (i.e. the root package) to not have a lib |
445 // directory since it may just be a leaf application that only has | 388 // directory since it may just be a leaf application that only has |
446 // code in bin or web. | 389 // code in bin or web. |
447 if (!isSelfLink) { | 390 if (!isSelfLink) { |
448 log.warning('Warning: Package "$name" does not have a "lib" directory so ' | 391 printError( |
449 'you will not be able to import any libraries from it.'); | 392 'Warning: Package "$name" does not have a "lib" directory so you ' |
| 393 'will not be able to import any libraries from it.'); |
450 } | 394 } |
451 | 395 |
452 return new Future.immediate(to); | 396 return new Future.immediate(to); |
453 }); | 397 }); |
454 } | 398 } |
455 | 399 |
456 /// Given [entry] which may be a [String], [File], or [Directory] relative to | 400 /// Given [entry] which may be a [String], [File], or [Directory] relative to |
457 /// the current working directory, returns its full canonicalized path. | 401 /// the current working directory, returns its full canonicalized path. |
458 String getFullPath(entry) { | 402 String getFullPath(entry) { |
459 var path = _getPath(entry); | 403 var path = _getPath(entry); |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
555 | 499 |
556 /// An HTTP client that transforms 40* errors and socket exceptions into more | 500 /// An HTTP client that transforms 40* errors and socket exceptions into more |
557 /// user-friendly error messages. | 501 /// user-friendly error messages. |
558 class PubHttpClient extends http.BaseClient { | 502 class PubHttpClient extends http.BaseClient { |
559 final http.Client _inner; | 503 final http.Client _inner; |
560 | 504 |
561 PubHttpClient([http.Client inner]) | 505 PubHttpClient([http.Client inner]) |
562 : _inner = inner == null ? new http.Client() : inner; | 506 : _inner = inner == null ? new http.Client() : inner; |
563 | 507 |
564 Future<http.StreamedResponse> send(http.BaseRequest request) { | 508 Future<http.StreamedResponse> send(http.BaseRequest request) { |
565 log.io("Sending HTTP request $request."); | |
566 | |
567 // TODO(nweiz): remove this when issue 4061 is fixed. | 509 // TODO(nweiz): remove this when issue 4061 is fixed. |
568 var stackTrace; | 510 var stackTrace; |
569 try { | 511 try { |
570 throw null; | 512 throw null; |
571 } catch (_, localStackTrace) { | 513 } catch (_, localStackTrace) { |
572 stackTrace = localStackTrace; | 514 stackTrace = localStackTrace; |
573 } | 515 } |
574 | 516 |
575 // TODO(nweiz): Ideally the timeout would extend to reading from the | 517 // TODO(nweiz): Ideally the timeout would extend to reading from the |
576 // response input stream, but until issue 3657 is fixed that's not feasible. | 518 // response input stream, but until issue 3657 is fixed that's not feasible. |
577 return timeout(_inner.send(request).chain((streamedResponse) { | 519 return timeout(_inner.send(request).chain((streamedResponse) { |
578 log.fine("Got response ${streamedResponse.statusCode} " | |
579 "${streamedResponse.reasonPhrase}."); | |
580 | |
581 var status = streamedResponse.statusCode; | 520 var status = streamedResponse.statusCode; |
582 // 401 responses should be handled by the OAuth2 client. It's very | 521 // 401 responses should be handled by the OAuth2 client. It's very |
583 // unlikely that they'll be returned by non-OAuth2 requests. | 522 // unlikely that they'll be returned by non-OAuth2 requests. |
584 if (status < 400 || status == 401) { | 523 if (status < 400 || status == 401) { |
585 return new Future.immediate(streamedResponse); | 524 return new Future.immediate(streamedResponse); |
586 } | 525 } |
587 | 526 |
588 return http.Response.fromStream(streamedResponse).transform((response) { | 527 return http.Response.fromStream(streamedResponse).transform((response) { |
589 throw new PubHttpException(response); | 528 throw new PubHttpException(response); |
590 }); | 529 }); |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
676 Future<PubProcessResult> runProcess(String executable, List<String> args, | 615 Future<PubProcessResult> runProcess(String executable, List<String> args, |
677 {workingDir, Map<String, String> environment}) { | 616 {workingDir, Map<String, String> environment}) { |
678 return _doProcess(Process.run, executable, args, workingDir, environment) | 617 return _doProcess(Process.run, executable, args, workingDir, environment) |
679 .transform((result) { | 618 .transform((result) { |
680 // TODO(rnystrom): Remove this and change to returning one string. | 619 // TODO(rnystrom): Remove this and change to returning one string. |
681 List<String> toLines(String output) { | 620 List<String> toLines(String output) { |
682 var lines = output.split(NEWLINE_PATTERN); | 621 var lines = output.split(NEWLINE_PATTERN); |
683 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | 622 if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
684 return lines; | 623 return lines; |
685 } | 624 } |
686 | 625 return new PubProcessResult(toLines(result.stdout), |
687 var pubResult = new PubProcessResult(toLines(result.stdout), | |
688 toLines(result.stderr), | 626 toLines(result.stderr), |
689 result.exitCode); | 627 result.exitCode); |
690 | |
691 log.processResult(executable, pubResult); | |
692 return pubResult; | |
693 }); | 628 }); |
694 } | 629 } |
695 | 630 |
696 /// Spawns the process located at [executable], passing in [args]. Returns a | 631 /// Spawns the process located at [executable], passing in [args]. Returns a |
697 /// [Future] that will complete with the [Process] once it's been started. | 632 /// [Future] that will complete with the [Process] once it's been started. |
698 /// | 633 /// |
699 /// The spawned process will inherit its parent's environment variables. If | 634 /// The spawned process will inherit its parent's environment variables. If |
700 /// [environment] is provided, that will be used to augment (not replace) the | 635 /// [environment] is provided, that will be used to augment (not replace) the |
701 /// the inherited variables. | 636 /// the inherited variables. |
702 Future<Process> startProcess(String executable, List<String> args, | 637 Future<Process> startProcess(String executable, List<String> args, |
(...skipping 18 matching lines...) Expand all Loading... |
721 final options = new ProcessOptions(); | 656 final options = new ProcessOptions(); |
722 if (workingDir != null) { | 657 if (workingDir != null) { |
723 options.workingDirectory = _getDirectory(workingDir).path; | 658 options.workingDirectory = _getDirectory(workingDir).path; |
724 } | 659 } |
725 | 660 |
726 if (environment != null) { | 661 if (environment != null) { |
727 options.environment = new Map.from(Platform.environment); | 662 options.environment = new Map.from(Platform.environment); |
728 environment.forEach((key, value) => options.environment[key] = value); | 663 environment.forEach((key, value) => options.environment[key] = value); |
729 } | 664 } |
730 | 665 |
731 log.process(executable, args); | |
732 | |
733 return fn(executable, args, options); | 666 return fn(executable, args, options); |
734 } | 667 } |
735 | 668 |
736 /// Closes [response] while ignoring the body of [request]. Returns a Future | 669 /// Closes [response] while ignoring the body of [request]. Returns a Future |
737 /// that completes once the response is closed. | 670 /// that completes once the response is closed. |
738 /// | 671 /// |
739 /// Due to issue 6984, it's necessary to drain the request body before closing | 672 /// Due to issue 6984, it's necessary to drain the request body before closing |
740 /// the response. | 673 /// the response. |
741 Future closeHttpResponse(HttpRequest request, HttpResponse response) { | 674 Future closeHttpResponse(HttpRequest request, HttpResponse response) { |
742 // TODO(nweiz): remove this when issue 4061 is fixed. | 675 // TODO(nweiz): remove this when issue 4061 is fixed. |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
791 | 724 |
792 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] | 725 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
793 /// returned by [fn] completes, the temporary directory and all its contents | 726 /// returned by [fn] completes, the temporary directory and all its contents |
794 /// will be deleted. | 727 /// will be deleted. |
795 Future withTempDir(Future fn(String path)) { | 728 Future withTempDir(Future fn(String path)) { |
796 var tempDir; | 729 var tempDir; |
797 var future = createTempDir().chain((dir) { | 730 var future = createTempDir().chain((dir) { |
798 tempDir = dir; | 731 tempDir = dir; |
799 return fn(tempDir.path); | 732 return fn(tempDir.path); |
800 }); | 733 }); |
801 future.onComplete((_) { | 734 future.onComplete((_) => tempDir.delete(recursive: true)); |
802 log.fine('Cleaning up temp directory ${tempDir.path}.'); | |
803 deleteDir(tempDir); | |
804 }); | |
805 return future; | 735 return future; |
806 } | 736 } |
807 | 737 |
808 /// Tests whether or not the git command-line app is available for use. | 738 /// Tests whether or not the git command-line app is available for use. |
809 Future<bool> get isGitInstalled { | 739 Future<bool> get isGitInstalled { |
810 if (_isGitInstalledCache != null) { | 740 if (_isGitInstalledCache != null) { |
811 // TODO(rnystrom): The sleep is to pump the message queue. Can use | 741 // TODO(rnystrom): The sleep is to pump the message queue. Can use |
812 // Future.immediate() when #3356 is fixed. | 742 // Future.immediate() when #3356 is fixed. |
813 return sleep(0).transform((_) => _isGitInstalledCache); | 743 return sleep(0).transform((_) => _isGitInstalledCache); |
814 } | 744 } |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
867 return completer.future; | 797 return completer.future; |
868 } | 798 } |
869 | 799 |
870 /** | 800 /** |
871 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 801 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
872 * directory or a path. Returns whether or not the extraction was successful. | 802 * directory or a path. Returns whether or not the extraction was successful. |
873 */ | 803 */ |
874 Future<bool> extractTarGz(InputStream stream, destination) { | 804 Future<bool> extractTarGz(InputStream stream, destination) { |
875 destination = _getPath(destination); | 805 destination = _getPath(destination); |
876 | 806 |
877 log.fine("Extracting .tar.gz stream to $destination."); | |
878 | |
879 if (Platform.operatingSystem == "windows") { | 807 if (Platform.operatingSystem == "windows") { |
880 return _extractTarGzWindows(stream, destination); | 808 return _extractTarGzWindows(stream, destination); |
881 } | 809 } |
882 | 810 |
883 var completer = new Completer<int>(); | 811 var completer = new Completer<int>(); |
884 var processFuture = Process.start("tar", | 812 var processFuture = Process.start("tar", |
885 ["--extract", "--gunzip", "--directory", destination]); | 813 ["--extract", "--gunzip", "--directory", destination]); |
886 processFuture.then((process) { | 814 processFuture.then((process) { |
887 process.onExit = completer.complete; | 815 process.onExit = completer.complete; |
888 stream.pipe(process.stdin); | 816 stream.pipe(process.stdin); |
889 process.stdout.pipe(stdout, close: false); | 817 process.stdout.pipe(stdout, close: false); |
890 process.stderr.pipe(stderr, close: false); | 818 process.stderr.pipe(stderr, close: false); |
891 }); | 819 }); |
892 processFuture.handleException((error) { | 820 processFuture.handleException((error) { |
893 completer.completeException(error, processFuture.stackTrace); | 821 completer.completeException(error, processFuture.stackTrace); |
894 return true; | 822 return true; |
895 }); | 823 }); |
896 | 824 |
897 return completer.future.transform((exitCode) { | 825 return completer.future.transform((exitCode) => exitCode == 0); |
898 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); | |
899 // TODO(rnystrom): Does anything check this result value? If not, it should | |
900 // throw on a bad exit code. | |
901 return exitCode == 0; | |
902 }); | |
903 } | 826 } |
904 | 827 |
905 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { | 828 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { |
906 // TODO(rnystrom): In the repo's history, there is an older implementation of | 829 // TODO(rnystrom): In the repo's history, there is an older implementation of |
907 // this that does everything in memory by piping streams directly together | 830 // this that does everything in memory by piping streams directly together |
908 // instead of writing out temp files. The code is simpler, but unfortunately, | 831 // instead of writing out temp files. The code is simpler, but unfortunately, |
909 // 7zip seems to periodically fail when we invoke it from Dart and tell it to | 832 // 7zip seems to periodically fail when we invoke it from Dart and tell it to |
910 // read from stdin instead of a file. Consider resurrecting that version if | 833 // read from stdin instead of a file. Consider resurrecting that version if |
911 // we can figure out why it fails. | 834 // we can figure out why it fails. |
912 | 835 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
949 | 872 |
950 // Untar the archive into the destination directory. | 873 // Untar the archive into the destination directory. |
951 return runProcess(command, ['x', tarFile], workingDir: destination); | 874 return runProcess(command, ['x', tarFile], workingDir: destination); |
952 }).chain((result) { | 875 }).chain((result) { |
953 if (result.exitCode != 0) { | 876 if (result.exitCode != 0) { |
954 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' | 877 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' |
955 '${Strings.join(result.stdout, "\n")}\n' | 878 '${Strings.join(result.stdout, "\n")}\n' |
956 '${Strings.join(result.stderr, "\n")}'; | 879 '${Strings.join(result.stderr, "\n")}'; |
957 } | 880 } |
958 | 881 |
959 log.fine('Clean up 7zip temp directory ${tempDir.path}.'); | 882 // Clean up the temp directory. |
960 // TODO(rnystrom): Should also delete this if anything fails. | 883 // TODO(rnystrom): Should also delete this if anything fails. |
961 return deleteDir(tempDir); | 884 return deleteDir(tempDir); |
962 }).transform((_) => true); | 885 }).transform((_) => true); |
963 } | 886 } |
964 | 887 |
965 /// Create a .tar.gz archive from a list of entries. Each entry can be a | 888 /// Create a .tar.gz archive from a list of entries. Each entry can be a |
966 /// [String], [Directory], or [File] object. The root of the archive is | 889 /// [String], [Directory], or [File] object. The root of the archive is |
967 /// considered to be [baseDir], which defaults to the current working directory. | 890 /// considered to be [baseDir], which defaults to the current working directory. |
968 /// Returns an [InputStream] that will emit the contents of the archive. | 891 /// Returns an [InputStream] that will emit the contents of the archive. |
969 InputStream createTarGz(List contents, {baseDir}) { | 892 InputStream createTarGz(List contents, {baseDir}) { |
970 log.fine('Creating .tag.gz stream containing:'); | |
971 contents.forEach(log.fine); | |
972 | |
973 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 893 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
974 // exit codes). See issue 3657. | 894 // exit codes). See issue 3657. |
975 var stream = new ListInputStream(); | 895 var stream = new ListInputStream(); |
976 | 896 |
977 if (baseDir == null) baseDir = currentWorkingDir; | 897 if (baseDir == null) baseDir = currentWorkingDir; |
978 baseDir = getFullPath(baseDir); | 898 baseDir = getFullPath(baseDir); |
979 contents = contents.map((entry) { | 899 contents = contents.map((entry) { |
980 entry = getFullPath(entry); | 900 entry = getFullPath(entry); |
981 if (!isBeneath(entry, baseDir)) { | 901 if (!isBeneath(entry, baseDir)) { |
982 throw 'Entry $entry is not inside $baseDir.'; | 902 throw 'Entry $entry is not inside $baseDir.'; |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1028 | 948 |
1029 /** | 949 /** |
1030 * Exception thrown when an HTTP operation fails. | 950 * Exception thrown when an HTTP operation fails. |
1031 */ | 951 */ |
1032 class PubHttpException implements Exception { | 952 class PubHttpException implements Exception { |
1033 final http.Response response; | 953 final http.Response response; |
1034 | 954 |
1035 const PubHttpException(this.response); | 955 const PubHttpException(this.response); |
1036 | 956 |
1037 String toString() => 'HTTP error ${response.statusCode}: ' | 957 String toString() => 'HTTP error ${response.statusCode}: ' |
1038 '${response.reasonPhrase}'; | 958 '${response.reasonPhrase}'; |
1039 } | 959 } |
1040 | 960 |
1041 /** | 961 /** |
1042 * Exception thrown when an operation times out. | 962 * Exception thrown when an operation times out. |
1043 */ | 963 */ |
1044 class TimeoutException implements Exception { | 964 class TimeoutException implements Exception { |
1045 final String message; | 965 final String message; |
1046 | 966 |
1047 const TimeoutException(this.message); | 967 const TimeoutException(this.message); |
1048 | 968 |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1099 return new Directory(entry); | 1019 return new Directory(entry); |
1100 } | 1020 } |
1101 | 1021 |
1102 /** | 1022 /** |
1103 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 1023 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
1104 */ | 1024 */ |
1105 Uri _getUri(uri) { | 1025 Uri _getUri(uri) { |
1106 if (uri is Uri) return uri; | 1026 if (uri is Uri) return uri; |
1107 return new Uri.fromString(uri); | 1027 return new Uri.fromString(uri); |
1108 } | 1028 } |
OLD | NEW |