Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(158)

Side by Side Diff: utils/pub/io.dart

Issue 11437019: Add logging system to pub and sprinkle some logging in. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: print() -> log.message(). Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 'curl_client.dart';
17 import 'log.dart' as log;
16 import 'utils.dart'; 18 import 'utils.dart';
17 import 'curl_client.dart';
18 19
19 bool _isGitInstalledCache; 20 bool _isGitInstalledCache;
20 21
21 /// The cached Git command. 22 /// The cached Git command.
22 String _gitCommandCache; 23 String _gitCommandCache;
23 24
24 /** Gets the current working directory. */ 25 /** Gets the current working directory. */
25 String get currentWorkingDir => new File('.').fullPathSync(); 26 String get currentWorkingDir => new File('.').fullPathSync();
26 27
27 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); 28 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?");
28 29
29 /** 30 /**
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 /**
39 * Joins a number of path string parts into a single path. Handles 31 * Joins a number of path string parts into a single path. Handles
40 * platform-specific path separators. Parts can be [String], [Directory], or 32 * platform-specific path separators. Parts can be [String], [Directory], or
41 * [File] objects. 33 * [File] objects.
42 */ 34 */
43 String join(part1, [part2, part3, part4]) { 35 String join(part1, [part2, part3, part4]) {
44 final parts = _sanitizePath(part1).split('/'); 36 final parts = _sanitizePath(part1).split('/');
45 37
46 for (final part in [part2, part3, part4]) { 38 for (final part in [part2, part3, part4]) {
47 if (part == null) continue; 39 if (part == null) continue;
48 40
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
115 return results[0] || results[1]; 107 return results[0] || results[1];
116 }); 108 });
117 } 109 }
118 110
119 /** 111 /**
120 * Asynchronously determines if [file], which can be a [String] file path or a 112 * Asynchronously determines if [file], which can be a [String] file path or a
121 * [File], exists on the file system. Returns a [Future] that completes with 113 * [File], exists on the file system. Returns a [Future] that completes with
122 * the result. 114 * the result.
123 */ 115 */
124 Future<bool> fileExists(file) { 116 Future<bool> fileExists(file) {
125 return new File(_getPath(file)).exists(); 117 var path = _getPath(file);
118 return log.ioAsync("Seeing if file $path exists.",
119 new File(path).exists(),
120 (exists) => "File $path ${exists ? 'exists' : 'does not exist'}.");
126 } 121 }
127 122
128 /** 123 /**
129 * Reads the contents of the text file [file], which can either be a [String] or 124 * Reads the contents of the text file [file], which can either be a [String] or
130 * a [File]. 125 * a [File].
131 */ 126 */
132 Future<String> readTextFile(file) { 127 Future<String> readTextFile(file) {
133 return new File(_getPath(file)).readAsString(Encoding.UTF_8); 128 var path = _getPath(file);
129 log.io("Reading text file $path.");
130 return new File(path).readAsString(Encoding.UTF_8).transform((contents) {
nweiz 2012/12/05 23:56:54 Why aren't you using log.ioAsync here?
Bob Nystrom 2012/12/06 01:33:26 Because it uses log.fine and not log.io for the re
131 // Sanity check: don't spew a huge file.
132 if (contents.length < 1024 * 1024) {
133 log.fine("Read $path. Contents:\n$contents");
nweiz 2012/12/05 23:56:54 log.io? Seems like almost every log call in this f
Bob Nystrom 2012/12/06 01:33:26 My initial thought was that file contents and proc
nweiz 2012/12/06 19:40:02 I think IO: Read file "foo" Contents: | foo | bar
Bob Nystrom 2012/12/07 21:13:44 Done.
134 } else {
135 log.fine("Read ${contents.length} characters from $path.");
136 }
137 return contents;
138 });
134 } 139 }
135 140
136 /** 141 /**
137 * Creates [file] (which can either be a [String] or a [File]), and writes 142 * Creates [file] (which can either be a [String] or a [File]), and writes
138 * [contents] to it. Completes when the file is written and closed. 143 * [contents] to it. Completes when the file is written and closed.
139 */ 144 */
140 Future<File> writeTextFile(file, String contents) { 145 Future<File> writeTextFile(file, String contents) {
141 file = new File(_getPath(file)); 146 var path = _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
142 return file.open(FileMode.WRITE).chain((opened) { 155 return file.open(FileMode.WRITE).chain((opened) {
143 return opened.writeString(contents).chain((ignore) { 156 return opened.writeString(contents).chain((ignore) {
144 return opened.close().transform((ignore) => file); 157 return opened.close().transform((ignore) {
nweiz 2012/12/05 23:56:54 "ignore" -> "_"
Bob Nystrom 2012/12/06 01:33:26 Done.
158 log.fine("Wrote text file $path.");
159 return file;
160 });
145 }); 161 });
146 }); 162 });
147 } 163 }
148 164
149 /** 165 /**
150 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a 166 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a
151 * [Future] that completes when the deletion is done. 167 * [Future] that completes when the deletion is done.
152 */ 168 */
153 Future<File> deleteFile(file) { 169 Future<File> deleteFile(file) {
154 return new File(_getPath(file)).delete(); 170 var path = _getPath(file);
171 return log.ioAsync("delete file $path",
172 new File(path).delete());
nweiz 2012/12/05 23:56:54 Looks like this could be one line? Same goes for s
Bob Nystrom 2012/12/06 01:33:26 I put the future stuff on the second line even whe
155 } 173 }
156 174
157 /// Writes [stream] to a new file at [path], which may be a [String] or a 175 /// Writes [stream] to a new file at [path], which may be a [String] or a
158 /// [File]. Will replace any file already at that path. Completes when the file 176 /// [File]. Will replace any file already at that path. Completes when the file
159 /// is done being written. 177 /// is done being written.
160 Future<File> createFileFromStream(InputStream stream, path) { 178 Future<File> createFileFromStream(InputStream stream, path) {
161 path = _getPath(path); 179 path = _getPath(path);
162 180
181 log.io("Creating $path from stream.");
182
163 var completer = new Completer<File>(); 183 var completer = new Completer<File>();
164 var file = new File(path); 184 var file = new File(path);
165 var outputStream = file.openOutputStream(); 185 var outputStream = file.openOutputStream();
166 stream.pipe(outputStream); 186 stream.pipe(outputStream);
167 187
168 outputStream.onClosed = () { 188 outputStream.onClosed = () {
189 log.fine("Created $path from stream.");
169 completer.complete(file); 190 completer.complete(file);
170 }; 191 };
171 192
172 // TODO(nweiz): remove this when issue 4061 is fixed. 193 // TODO(nweiz): remove this when issue 4061 is fixed.
173 var stackTrace; 194 var stackTrace;
174 try { 195 try {
175 throw ""; 196 throw "";
176 } catch (_, localStackTrace) { 197 } catch (_, localStackTrace) {
177 stackTrace = localStackTrace; 198 stackTrace = localStackTrace;
178 } 199 }
179 200
180 completeError(error) { 201 completeError(error) {
181 if (!completer.isComplete) completer.completeException(error, stackTrace); 202 if (!completer.isComplete) completer.completeException(error, stackTrace);
nweiz 2012/12/05 23:56:54 Log the error if the completer is complete.
Bob Nystrom 2012/12/06 01:33:26 Done.
182 } 203 }
183 204
184 stream.onError = completeError; 205 stream.onError = completeError;
185 outputStream.onError = completeError; 206 outputStream.onError = completeError;
186 207
187 return completer.future; 208 return completer.future;
188 } 209 }
189 210
190 /** 211 /**
191 * Creates a directory [dir]. Returns a [Future] that completes when the 212 * Creates a directory [dir]. Returns a [Future] that completes when the
192 * directory is created. 213 * directory is created.
193 */ 214 */
194 Future<Directory> createDir(dir) { 215 Future<Directory> createDir(dir) {
195 dir = _getDirectory(dir); 216 dir = _getDirectory(dir);
196 return dir.create(); 217 return log.ioAsync("create directory ${dir.path}",
218 dir.create());
197 } 219 }
198 220
199 /** 221 /**
200 * Ensures that [path] and all its parent directories exist. If they don't 222 * Ensures that [path] and all its parent directories exist. If they don't
201 * exist, creates them. Returns a [Future] that completes once all the 223 * exist, creates them. Returns a [Future] that completes once all the
202 * directories are created. 224 * directories are created.
203 */ 225 */
204 Future<Directory> ensureDir(path) { 226 Future<Directory> ensureDir(path) {
205 path = _getPath(path); 227 path = _getPath(path);
228 log.fine("Ensuring directory $path exists.");
206 if (path == '.') return new Future.immediate(new Directory('.')); 229 if (path == '.') return new Future.immediate(new Directory('.'));
207 230
208 return dirExists(path).chain((exists) { 231 return dirExists(path).chain((exists) {
209 if (exists) return new Future.immediate(new Directory(path)); 232 if (exists) {
233 log.fine("Directory $path already exists.");
234 return new Future.immediate(new Directory(path));
235 }
236
210 return ensureDir(dirname(path)).chain((_) { 237 return ensureDir(dirname(path)).chain((_) {
211 var completer = new Completer<Directory>(); 238 var completer = new Completer<Directory>();
212 var future = createDir(path); 239 var future = createDir(path);
213 future.handleException((error) { 240 future.handleException((error) {
214 if (error is! DirectoryIOException) return false; 241 if (error is! DirectoryIOException) return false;
215 // Error 17 means the directory already exists (or 183 on Windows). 242 // Error 17 means the directory already exists (or 183 on Windows).
216 if (error.osError.errorCode != 17 && 243 if (error.osError.errorCode != 17 &&
217 error.osError.errorCode != 183) return false; 244 error.osError.errorCode != 183) return false;
nweiz 2012/12/05 23:56:54 Log this case as well.
Bob Nystrom 2012/12/06 01:33:26 Done.
218 245
219 completer.complete(_getDirectory(path)); 246 completer.complete(_getDirectory(path));
220 return true; 247 return true;
221 }); 248 });
222 future.then(completer.complete); 249 future.then(completer.complete);
223 return completer.future; 250 return completer.future;
224 }); 251 });
225 }); 252 });
226 } 253 }
227 254
228 /** 255 /**
229 * Creates a temp directory whose name will be based on [dir] with a unique 256 * Creates a temp directory whose name will be based on [dir] with a unique
230 * suffix appended to it. If [dir] is not provided, a temp directory will be 257 * suffix appended to it. If [dir] is not provided, a temp directory will be
231 * created in a platform-dependent temporary location. Returns a [Future] that 258 * created in a platform-dependent temporary location. Returns a [Future] that
232 * completes when the directory is created. 259 * completes when the directory is created.
233 */ 260 */
234 Future<Directory> createTempDir([dir = '']) { 261 Future<Directory> createTempDir([dir = '']) {
235 dir = _getDirectory(dir); 262 dir = _getDirectory(dir);
236 return dir.createTemp(); 263 return log.ioAsync("create temp directory ${dir.path}",
264 dir.createTemp());
237 } 265 }
238 266
239 /** 267 /**
240 * Asynchronously recursively deletes [dir], which can be a [String] or a 268 * Asynchronously recursively deletes [dir], which can be a [String] or a
241 * [Directory]. Returns a [Future] that completes when the deletion is done. 269 * [Directory]. Returns a [Future] that completes when the deletion is done.
242 */ 270 */
243 Future<Directory> deleteDir(dir) { 271 Future<Directory> deleteDir(dir) {
244 dir = _getDirectory(dir); 272 dir = _getDirectory(dir);
245 return dir.delete(recursive: true); 273 return log.ioAsync("delete directory ${dir.path}",
274 dir.delete(recursive: true));
246 } 275 }
247 276
248 /** 277 /**
249 * Asynchronously lists the contents of [dir], which can be a [String] directory 278 * Asynchronously lists the contents of [dir], which can be a [String] directory
250 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents 279 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents
251 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and 280 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and
252 * directories beginning with `.` (defaults to `false`). 281 * directories beginning with `.` (defaults to `false`).
253 */ 282 */
254 Future<List<String>> listDir(dir, 283 Future<List<String>> listDir(dir,
255 {bool recursive: false, bool includeHiddenFiles: false}) { 284 {bool recursive: false, bool includeHiddenFiles: false}) {
256 final completer = new Completer<List<String>>(); 285 final completer = new Completer<List<String>>();
257 final contents = <String>[]; 286 final contents = <String>[];
258 287
259 dir = _getDirectory(dir); 288 dir = _getDirectory(dir);
289 log.io("Listing directory ${dir.path}.");
260 var lister = dir.list(recursive: recursive); 290 var lister = dir.list(recursive: recursive);
261 291
262 lister.onDone = (done) { 292 lister.onDone = (done) {
263 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile 293 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile
264 // aren't guaranteed to be called in a certain order. So far, they seem to. 294 // aren't guaranteed to be called in a certain order. So far, they seem to.
265 if (done) completer.complete(contents); 295 if (done) {
296 log.fine("Listed directory ${dir.path}:\n"
297 "${Strings.join(contents, '\n')}");
nweiz 2012/12/05 23:56:54 Indent contents.
Bob Nystrom 2012/12/06 01:33:26 See previous comment.
298 completer.complete(contents);
299 }
266 }; 300 };
267 301
268 // TODO(nweiz): remove this when issue 4061 is fixed. 302 // TODO(nweiz): remove this when issue 4061 is fixed.
269 var stackTrace; 303 var stackTrace;
270 try { 304 try {
271 throw ""; 305 throw "";
272 } catch (_, localStackTrace) { 306 } catch (_, localStackTrace) {
273 stackTrace = localStackTrace; 307 stackTrace = localStackTrace;
274 } 308 }
275 309
(...skipping 10 matching lines...) Expand all
286 return completer.future; 320 return completer.future;
287 } 321 }
288 322
289 /** 323 /**
290 * Asynchronously determines if [dir], which can be a [String] directory path 324 * Asynchronously determines if [dir], which can be a [String] directory path
291 * or a [Directory], exists on the file system. Returns a [Future] that 325 * or a [Directory], exists on the file system. Returns a [Future] that
292 * completes with the result. 326 * completes with the result.
293 */ 327 */
294 Future<bool> dirExists(dir) { 328 Future<bool> dirExists(dir) {
295 dir = _getDirectory(dir); 329 dir = _getDirectory(dir);
296 return dir.exists(); 330 return log.ioAsync("Seeing if directory ${dir.path} exists.",
331 dir.exists(),
332 (exists) => "Directory ${dir.path} "
333 "${exists ? 'exists' : 'does not exist'}.");
297 } 334 }
298 335
299 /** 336 /**
300 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a 337 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a
301 * new empty directory will be created. Returns a [Future] that completes when 338 * new empty directory will be created. Returns a [Future] that completes when
302 * the new clean directory is created. 339 * the new clean directory is created.
303 */ 340 */
304 Future<Directory> cleanDir(dir) { 341 Future<Directory> cleanDir(dir) {
305 return dirExists(dir).chain((exists) { 342 return dirExists(dir).chain((exists) {
306 if (exists) { 343 if (exists) {
307 // Delete it first. 344 // Delete it first.
308 return deleteDir(dir).chain((_) => createDir(dir)); 345 return deleteDir(dir).chain((_) => createDir(dir));
309 } else { 346 } else {
310 // Just create it. 347 // Just create it.
311 return createDir(dir); 348 return createDir(dir);
312 } 349 }
313 }); 350 });
314 } 351 }
315 352
316 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with 353 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with
317 /// the destination directory. 354 /// the destination directory.
318 Future<Directory> renameDir(from, String to) { 355 Future<Directory> renameDir(from, String to) {
319 from = _getDirectory(from); 356 from = _getDirectory(from);
357 log.io("Renaming directory ${from.path} to $to.");
320 358
321 if (Platform.operatingSystem != 'windows') return from.rename(to); 359 if (Platform.operatingSystem != 'windows') {
360 return from.rename(to).transform((dir) {
361 log.fine("Renamed directory ${from.path} to $to.");
362 return dir;
363 });
364 }
322 365
323 // On Windows, we sometimes get failures where the directory is still in use 366 // On Windows, we sometimes get failures where the directory is still in use
324 // when we try to move it. To be a bit more resilient, we wait and retry a 367 // when we try to move it. To be a bit more resilient, we wait and retry a
325 // few times. 368 // few times.
326 var attempts = 0; 369 var attempts = 0;
327 attemptRename(_) { 370 attemptRename(_) {
328 attempts++; 371 attempts++;
329 return from.rename(to).transformException((e) { 372 return from.rename(to).transform((dir) {
373 log.fine("Renamed directory ${from.path} to $to.");
374 return dir;
375 }).transformException((e) {
330 if (attempts >= 10) { 376 if (attempts >= 10) {
331 throw 'Could not move directory "${from.path}" to "$to". Gave up ' 377 throw 'Could not move directory "${from.path}" to "$to". Gave up '
332 'after $attempts attempts.'; 378 'after $attempts attempts.';
333 } 379 }
334 380
335 // Wait a bit and try again. 381 // Wait a bit and try again.
382 log.fine("Rename ${from.path} failed, retrying (attempt $attempts).");
336 return sleep(500).chain(attemptRename); 383 return sleep(500).chain(attemptRename);
337 }); 384 });
338 385
339 return from; 386 return from;
340 } 387 }
341 388
342 return attemptRename(null); 389 return attemptRename(null);
343 } 390 }
344 391
345 /** 392 /**
346 * Creates a new symlink that creates an alias from [from] to [to], both of 393 * Creates a new symlink that creates an alias from [from] to [to], both of
347 * which can be a [String], [File], or [Directory]. Returns a [Future] which 394 * which can be a [String], [File], or [Directory]. Returns a [Future] which
348 * completes to the symlink file (i.e. [to]). 395 * completes to the symlink file (i.e. [to]).
349 */ 396 */
350 Future<File> createSymlink(from, to) { 397 Future<File> createSymlink(from, to) {
351 from = _getPath(from); 398 from = _getPath(from);
352 to = _getPath(to); 399 to = _getPath(to);
353 400
401 log.fine("Create symlink $from -> $to.");
402
354 var command = 'ln'; 403 var command = 'ln';
355 var args = ['-s', from, to]; 404 var args = ['-s', from, to];
356 405
357 if (Platform.operatingSystem == 'windows') { 406 if (Platform.operatingSystem == 'windows') {
358 // Call mklink on Windows to create an NTFS junction point. Only works on 407 // Call mklink on Windows to create an NTFS junction point. Only works on
359 // Vista or later. (Junction points are available earlier, but the "mklink" 408 // Vista or later. (Junction points are available earlier, but the "mklink"
360 // command is not.) I'm using a junction point (/j) here instead of a soft 409 // command is not.) I'm using a junction point (/j) here instead of a soft
361 // link (/d) because the latter requires some privilege shenanigans that 410 // link (/d) because the latter requires some privilege shenanigans that
362 // I'm not sure how to specify from the command line. 411 // I'm not sure how to specify from the command line.
363 command = 'mklink'; 412 command = 'mklink';
(...skipping 11 matching lines...) Expand all
375 * package [from] to [to], both of which can be a [String], [File], or 424 * package [from] to [to], both of which can be a [String], [File], or
376 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. 425 * [Directory]. Returns a [Future] which completes to the symlink file (i.e.
377 * [to]). If [from] does not have a `lib` directory, this shows a warning if 426 * [to]). If [from] does not have a `lib` directory, this shows a warning if
378 * appropriate and then does nothing. 427 * appropriate and then does nothing.
379 */ 428 */
380 Future<File> createPackageSymlink(String name, from, to, 429 Future<File> createPackageSymlink(String name, from, to,
381 {bool isSelfLink: false}) { 430 {bool isSelfLink: false}) {
382 // See if the package has a "lib" directory. 431 // See if the package has a "lib" directory.
383 from = join(from, 'lib'); 432 from = join(from, 'lib');
384 return dirExists(from).chain((exists) { 433 return dirExists(from).chain((exists) {
434 log.fine("Creating package ${isSelfLink ? "self" : ""}link from "
435 "$from to $to.");
nweiz 2012/12/05 23:56:54 Including "$from" and "$to" seems redundant with c
Bob Nystrom 2012/12/06 01:33:26 Done.
385 if (exists) return createSymlink(from, to); 436 if (exists) return createSymlink(from, to);
386 437
387 // It's OK for the self link (i.e. the root package) to not have a lib 438 // It's OK for the self link (i.e. the root package) to not have a lib
388 // directory since it may just be a leaf application that only has 439 // directory since it may just be a leaf application that only has
389 // code in bin or web. 440 // code in bin or web.
390 if (!isSelfLink) { 441 if (!isSelfLink) {
391 printError( 442 log.warning('Package "$name" does not have a "lib" directory so you '
392 'Warning: Package "$name" does not have a "lib" directory so you ' 443 'will not be able to import any libraries from it.');
393 'will not be able to import any libraries from it.');
394 } 444 }
395 445
396 return new Future.immediate(to); 446 return new Future.immediate(to);
397 }); 447 });
398 } 448 }
399 449
400 /// Given [entry] which may be a [String], [File], or [Directory] relative to 450 /// Given [entry] which may be a [String], [File], or [Directory] relative to
401 /// the current working directory, returns its full canonicalized path. 451 /// the current working directory, returns its full canonicalized path.
402 String getFullPath(entry) { 452 String getFullPath(entry) {
403 var path = _getPath(entry); 453 var path = _getPath(entry);
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
499 549
500 /// An HTTP client that transforms 40* errors and socket exceptions into more 550 /// An HTTP client that transforms 40* errors and socket exceptions into more
501 /// user-friendly error messages. 551 /// user-friendly error messages.
502 class PubHttpClient extends http.BaseClient { 552 class PubHttpClient extends http.BaseClient {
503 final http.Client _inner; 553 final http.Client _inner;
504 554
505 PubHttpClient([http.Client inner]) 555 PubHttpClient([http.Client inner])
506 : _inner = inner == null ? new http.Client() : inner; 556 : _inner = inner == null ? new http.Client() : inner;
507 557
508 Future<http.StreamedResponse> send(http.BaseRequest request) { 558 Future<http.StreamedResponse> send(http.BaseRequest request) {
559 log.io("Sending HTTP request $request.");
nweiz 2012/12/05 23:56:54 We should do the same trick as for reading/writing
Bob Nystrom 2012/12/06 01:33:26 Is that easy to do? I thought that would be tricky
nweiz 2012/12/06 19:40:02 For the response it may not be possible until we h
560
509 // TODO(nweiz): remove this when issue 4061 is fixed. 561 // TODO(nweiz): remove this when issue 4061 is fixed.
510 var stackTrace; 562 var stackTrace;
511 try { 563 try {
512 throw null; 564 throw null;
513 } catch (_, localStackTrace) { 565 } catch (_, localStackTrace) {
514 stackTrace = localStackTrace; 566 stackTrace = localStackTrace;
515 } 567 }
516 568
517 // TODO(nweiz): Ideally the timeout would extend to reading from the 569 // TODO(nweiz): Ideally the timeout would extend to reading from the
518 // response input stream, but until issue 3657 is fixed that's not feasible. 570 // response input stream, but until issue 3657 is fixed that's not feasible.
519 return timeout(_inner.send(request).chain((streamedResponse) { 571 return timeout(_inner.send(request).chain((streamedResponse) {
572 log.fine("Got response ${streamedResponse.statusCode} "
573 "${streamedResponse.reasonPhrase}.");
574
520 if (streamedResponse.statusCode < 400) { 575 if (streamedResponse.statusCode < 400) {
521 return new Future.immediate(streamedResponse); 576 return new Future.immediate(streamedResponse);
522 } 577 }
523 578
524 return http.Response.fromStream(streamedResponse).transform((response) { 579 return http.Response.fromStream(streamedResponse).transform((response) {
525 throw new PubHttpException(response); 580 throw new PubHttpException(response);
526 }); 581 });
527 }).transformException((e) { 582 }).transformException((e) {
528 if (e is SocketIOException && 583 if (e is SocketIOException &&
529 e.osError != null && 584 e.osError != null &&
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
612 Future<PubProcessResult> runProcess(String executable, List<String> args, 667 Future<PubProcessResult> runProcess(String executable, List<String> args,
613 {workingDir, Map<String, String> environment}) { 668 {workingDir, Map<String, String> environment}) {
614 return _doProcess(Process.run, executable, args, workingDir, environment) 669 return _doProcess(Process.run, executable, args, workingDir, environment)
615 .transform((result) { 670 .transform((result) {
616 // TODO(rnystrom): Remove this and change to returning one string. 671 // TODO(rnystrom): Remove this and change to returning one string.
617 List<String> toLines(String output) { 672 List<String> toLines(String output) {
618 var lines = output.split(NEWLINE_PATTERN); 673 var lines = output.split(NEWLINE_PATTERN);
619 if (!lines.isEmpty && lines.last == "") lines.removeLast(); 674 if (!lines.isEmpty && lines.last == "") lines.removeLast();
620 return lines; 675 return lines;
621 } 676 }
622 return new PubProcessResult(toLines(result.stdout), 677
678 var pubResult = new PubProcessResult(toLines(result.stdout),
nweiz 2012/12/05 23:56:54 nit: "pubResult" -> "result"
Bob Nystrom 2012/12/06 01:33:26 "result" is the already the name of the closure's
623 toLines(result.stderr), 679 toLines(result.stderr),
624 result.exitCode); 680 result.exitCode);
681
682 log.processResult(executable, args, pubResult);
683 return pubResult;
625 }); 684 });
626 } 685 }
627 686
628 /// Spawns the process located at [executable], passing in [args]. Returns a 687 /// Spawns the process located at [executable], passing in [args]. Returns a
629 /// [Future] that will complete with the [Process] once it's been started. 688 /// [Future] that will complete with the [Process] once it's been started.
630 /// 689 ///
631 /// The spawned process will inherit its parent's environment variables. If 690 /// The spawned process will inherit its parent's environment variables. If
632 /// [environment] is provided, that will be used to augment (not replace) the 691 /// [environment] is provided, that will be used to augment (not replace) the
633 /// the inherited variables. 692 /// the inherited variables.
634 Future<Process> startProcess(String executable, List<String> args, 693 Future<Process> startProcess(String executable, List<String> args,
635 {workingDir, Map<String, String> environment}) => 694 {workingDir, Map<String, String> environment}) {
636 _doProcess(Process.start, executable, args, workingDir, environment); 695 return _doProcess(Process.start, executable, args, workingDir, environment);
696 }
nweiz 2012/12/05 23:56:54 :(
Bob Nystrom 2012/12/06 01:33:26 Fixed. Leftover code from some discarded changes t
637 697
638 /// Calls [fn] with appropriately modified arguments. [fn] should have the same 698 /// Calls [fn] with appropriately modified arguments. [fn] should have the same
639 /// signature as [Process.start], except that the returned [Future] may have a 699 /// signature as [Process.start], except that the returned [Future] may have a
640 /// type other than [Process]. 700 /// type other than [Process].
641 Future _doProcess(Function fn, String executable, List<String> args, workingDir, 701 Future _doProcess(Function fn, String executable, List<String> args, workingDir,
642 Map<String, String> environment) { 702 Map<String, String> environment) {
643 // TODO(rnystrom): Should dart:io just handle this? 703 // TODO(rnystrom): Should dart:io just handle this?
644 // Spawning a process on Windows will not look for the executable in the 704 // Spawning a process on Windows will not look for the executable in the
645 // system path. So, if executable looks like it needs that (i.e. it doesn't 705 // system path. So, if executable looks like it needs that (i.e. it doesn't
646 // have any path separators in it), then spawn it through a shell. 706 // have any path separators in it), then spawn it through a shell.
647 if ((Platform.operatingSystem == "windows") && 707 if ((Platform.operatingSystem == "windows") &&
648 (executable.indexOf('\\') == -1)) { 708 (executable.indexOf('\\') == -1)) {
649 args = flatten(["/c", executable, args]); 709 args = flatten(["/c", executable, args]);
650 executable = "cmd"; 710 executable = "cmd";
651 } 711 }
652 712
653 final options = new ProcessOptions(); 713 final options = new ProcessOptions();
654 if (workingDir != null) { 714 if (workingDir != null) {
655 options.workingDirectory = _getDirectory(workingDir).path; 715 options.workingDirectory = _getDirectory(workingDir).path;
656 } 716 }
657 717
658 if (environment != null) { 718 if (environment != null) {
659 options.environment = new Map.from(Platform.environment); 719 options.environment = new Map.from(Platform.environment);
660 environment.forEach((key, value) => options.environment[key] = value); 720 environment.forEach((key, value) => options.environment[key] = value);
661 } 721 }
662 722
723 log.process(executable, args);
724
663 return fn(executable, args, options); 725 return fn(executable, args, options);
664 } 726 }
665 727
666 /// Closes [response] while ignoring the body of [request]. Returns a Future 728 /// Closes [response] while ignoring the body of [request]. Returns a Future
667 /// that completes once the response is closed. 729 /// that completes once the response is closed.
668 /// 730 ///
669 /// Due to issue 6984, it's necessary to drain the request body before closing 731 /// Due to issue 6984, it's necessary to drain the request body before closing
670 /// the response. 732 /// the response.
671 Future closeHttpResponse(HttpRequest request, HttpResponse response) { 733 Future closeHttpResponse(HttpRequest request, HttpResponse response) {
672 // TODO(nweiz): remove this when issue 4061 is fixed. 734 // TODO(nweiz): remove this when issue 4061 is fixed.
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
715 if (completer.future.isComplete) return; 777 if (completer.future.isComplete) return;
716 timer.cancel(); 778 timer.cancel();
717 completer.complete(value); 779 completer.complete(value);
718 }); 780 });
719 return completer.future; 781 return completer.future;
720 } 782 }
721 783
722 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] 784 /// Creates a temporary directory and passes its path to [fn]. Once the [Future]
723 /// returned by [fn] completes, the temporary directory and all its contents 785 /// returned by [fn] completes, the temporary directory and all its contents
724 /// will be deleted. 786 /// will be deleted.
725 Future withTempDir(Future fn(String path)) { 787 Future withTempDir(Future fn(String path)) {
nweiz 2012/12/05 23:56:54 It would probably help with debugging if this and
Bob Nystrom 2012/12/06 01:33:26 Agreed, but dart:io doesn't support that. :(
nweiz 2012/12/06 19:40:02 File a bug?
Bob Nystrom 2012/12/07 21:13:44 Eh, I don't care that much about it.
726 var tempDir; 788 var tempDir;
727 var future = createTempDir().chain((dir) { 789 var future = createTempDir().chain((dir) {
728 tempDir = dir; 790 tempDir = dir;
791 log.fine('Within temp directory ${tempDir.path}.');
nweiz 2012/12/05 23:56:54 "Within" is a little confusing, since the working
Bob Nystrom 2012/12/06 01:33:26 Removed.
729 return fn(tempDir.path); 792 return fn(tempDir.path);
730 }); 793 });
731 future.onComplete((_) => tempDir.delete(recursive: true)); 794 future.onComplete((_) {
795 log.fine('Cleaning up temp directory ${tempDir.path}.');
796 deleteDir(tempDir);
797 });
732 return future; 798 return future;
733 } 799 }
734 800
735 /// Tests whether or not the git command-line app is available for use. 801 /// Tests whether or not the git command-line app is available for use.
736 Future<bool> get isGitInstalled { 802 Future<bool> get isGitInstalled {
737 if (_isGitInstalledCache != null) { 803 if (_isGitInstalledCache != null) {
738 // TODO(rnystrom): The sleep is to pump the message queue. Can use 804 // TODO(rnystrom): The sleep is to pump the message queue. Can use
739 // Future.immediate() when #3356 is fixed. 805 // Future.immediate() when #3356 is fixed.
740 return sleep(0).transform((_) => _isGitInstalledCache); 806 return sleep(0).transform((_) => _isGitInstalledCache);
741 } 807 }
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
794 return completer.future; 860 return completer.future;
795 } 861 }
796 862
797 /** 863 /**
798 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a 864 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a
799 * directory or a path. Returns whether or not the extraction was successful. 865 * directory or a path. Returns whether or not the extraction was successful.
800 */ 866 */
801 Future<bool> extractTarGz(InputStream stream, destination) { 867 Future<bool> extractTarGz(InputStream stream, destination) {
802 destination = _getPath(destination); 868 destination = _getPath(destination);
803 869
870 log.fine("Extracting .tar.gz stream to $destination.");
871
804 if (Platform.operatingSystem == "windows") { 872 if (Platform.operatingSystem == "windows") {
805 return _extractTarGzWindows(stream, destination); 873 return _extractTarGzWindows(stream, destination);
806 } 874 }
807 875
808 var completer = new Completer<int>(); 876 var completer = new Completer<int>();
809 var processFuture = Process.start("tar", 877 var processFuture = Process.start("tar",
810 ["--extract", "--gunzip", "--directory", destination]); 878 ["--extract", "--gunzip", "--directory", destination]);
811 processFuture.then((process) { 879 processFuture.then((process) {
812 process.onExit = completer.complete; 880 process.onExit = completer.complete;
813 stream.pipe(process.stdin); 881 stream.pipe(process.stdin);
814 process.stdout.pipe(stdout, close: false); 882 process.stdout.pipe(stdout, close: false);
815 process.stderr.pipe(stderr, close: false); 883 process.stderr.pipe(stderr, close: false);
816 }); 884 });
817 processFuture.handleException((error) { 885 processFuture.handleException((error) {
818 completer.completeException(error, processFuture.stackTrace); 886 completer.completeException(error, processFuture.stackTrace);
819 return true; 887 return true;
820 }); 888 });
821 889
822 return completer.future.transform((exitCode) => exitCode == 0); 890 return completer.future.transform((exitCode) {
891 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode.");
892 return exitCode == 0;
nweiz 2012/12/05 23:56:54 Not strictly related to this change, but do we eve
Bob Nystrom 2012/12/06 01:33:26 Not sure. I'll add a TODO.
893 });
823 } 894 }
824 895
825 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { 896 Future<bool> _extractTarGzWindows(InputStream stream, String destination) {
826 // TODO(rnystrom): In the repo's history, there is an older implementation of 897 // TODO(rnystrom): In the repo's history, there is an older implementation of
827 // this that does everything in memory by piping streams directly together 898 // this that does everything in memory by piping streams directly together
828 // instead of writing out temp files. The code is simpler, but unfortunately, 899 // instead of writing out temp files. The code is simpler, but unfortunately,
829 // 7zip seems to periodically fail when we invoke it from Dart and tell it to 900 // 7zip seems to periodically fail when we invoke it from Dart and tell it to
830 // read from stdin instead of a file. Consider resurrecting that version if 901 // read from stdin instead of a file. Consider resurrecting that version if
831 // we can figure out why it fails. 902 // we can figure out why it fails.
832 903
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
869 940
870 // Untar the archive into the destination directory. 941 // Untar the archive into the destination directory.
871 return runProcess(command, ['x', tarFile], workingDir: destination); 942 return runProcess(command, ['x', tarFile], workingDir: destination);
872 }).chain((result) { 943 }).chain((result) {
873 if (result.exitCode != 0) { 944 if (result.exitCode != 0) {
874 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' 945 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n'
875 '${Strings.join(result.stdout, "\n")}\n' 946 '${Strings.join(result.stdout, "\n")}\n'
876 '${Strings.join(result.stderr, "\n")}'; 947 '${Strings.join(result.stderr, "\n")}';
877 } 948 }
878 949
879 // Clean up the temp directory. 950 log.fine('Clean up 7zip temp directory ${tempDir.path}.');
nweiz 2012/12/05 23:56:54 It seems more useful to log that tempDir is the 7z
Bob Nystrom 2012/12/06 01:33:26 You should see: Extracting .tar.gz stream to $des
nweiz 2012/12/06 19:40:02 In that case, this log line is probably redundant
Bob Nystrom 2012/12/07 21:13:44 I don't think so. Since this is async, the delete
880 // TODO(rnystrom): Should also delete this if anything fails. 951 // TODO(rnystrom): Should also delete this if anything fails.
881 return deleteDir(tempDir); 952 return deleteDir(tempDir);
882 }).transform((_) => true); 953 }).transform((_) => true);
883 } 954 }
884 955
885 /// Create a .tar.gz archive from a list of entries. Each entry can be a 956 /// Create a .tar.gz archive from a list of entries. Each entry can be a
886 /// [String], [Directory], or [File] object. The root of the archive is 957 /// [String], [Directory], or [File] object. The root of the archive is
887 /// considered to be [baseDir], which defaults to the current working directory. 958 /// considered to be [baseDir], which defaults to the current working directory.
888 /// Returns an [InputStream] that will emit the contents of the archive. 959 /// Returns an [InputStream] that will emit the contents of the archive.
889 InputStream createTarGz(List contents, {baseDir}) { 960 InputStream createTarGz(List contents, {baseDir}) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
944 } 1015 }
945 1016
946 /** 1017 /**
947 * Exception thrown when an HTTP operation fails. 1018 * Exception thrown when an HTTP operation fails.
948 */ 1019 */
949 class PubHttpException implements Exception { 1020 class PubHttpException implements Exception {
950 final http.Response response; 1021 final http.Response response;
951 1022
952 const PubHttpException(this.response); 1023 const PubHttpException(this.response);
953 1024
954 String toString() => 'HTTP error ${response.statusCode}: ${response.reason}'; 1025 String toString() => 'HTTP error ${response.statusCode}: '
1026 '${response.reasonPhrase}';
955 } 1027 }
956 1028
957 /** 1029 /**
958 * Exception thrown when an operation times out. 1030 * Exception thrown when an operation times out.
959 */ 1031 */
960 class TimeoutException implements Exception { 1032 class TimeoutException implements Exception {
961 final String message; 1033 final String message;
962 1034
963 const TimeoutException(this.message); 1035 const TimeoutException(this.message);
964 1036
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
1015 return new Directory(entry); 1087 return new Directory(entry);
1016 } 1088 }
1017 1089
1018 /** 1090 /**
1019 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. 1091 * Gets a [Uri] for [uri], which can either already be one, or be a [String].
1020 */ 1092 */
1021 Uri _getUri(uri) { 1093 Uri _getUri(uri) {
1022 if (uri is Uri) return uri; 1094 if (uri is Uri) return uri;
1023 return new Uri.fromString(uri); 1095 return new Uri.fromString(uri);
1024 } 1096 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698