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'; |
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
256 return log.ioAsync("create temp directory ${dir.path}", | 256 return log.ioAsync("create temp directory ${dir.path}", |
257 dir.createTemp()); | 257 dir.createTemp()); |
258 } | 258 } |
259 | 259 |
260 /** | 260 /** |
261 * Asynchronously recursively deletes [dir], which can be a [String] or a | 261 * Asynchronously recursively deletes [dir], which can be a [String] or a |
262 * [Directory]. Returns a [Future] that completes when the deletion is done. | 262 * [Directory]. Returns a [Future] that completes when the deletion is done. |
263 */ | 263 */ |
264 Future<Directory> deleteDir(dir) { | 264 Future<Directory> deleteDir(dir) { |
265 dir = _getDirectory(dir); | 265 dir = _getDirectory(dir); |
266 return log.ioAsync("delete directory ${dir.path}", | 266 |
267 dir.delete(recursive: true)); | 267 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", |
| 268 dir.delete(recursive: true))); |
268 } | 269 } |
269 | 270 |
270 /** | 271 /** |
271 * Asynchronously lists the contents of [dir], which can be a [String] directory | 272 * Asynchronously lists the contents of [dir], which can be a [String] directory |
272 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents | 273 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
273 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and | 274 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and |
274 * directories beginning with `.` (defaults to `false`). | 275 * directories beginning with `.` (defaults to `false`). |
275 */ | 276 */ |
276 Future<List<String>> listDir(dir, | 277 Future<List<String>> listDir(dir, |
277 {bool recursive: false, bool includeHiddenFiles: false}) { | 278 {bool recursive: false, bool includeHiddenFiles: false}) { |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
342 } | 343 } |
343 }); | 344 }); |
344 } | 345 } |
345 | 346 |
346 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with | 347 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with |
347 /// the destination directory. | 348 /// the destination directory. |
348 Future<Directory> renameDir(from, String to) { | 349 Future<Directory> renameDir(from, String to) { |
349 from = _getDirectory(from); | 350 from = _getDirectory(from); |
350 log.io("Renaming directory ${from.path} to $to."); | 351 log.io("Renaming directory ${from.path} to $to."); |
351 | 352 |
352 if (Platform.operatingSystem != 'windows') { | 353 return _attemptRetryable(() => from.rename(to)).transform((dir) { |
353 return from.rename(to).transform((dir) { | 354 log.fine("Renamed directory ${from.path} to $to."); |
354 log.fine("Renamed directory ${from.path} to $to."); | 355 return dir; |
355 return dir; | 356 }); |
| 357 } |
| 358 |
| 359 /// On Windows, we sometimes get failures where the directory is still in use |
| 360 /// when we try to do something with it. This is usually because the OS hasn't |
| 361 /// noticed yet that a process using that directory has closed. To be a bit |
| 362 /// more resilient, we wait and retry a few times. |
| 363 /// |
| 364 /// Takes a [callback] which returns a future for the operation being attempted. |
| 365 /// If that future completes with an error, it will slepp and then [callback] |
| 366 /// will be invoked again to retry the operation. It will try a few times before |
| 367 /// giving up. |
| 368 Future _attemptRetryable(Future callback()) { |
| 369 // Only do lame retry logic on Windows. |
| 370 if (Platform.operatingSystem != 'windows') return callback(); |
| 371 |
| 372 var attempts = 0; |
| 373 makeAttempt(_) { |
| 374 attempts++; |
| 375 return callback().transformException((e) { |
| 376 if (attempts >= 10) { |
| 377 throw 'Could not complete operation. Gave up after $attempts attempts.'; |
| 378 } |
| 379 |
| 380 // Wait a bit and try again. |
| 381 log.fine("Operation failed, retrying (attempt $attempts)."); |
| 382 return sleep(500).chain(makeAttempt); |
356 }); | 383 }); |
357 } | 384 } |
358 | 385 |
359 // On Windows, we sometimes get failures where the directory is still in use | 386 return makeAttempt(null); |
360 // when we try to move it. To be a bit more resilient, we wait and retry a | |
361 // few times. | |
362 var attempts = 0; | |
363 attemptRename(_) { | |
364 attempts++; | |
365 return from.rename(to).transform((dir) { | |
366 log.fine("Renamed directory ${from.path} to $to."); | |
367 return dir; | |
368 }).transformException((e) { | |
369 if (attempts >= 10) { | |
370 throw 'Could not move directory "${from.path}" to "$to". Gave up ' | |
371 'after $attempts attempts.'; | |
372 } | |
373 | |
374 // Wait a bit and try again. | |
375 log.fine("Rename ${from.path} failed, retrying (attempt $attempts)."); | |
376 return sleep(500).chain(attemptRename); | |
377 }); | |
378 | |
379 return from; | |
380 } | |
381 | |
382 return attemptRename(null); | |
383 } | 387 } |
384 | 388 |
385 /** | 389 /** |
386 * Creates a new symlink that creates an alias from [from] to [to], both of | 390 * Creates a new symlink that creates an alias from [from] to [to], both of |
387 * which can be a [String], [File], or [Directory]. Returns a [Future] which | 391 * which can be a [String], [File], or [Directory]. Returns a [Future] which |
388 * completes to the symlink file (i.e. [to]). | 392 * completes to the symlink file (i.e. [to]). |
389 */ | 393 */ |
390 Future<File> createSymlink(from, to) { | 394 Future<File> createSymlink(from, to) { |
391 from = _getPath(from); | 395 from = _getPath(from); |
392 to = _getPath(to); | 396 to = _getPath(to); |
(...skipping 598 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
991 }); | 995 }); |
992 | 996 |
993 if (Platform.operatingSystem != "windows") { | 997 if (Platform.operatingSystem != "windows") { |
994 var args = ["--create", "--gzip", "--directory", baseDir]; | 998 var args = ["--create", "--gzip", "--directory", baseDir]; |
995 args.addAll(contents.map(_getPath)); | 999 args.addAll(contents.map(_getPath)); |
996 // TODO(nweiz): It's possible that enough command-line arguments will make | 1000 // TODO(nweiz): It's possible that enough command-line arguments will make |
997 // the process choke, so at some point we should save the arguments to a | 1001 // the process choke, so at some point we should save the arguments to a |
998 // file and pass them in via --files-from for tar and -i@filename for 7zip. | 1002 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
999 startProcess("tar", args).then((process) { | 1003 startProcess("tar", args).then((process) { |
1000 pipeInputToInput(process.stdout, stream); | 1004 pipeInputToInput(process.stdout, stream); |
1001 process.stderr.pipe(stderr, close: false); | 1005 |
| 1006 // Drain and discard 7zip's stderr. 7zip writes its normal output to |
| 1007 // stderr. We don't want to show that since it's meaningless. |
| 1008 // TODO(rnystrom): Should log this and display it if an actual error |
| 1009 // occurs. |
| 1010 consumeInputStream(process.stderr); |
1002 }); | 1011 }); |
1003 return stream; | 1012 return stream; |
1004 } | 1013 } |
1005 | 1014 |
1006 withTempDir((tempDir) { | 1015 withTempDir((tempDir) { |
1007 // Create the tar file. | 1016 // Create the tar file. |
1008 var tarFile = join(tempDir, "intermediate.tar"); | 1017 var tarFile = join(tempDir, "intermediate.tar"); |
1009 var args = ["a", "-w$baseDir", tarFile]; | 1018 var args = ["a", "-w$baseDir", tarFile]; |
1010 args.addAll(contents.map((entry) => '-i!"$entry"')); | 1019 args.addAll(contents.map((entry) => '-i!"$entry"')); |
1011 | 1020 |
1012 // Note: This line of code gets munged by create_sdk.py to be the correct | 1021 // Note: This line of code gets munged by create_sdk.py to be the correct |
1013 // relative path to 7zip in the SDK. | 1022 // relative path to 7zip in the SDK. |
1014 var pathTo7zip = '../../third_party/7zip/7za.exe'; | 1023 var pathTo7zip = '../../third_party/7zip/7za.exe'; |
1015 var command = relativeToPub(pathTo7zip); | 1024 var command = relativeToPub(pathTo7zip); |
1016 | 1025 |
1017 // We're passing 'baseDir' both as '-w' and setting it as the working | 1026 // We're passing 'baseDir' both as '-w' and setting it as the working |
1018 // directory explicitly here intentionally. The former ensures that the | 1027 // directory explicitly here intentionally. The former ensures that the |
1019 // files added to the archive have the correct relative path in the archive. | 1028 // files added to the archive have the correct relative path in the archive. |
1020 // The latter enables relative paths in the "-i" args to be resolved. | 1029 // The latter enables relative paths in the "-i" args to be resolved. |
1021 return runProcess(command, args, workingDir: baseDir).chain((_) { | 1030 return runProcess(command, args, workingDir: baseDir).chain((_) { |
1022 // GZIP it. 7zip doesn't support doing both as a single operation. Send | 1031 // GZIP it. 7zip doesn't support doing both as a single operation. Send |
1023 // the output to stdout. | 1032 // the output to stdout. |
1024 args = ["a", "unused", "-tgzip", "-so", tarFile]; | 1033 args = ["a", "unused", "-tgzip", "-so", tarFile]; |
1025 return startProcess(command, args); | 1034 return startProcess(command, args); |
1026 }).chain((process) { | 1035 }).chain((process) { |
1027 process.stderr.pipe(stderr, close: false); | 1036 // Drain and discard 7zip's stderr. 7zip writes its normal output to |
| 1037 // stderr. We don't want to show that since it's meaningless. |
| 1038 // TODO(rnystrom): Should log this and display it if an actual error |
| 1039 // occurs. |
| 1040 consumeInputStream(process.stderr); |
1028 return pipeInputToInput(process.stdout, stream); | 1041 return pipeInputToInput(process.stdout, stream); |
1029 }); | 1042 }); |
1030 }); | 1043 }); |
1031 return stream; | 1044 return stream; |
1032 } | 1045 } |
1033 | 1046 |
1034 /** | 1047 /** |
1035 * Exception thrown when an HTTP operation fails. | 1048 * Exception thrown when an HTTP operation fails. |
1036 */ | 1049 */ |
1037 class PubHttpException implements Exception { | 1050 class PubHttpException implements Exception { |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1122 return new Directory(entry); | 1135 return new Directory(entry); |
1123 } | 1136 } |
1124 | 1137 |
1125 /** | 1138 /** |
1126 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 1139 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
1127 */ | 1140 */ |
1128 Uri _getUri(uri) { | 1141 Uri _getUri(uri) { |
1129 if (uri is Uri) return uri; | 1142 if (uri is Uri) return uri; |
1130 return new Uri.fromString(uri); | 1143 return new Uri.fromString(uri); |
1131 } | 1144 } |
OLD | NEW |