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:uri'); | 12 #import('dart:uri'); |
12 | 13 |
13 /** Gets the current working directory. */ | 14 /** Gets the current working directory. */ |
14 String get workingDir => new File('.').fullPathSync(); | 15 String get workingDir => new File('.').fullPathSync(); |
15 | 16 |
16 /** | 17 /** |
17 * Prints the given string to `stderr` on its own line. | 18 * Prints the given string to `stderr` on its own line. |
18 */ | 19 */ |
19 void printError(value) { | 20 void printError(value) { |
20 stderr.writeString(value.toString()); | 21 stderr.writeString(value.toString()); |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
278 }); | 279 }); |
279 } | 280 } |
280 | 281 |
281 /** | 282 /** |
282 * Given [entry] which may be a [String], [File], or [Directory] relative to | 283 * Given [entry] which may be a [String], [File], or [Directory] relative to |
283 * the current working directory, returns its full canonicalized path. | 284 * the current working directory, returns its full canonicalized path. |
284 */ | 285 */ |
285 // TODO(rnystrom): Should this be async? | 286 // TODO(rnystrom): Should this be async? |
286 String getFullPath(entry) => new File(_getPath(entry)).fullPathSync(); | 287 String getFullPath(entry) => new File(_getPath(entry)).fullPathSync(); |
287 | 288 |
| 289 // TODO(nweiz): make this configurable |
| 290 /** |
| 291 * The amount of time in milliseconds to allow HTTP requests before assuming |
| 292 * they've failed. |
| 293 */ |
| 294 final HTTP_TIMEOUT = 30 * 1000; |
| 295 |
288 /** | 296 /** |
289 * Opens an input stream for a HTTP GET request to [uri], which may be a | 297 * Opens an input stream for a HTTP GET request to [uri], which may be a |
290 * [String] or [Uri]. | 298 * [String] or [Uri]. |
| 299 * |
| 300 * Callers should be sure to use [timeout] to make sure that the HTTP request |
| 301 * doesn't last indefinitely |
291 */ | 302 */ |
292 InputStream httpGet(uri) { | 303 Future<InputStream> httpGet(uri) { |
293 var resultStream = new ListInputStream(); | 304 // TODO(nweiz): This could return an InputStream synchronously if issue 3657 |
294 var client = new HttpClient(); | 305 // were fixed and errors could be propagated through it. Then we could also |
295 var connection = client.getUrl(_getUri(uri)); | 306 // automatically attach a timeout to that stream. |
296 | |
297 // TODO(nweiz): propagate this error to the return value. See issue 3657. | |
298 connection.onError = (e) { throw e; }; | |
299 connection.onResponse = (response) { | |
300 if (response.statusCode >= 400) { | |
301 // TODO(nweiz): propagate this error to the return value. See issue 3657. | |
302 throw new Exception( | |
303 "HTTP request for $uri failed with status ${response.statusCode}"); | |
304 } | |
305 | |
306 pipeInputToInput(response.inputStream, resultStream, client.shutdown); | |
307 }; | |
308 | |
309 return resultStream; | |
310 } | |
311 | |
312 /** | |
313 * Opens an input stream for a HTTP GET request to [uri], which may be a | |
314 * [String] or [Uri]. Completes with the result of the request as a String. | |
315 */ | |
316 Future<String> httpGetString(uri) { | |
317 // TODO(rnystrom): This code is very similar to httpGet() and | |
318 // consumeInputStream() (but this one propagates errors). Merge them when | |
319 // #3657 is fixed. | |
320 uri = _getUri(uri); | 307 uri = _getUri(uri); |
321 | 308 |
322 var completer = new Completer<String>(); | 309 var completer = new Completer<InputStream>(); |
323 var client = new HttpClient(); | 310 var client = new HttpClient(); |
324 var connection = client.getUrl(uri); | 311 var connection = client.getUrl(uri); |
325 | 312 |
326 connection.onError = (e) { | 313 connection.onError = (e) { |
327 // Show a friendly error if the URL couldn't be resolved. | 314 // Show a friendly error if the URL couldn't be resolved. |
328 if (e is SocketIOException && | 315 if (e is SocketIOException && |
329 (e.osError.errorCode == 8 || | 316 (e.osError.errorCode == 8 || |
330 e.osError.errorCode == -2 || | 317 e.osError.errorCode == -2 || |
331 e.osError.errorCode == -5)) { | 318 e.osError.errorCode == -5)) { |
332 e = 'Could not resolve URL "${uri.origin}".'; | 319 e = 'Could not resolve URL "${uri.origin}".'; |
333 } | 320 } |
334 | 321 |
335 client.shutdown(); | 322 client.shutdown(); |
336 completer.completeException(e); | 323 completer.completeException(e); |
337 }; | 324 }; |
338 | 325 |
339 connection.onResponse = (response) { | 326 connection.onResponse = (response) { |
340 if (response.statusCode >= 400) { | 327 if (response.statusCode >= 400) { |
341 client.shutdown(); | 328 client.shutdown(); |
342 completer.completeException( | 329 completer.completeException( |
343 new HttpException(response.statusCode, response.reasonPhrase)); | 330 new HttpException(response.statusCode, response.reasonPhrase)); |
344 return; | 331 return; |
345 } | 332 } |
346 | 333 |
347 var resultStream = new ListInputStream(); | 334 completer.complete(response.inputStream); |
348 var buffer = <int>[]; | |
349 | |
350 response.inputStream.onClosed = () { | |
351 client.shutdown(); | |
352 completer.complete(new String.fromCharCodes(buffer)); | |
353 }; | |
354 | |
355 response.inputStream.onData = () { | |
356 buffer.addAll(response.inputStream.read()); | |
357 }; | |
358 | |
359 response.inputStream.onError = (e) { | |
360 client.shutdown(); | |
361 completer.completeException(e); | |
362 }; | |
363 }; | 335 }; |
364 | 336 |
365 return completer.future; | 337 return completer.future; |
366 } | 338 } |
367 | 339 |
368 /** | 340 /** |
| 341 * Opens an input stream for a HTTP GET request to [uri], which may be a |
| 342 * [String] or [Uri]. Completes with the result of the request as a String. |
| 343 */ |
| 344 Future<String> httpGetString(uri) { |
| 345 var future = httpGet(uri).chain((stream) => consumeInputStream(stream)) |
| 346 .transform((bytes) => new String.fromCharCodes(bytes)); |
| 347 return timeout(future, HTTP_TIMEOUT, 'Timed out while fetching URL "$uri".'); |
| 348 } |
| 349 |
| 350 /** |
369 * Takes all input from [source] and writes it to [sink]. | 351 * Takes all input from [source] and writes it to [sink]. |
370 * | 352 * |
371 * [onClosed] is called when [source] is closed. | 353 * [onClosed] is called when [source] is closed. |
372 */ | 354 */ |
373 void pipeInputToInput(InputStream source, ListInputStream sink, | 355 void pipeInputToInput(InputStream source, ListInputStream sink, |
374 [void onClosed()]) { | 356 [void onClosed()]) { |
375 source.onClosed = () { | 357 source.onClosed = () { |
376 sink.markEndOfStream(); | 358 sink.markEndOfStream(); |
377 if (onClosed != null) onClosed(); | 359 if (onClosed != null) onClosed(); |
378 }; | 360 }; |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
453 exitCode = actualExitCode; | 435 exitCode = actualExitCode; |
454 checkComplete(); | 436 checkComplete(); |
455 }; | 437 }; |
456 | 438 |
457 process.onError = (error) => completer.completeException(error); | 439 process.onError = (error) => completer.completeException(error); |
458 | 440 |
459 return completer.future; | 441 return completer.future; |
460 } | 442 } |
461 | 443 |
462 /** | 444 /** |
| 445 * Wraps [input] to provide a timeout. If [input] completes before |
| 446 * [milliSeconds] have passed, then the return value completes in the same way. |
| 447 * However, if [milliSeconds] pass before [input] has completed, it completes |
| 448 * with a [TimeoutException] with [message]. |
| 449 */ |
| 450 Future timeout(Future input, int milliSeconds, String message) { |
| 451 var completer = new Completer(); |
| 452 var timer = new Timer(milliSeconds, (_) { |
| 453 if (completer.future.isComplete) return; |
| 454 completer.completeException(new TimeoutException(message)); |
| 455 }); |
| 456 input.handleException((e) { |
| 457 if (completer.future.isComplete) return false; |
| 458 timer.cancel(); |
| 459 completer.completeException(e); |
| 460 return true; |
| 461 }); |
| 462 input.then((value) { |
| 463 if (completer.future.isComplete) return; |
| 464 timer.cancel(); |
| 465 completer.complete(value); |
| 466 }); |
| 467 return completer.future; |
| 468 } |
| 469 |
| 470 /** |
463 * Tests whether or not the git command-line app is available for use. | 471 * Tests whether or not the git command-line app is available for use. |
464 */ | 472 */ |
465 Future<bool> get isGitInstalled { | 473 Future<bool> get isGitInstalled { |
466 // TODO(rnystrom): We could cache this after the first check. We aren't right | 474 // TODO(rnystrom): We could cache this after the first check. We aren't right |
467 // now because Future.immediate() will invoke its callback synchronously. | 475 // now because Future.immediate() will invoke its callback synchronously. |
468 // That does bad things in cases where the caller expects futures to always | 476 // That does bad things in cases where the caller expects futures to always |
469 // be async. In particular, withGit() in the pub tests which calls | 477 // be async. In particular, withGit() in the pub tests which calls |
470 // expectAsync() will fail horribly if the test isn't actually async. | 478 // expectAsync() will fail horribly if the test isn't actually async. |
471 | 479 |
472 var completer = new Completer<bool>(); | 480 var completer = new Completer<bool>(); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
511 * Exception thrown when an HTTP operation fails. | 519 * Exception thrown when an HTTP operation fails. |
512 */ | 520 */ |
513 class HttpException implements Exception { | 521 class HttpException implements Exception { |
514 final int statusCode; | 522 final int statusCode; |
515 final String reason; | 523 final String reason; |
516 | 524 |
517 const HttpException(this.statusCode, this.reason); | 525 const HttpException(this.statusCode, this.reason); |
518 } | 526 } |
519 | 527 |
520 /** | 528 /** |
| 529 * Exception thrown when an operation times out. |
| 530 */ |
| 531 class TimeoutException implements Exception { |
| 532 final String message; |
| 533 |
| 534 const TimeoutException(this.message); |
| 535 } |
| 536 |
| 537 /** |
521 * Contains the results of invoking a [Process] and waiting for it to complete. | 538 * Contains the results of invoking a [Process] and waiting for it to complete. |
522 */ | 539 */ |
523 class PubProcessResult { | 540 class PubProcessResult { |
524 final List<String> stdout; | 541 final List<String> stdout; |
525 final List<String> stderr; | 542 final List<String> stderr; |
526 final int exitCode; | 543 final int exitCode; |
527 | 544 |
528 const PubProcessResult(this.stdout, this.stderr, this.exitCode); | 545 const PubProcessResult(this.stdout, this.stderr, this.exitCode); |
529 | 546 |
530 bool get success => exitCode == 0; | 547 bool get success => exitCode == 0; |
(...skipping 20 matching lines...) Expand all Loading... |
551 return new Directory(entry); | 568 return new Directory(entry); |
552 } | 569 } |
553 | 570 |
554 /** | 571 /** |
555 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 572 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
556 */ | 573 */ |
557 Uri _getUri(uri) { | 574 Uri _getUri(uri) { |
558 if (uri is Uri) return uri; | 575 if (uri is Uri) return uri; |
559 return new Uri.fromString(uri); | 576 return new Uri.fromString(uri); |
560 } | 577 } |
OLD | NEW |