OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 /// Helper functionality to make working with IO easier. | 5 /// Helper functionality to make working with IO easier. |
6 library io; | 6 library io; |
7 | 7 |
8 import 'dart:async'; | 8 import 'dart:async'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 import 'dart:isolate'; | 10 import 'dart:isolate'; |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
121 /// Creates a temp directory whose name will be based on [dir] with a unique | 121 /// Creates a temp directory whose name will be based on [dir] with a unique |
122 /// suffix appended to it. If [dir] is not provided, a temp directory will be | 122 /// suffix appended to it. If [dir] is not provided, a temp directory will be |
123 /// created in a platform-dependent temporary location. Returns the path of the | 123 /// created in a platform-dependent temporary location. Returns the path of the |
124 /// created directory. | 124 /// created directory. |
125 String createTempDir([dir = '']) { | 125 String createTempDir([dir = '']) { |
126 var tempDir = new Directory(dir).createTempSync(); | 126 var tempDir = new Directory(dir).createTempSync(); |
127 log.io("Created temp directory ${tempDir.path}"); | 127 log.io("Created temp directory ${tempDir.path}"); |
128 return tempDir.path; | 128 return tempDir.path; |
129 } | 129 } |
130 | 130 |
131 // TODO(nweiz): Remove this when issue 9252 is fixed. | 131 /// Recursively deletes [dir]. |
132 /// Asynchronously recursively deletes [dir]. Returns a [Future] that completes | 132 void deleteDir(String dir) { |
133 /// when the deletion is done. | 133 log.io("Deleting directory $dir."); |
134 Future<String> deleteDir(String dir) { | 134 new Directory(dir).deleteSync(recursive: true); |
135 return _attemptRetryable(() => log.ioAsync("delete directory $dir", | |
136 new Directory(dir).delete(recursive: true).then((_) => dir))); | |
137 } | 135 } |
138 | 136 |
139 /// Asynchronously lists the contents of [dir]. If [recursive] is `true`, lists | 137 /// Asynchronously lists the contents of [dir]. If [recursive] is `true`, lists |
140 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is | 138 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is |
141 /// `true`, includes files and directories beginning with `.` (defaults to | 139 /// `true`, includes files and directories beginning with `.` (defaults to |
142 /// `false`). | 140 /// `false`). |
143 /// | 141 /// |
144 /// If [dir] is a string, the returned paths are guaranteed to begin with it. | 142 /// If [dir] is a string, the returned paths are guaranteed to begin with it. |
145 Future<List<String>> listDir(String dir, | 143 Future<List<String>> listDir(String dir, |
146 {bool recursive: false, bool includeHiddenFiles: false}) { | 144 {bool recursive: false, bool includeHiddenFiles: false}) { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
201 }); | 199 }); |
202 } | 200 } |
203 | 201 |
204 return doList(dir, new Set<String>()); | 202 return doList(dir, new Set<String>()); |
205 } | 203 } |
206 | 204 |
207 /// Determines if [dir] exists on the file system. | 205 /// Determines if [dir] exists on the file system. |
208 bool dirExists(String dir) => new Directory(dir).existsSync(); | 206 bool dirExists(String dir) => new Directory(dir).existsSync(); |
209 | 207 |
210 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 208 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
211 /// new empty directory will be created. Returns a [Future] that completes when | 209 /// new empty directory will be created. |
212 /// the new clean directory is created. | 210 void cleanDir(String dir) { |
213 Future<String> cleanDir(String dir) { | 211 if (dirExists(dir)) { |
214 return defer(() { | 212 // Delete it first. |
215 if (dirExists(dir)) { | 213 deleteDir(dir); |
216 // Delete it first. | 214 } else if (fileExists(dir)) { |
217 return deleteDir(dir).then((_) => createDir(dir)); | 215 // If there is a non-directory there (file or symlink), delete it. |
218 } | 216 deleteFile(dir); |
| 217 } |
219 | 218 |
220 if (fileExists(dir)) { | 219 // Just create it. |
221 // If there is a non-directory there (file or symlink), delete it. | 220 createDir(dir); |
222 deleteFile(dir); | |
223 return createDir(dir); | |
224 } | |
225 | |
226 // Just create it. | |
227 return createDir(dir); | |
228 }); | |
229 } | 221 } |
230 | 222 |
231 // TODO(nweiz): remove this when issue 9253 is fixed. | 223 /// Renames (i.e. moves) the directory [from] to [to]. |
232 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with | 224 void renameDir(String from, String to) { |
233 /// the destination directory. | |
234 Future<String> renameDir(String from, String to) { | |
235 log.io("Renaming directory $from to $to."); | 225 log.io("Renaming directory $from to $to."); |
236 | 226 new Directory(from).renameSync(to); |
237 return _attemptRetryable(() => new Directory(from).rename(to)).then((dir) { | |
238 log.fine("Renamed directory $from to $to."); | |
239 return to; | |
240 }); | |
241 } | |
242 | |
243 /// On Windows, we sometimes get failures where the directory is still in use | |
244 /// when we try to do something with it. This is usually because the OS hasn't | |
245 /// noticed yet that a process using that directory has closed. To be a bit | |
246 /// more resilient, we wait and retry a few times. | |
247 /// | |
248 /// Takes a [callback] which returns a future for the operation being attempted. | |
249 /// If that future completes with an error, it will slepp and then [callback] | |
250 /// will be invoked again to retry the operation. It will try a few times before | |
251 /// giving up. | |
252 Future _attemptRetryable(Future callback()) { | |
253 // Only do lame retry logic on Windows. | |
254 if (Platform.operatingSystem != 'windows') return callback(); | |
255 | |
256 var attempts = 0; | |
257 makeAttempt(_) { | |
258 attempts++; | |
259 return callback().catchError((e) { | |
260 if (attempts >= 10) { | |
261 throw 'Could not complete operation. Gave up after $attempts attempts.'; | |
262 } | |
263 | |
264 // Wait a bit and try again. | |
265 log.fine("Operation failed, retrying (attempt $attempts)."); | |
266 return sleep(500).then(makeAttempt); | |
267 }); | |
268 } | |
269 | |
270 return makeAttempt(null); | |
271 } | 227 } |
272 | 228 |
273 /// Creates a new symlink at path [symlink] that points to [target]. Returns a | 229 /// Creates a new symlink at path [symlink] that points to [target]. Returns a |
274 /// [Future] which completes to the path to the symlink file. | 230 /// [Future] which completes to the path to the symlink file. |
275 /// | 231 /// |
276 /// If [relative] is true, creates a symlink with a relative path from the | 232 /// If [relative] is true, creates a symlink with a relative path from the |
277 /// symlink to the target. Otherwise, uses the [target] path unmodified. | 233 /// symlink to the target. Otherwise, uses the [target] path unmodified. |
278 /// | 234 /// |
279 /// Note that on Windows, only directories may be symlinked to. | 235 /// Note that on Windows, only directories may be symlinked to. |
280 Future<String> createSymlink(String target, String symlink, | 236 Future<String> createSymlink(String target, String symlink, |
(...skipping 332 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
613 }).catchError((e) { | 569 }).catchError((e) { |
614 if (completer.isCompleted) return; | 570 if (completer.isCompleted) return; |
615 timer.cancel(); | 571 timer.cancel(); |
616 completer.completeError(e); | 572 completer.completeError(e); |
617 }); | 573 }); |
618 return completer.future; | 574 return completer.future; |
619 } | 575 } |
620 | 576 |
621 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] | 577 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
622 /// returned by [fn] completes, the temporary directory and all its contents | 578 /// returned by [fn] completes, the temporary directory and all its contents |
623 /// will be deleted. | 579 /// will be deleted. [fn] can also return `null`, in which case the temporary |
| 580 /// directory is deleted immediately afterwards. |
624 /// | 581 /// |
625 /// Returns a future that completes to the value that the future returned from | 582 /// Returns a future that completes to the value that the future returned from |
626 /// [fn] completes to. | 583 /// [fn] completes to. |
627 Future withTempDir(Future fn(String path)) { | 584 Future withTempDir(Future fn(String path)) { |
628 return defer(() { | 585 return defer(() { |
629 var tempDir = createTempDir(); | 586 var tempDir = createTempDir(); |
630 return fn(tempDir).whenComplete(() { | 587 return new Future.of(() => fn(tempDir)) |
631 return deleteDir(tempDir); | 588 .whenComplete(() => deleteDir(tempDir)); |
632 }); | |
633 }); | 589 }); |
634 } | 590 } |
635 | 591 |
636 /// Extracts a `.tar.gz` file from [stream] to [destination]. Returns whether | 592 /// Extracts a `.tar.gz` file from [stream] to [destination]. Returns whether |
637 /// or not the extraction was successful. | 593 /// or not the extraction was successful. |
638 Future<bool> extractTarGz(Stream<List<int>> stream, String destination) { | 594 Future<bool> extractTarGz(Stream<List<int>> stream, String destination) { |
639 log.fine("Extracting .tar.gz stream to $destination."); | 595 log.fine("Extracting .tar.gz stream to $destination."); |
640 | 596 |
641 if (Platform.operatingSystem == "windows") { | 597 if (Platform.operatingSystem == "windows") { |
642 return _extractTarGzWindows(stream, destination); | 598 return _extractTarGzWindows(stream, destination); |
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
812 const PubProcessResult(this.stdout, this.stderr, this.exitCode); | 768 const PubProcessResult(this.stdout, this.stderr, this.exitCode); |
813 | 769 |
814 bool get success => exitCode == 0; | 770 bool get success => exitCode == 0; |
815 } | 771 } |
816 | 772 |
817 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 773 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
818 Uri _getUri(uri) { | 774 Uri _getUri(uri) { |
819 if (uri is Uri) return uri; | 775 if (uri is Uri) return uri; |
820 return Uri.parse(uri); | 776 return Uri.parse(uri); |
821 } | 777 } |
OLD | NEW |