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 part of dart.io; | 5 part of dart.io; |
6 | 6 |
7 class FileSystemEntityType { | 7 class FileSystemEntityType { |
8 static const FILE = const FileSystemEntityType._internal(0); | 8 static const FILE = const FileSystemEntityType._internal(0); |
9 static const DIRECTORY = const FileSystemEntityType._internal(1); | 9 static const DIRECTORY = const FileSystemEntityType._internal(1); |
10 static const LINK = const FileSystemEntityType._internal(2); | 10 static const LINK = const FileSystemEntityType._internal(2); |
(...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 * Identical to [:FileStat.statSync(this.path):]. | 310 * Identical to [:FileStat.statSync(this.path):]. |
311 * | 311 * |
312 * Returns a [FileStat] object containing the data returned by stat(). | 312 * Returns a [FileStat] object containing the data returned by stat(). |
313 * | 313 * |
314 * If the call fails, returns a [FileStat] object with .type set to | 314 * If the call fails, returns a [FileStat] object with .type set to |
315 * FileSystemEntityType.NOT_FOUND and the other fields invalid. | 315 * FileSystemEntityType.NOT_FOUND and the other fields invalid. |
316 */ | 316 */ |
317 FileStat statSync(); | 317 FileStat statSync(); |
318 | 318 |
319 | 319 |
| 320 |
| 321 /** |
| 322 * Start watch the [FileSystemEntity] for changes. |
| 323 * |
| 324 * The implementation uses platform-depending event-based APIs for receiving |
| 325 * file-system notifixations, thus behvaiour depends on the platform. |
| 326 * |
| 327 * * `Windows`: Uses `ReadDirectoryChangesW`. The implementation supports |
| 328 * only watching dirctories but supports recursive watching. |
| 329 * * `Linux`: Uses `inotify`. The implementation supports watching both |
| 330 * files and dirctories, but doesn't support recursive watching. |
| 331 * * `Mac OS`: Uses `FSEvents`. The implementation supports watching both |
| 332 * files and dirctories, and also recursive watching. Note that FSEvents |
| 333 * always use recursion internally, so when disabled, some events are |
| 334 * ignored. |
| 335 * |
| 336 * The system will start listen for events once the returned [Stream] is |
| 337 * being listened to, not when the call to [watch] is issued. Note that the |
| 338 * returned [Stream] is endless. To stop the [Stream], simply cancel the |
| 339 * subscription. |
| 340 */ |
| 341 Stream<FileSystemEvent> watch({int events: FileSystemEvent.ALL, |
| 342 bool recursive: false}) |
| 343 => new _FileSystemWatcher(_trimTrailingPathSeparators(path), |
| 344 events, |
| 345 recursive).stream; |
| 346 |
| 347 |
320 /** | 348 /** |
321 * Finds the type of file system object that a path points to. Returns | 349 * Finds the type of file system object that a path points to. Returns |
322 * a [:Future<FileSystemEntityType>:] that completes with the result. | 350 * a [:Future<FileSystemEntityType>:] that completes with the result. |
323 * | 351 * |
324 * [FileSystemEntityType] has the constant instances FILE, DIRECTORY, | 352 * [FileSystemEntityType] has the constant instances FILE, DIRECTORY, |
325 * LINK, and NOT_FOUND. [type] will return LINK only if the optional | 353 * LINK, and NOT_FOUND. [type] will return LINK only if the optional |
326 * named argument [followLinks] is false, and [path] points to a link. | 354 * named argument [followLinks] is false, and [path] points to a link. |
327 * If the path does not point to a file system object, or any other error | 355 * If the path does not point to a file system object, or any other error |
328 * occurs in looking up the path, NOT_FOUND is returned. The only | 356 * occurs in looking up the path, NOT_FOUND is returned. The only |
329 * error or exception that may be put on the returned future is ArgumentError, | 357 * error or exception that may be put on the returned future is ArgumentError, |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
383 (_getTypeSync(path, true) == FileSystemEntityType.FILE._type); | 411 (_getTypeSync(path, true) == FileSystemEntityType.FILE._type); |
384 | 412 |
385 /** | 413 /** |
386 * Synchronously checks if typeSync(path) returns | 414 * Synchronously checks if typeSync(path) returns |
387 * FileSystemEntityType.DIRECTORY. | 415 * FileSystemEntityType.DIRECTORY. |
388 */ | 416 */ |
389 static bool isDirectorySync(String path) => | 417 static bool isDirectorySync(String path) => |
390 (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY._type); | 418 (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY._type); |
391 | 419 |
392 | 420 |
393 static _throwIfError(Object result, String msg) { | 421 static _throwIfError(Object result, String msg, [String path]) { |
394 if (result is OSError) { | 422 if (result is OSError) { |
395 throw new FileException(msg, result); | 423 throw new FileException(msg, result, path); |
396 } else if (result is ArgumentError) { | 424 } else if (result is ArgumentError) { |
397 throw result; | 425 throw result; |
398 } | 426 } |
399 } | 427 } |
400 } | 428 |
| 429 static String _trimTrailingPathSeparators(String path) { |
| 430 // Don't handle argument errors here. |
| 431 if (path is! String) return path; |
| 432 if (Platform.operatingSystem == 'windows') { |
| 433 while (path.length > 1 && |
| 434 (path.endsWith(Platform.pathSeparator) || |
| 435 path.endsWith('/'))) { |
| 436 path = path.substring(0, path.length - 1); |
| 437 } |
| 438 } else { |
| 439 while (path.length > 1 && path.endsWith(Platform.pathSeparator)) { |
| 440 path = path.substring(0, path.length - 1); |
| 441 } |
| 442 } |
| 443 return path; |
| 444 } |
| 445 } |
| 446 |
| 447 |
| 448 /** |
| 449 * Base event class emitted by FileSystemWatcher. |
| 450 */ |
| 451 class FileSystemEvent { |
| 452 static const int CREATE = 1 << 0; |
| 453 static const int MODIFY = 1 << 1; |
| 454 static const int DELETE = 1 << 2; |
| 455 static const int MOVE = 1 << 3; |
| 456 static const int ALL = CREATE | MODIFY | DELETE | MOVE; |
| 457 |
| 458 static const int _MODIFY_ATTRIBUTES = 1 << 4; |
| 459 |
| 460 /** |
| 461 * The type of event. See [FileSystemEvent] for a list of events. |
| 462 */ |
| 463 final int type; |
| 464 |
| 465 /** |
| 466 * The path that triggered the event. Depending on the platform and the |
| 467 * FileSystemEntity, the path may be relative. |
| 468 */ |
| 469 final String path; |
| 470 |
| 471 FileSystemEvent._(this.type, this.path); |
| 472 } |
| 473 |
| 474 |
| 475 /** |
| 476 * File system event for newly created file system objects. |
| 477 */ |
| 478 class FileSystemCreateEvent extends FileSystemEvent { |
| 479 FileSystemCreateEvent._(path) |
| 480 : super._(FileSystemEvent.CREATE, path); |
| 481 |
| 482 String toString() => "FileSystemCreateEvent('$path')"; |
| 483 } |
| 484 |
| 485 |
| 486 /** |
| 487 * File system event for modifications of file system objects. |
| 488 */ |
| 489 class FileSystemModifyEvent extends FileSystemEvent { |
| 490 /** |
| 491 * If the content was changed and not only the attributes, [contentChanged] |
| 492 * is `true`. |
| 493 */ |
| 494 final bool contentChanged; |
| 495 |
| 496 FileSystemModifyEvent._(path, this.contentChanged) |
| 497 : super._(FileSystemEvent.MODIFY, path); |
| 498 |
| 499 String toString() => |
| 500 "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; |
| 501 } |
| 502 |
| 503 |
| 504 /** |
| 505 * File system event for deletion of file system objects. |
| 506 */ |
| 507 class FileSystemDeleteEvent extends FileSystemEvent { |
| 508 FileSystemDeleteEvent._(path) |
| 509 : super._(FileSystemEvent.DELETE, path); |
| 510 |
| 511 String toString() => "FileSystemDeleteEvent('$path')"; |
| 512 } |
| 513 |
| 514 |
| 515 /** |
| 516 * File system event for moving of file system objects. |
| 517 */ |
| 518 class FileSystemMoveEvent extends FileSystemEvent { |
| 519 /** |
| 520 * If the underlaying implementation is able to identify the destination of |
| 521 * the moved file, [destination] will be set. Otherwise, it will be `null`. |
| 522 */ |
| 523 final String destination; |
| 524 |
| 525 FileSystemMoveEvent._(path, this.destination) |
| 526 : super._(FileSystemEvent.MOVE, path); |
| 527 |
| 528 String toString() { |
| 529 var buffer = new StringBuffer(); |
| 530 buffer.write("FileSystemMoveEvent('$path'"); |
| 531 if (destination != null) buffer.write(", '$destination'"); |
| 532 buffer.write(')'); |
| 533 return buffer.toString(); |
| 534 } |
| 535 } |
| 536 |
| 537 |
| 538 class _FileSystemWatcher extends NativeFieldWrapperClass1 { |
| 539 final String _path; |
| 540 final int _events; |
| 541 final bool _recursive; |
| 542 |
| 543 StreamController _controller; |
| 544 StreamSubscription _subscription; |
| 545 |
| 546 _FileSystemWatcher(this._path, this._events, this._recursive) { |
| 547 _controller = new StreamController(onListen: _listen, onCancel: _cancel); |
| 548 } |
| 549 |
| 550 void _listen() { |
| 551 int socketId; |
| 552 try { |
| 553 socketId = _watchPath(_path, _events, identical(true, _recursive)); |
| 554 } catch (e) { |
| 555 throw new FileException( |
| 556 "Failed to watch path", |
| 557 _path, |
| 558 e); |
| 559 } |
| 560 var socket = new _RawSocket(new _NativeSocket.watch(socketId)); |
| 561 _subscription = socket.expand((event) { |
| 562 var events = []; |
| 563 var pair = {}; |
| 564 if (event == RawSocketEvent.READ) { |
| 565 String getPath(event) { |
| 566 var path = _path; |
| 567 if (event[2] != null) { |
| 568 path += Platform.pathSeparator; |
| 569 path += event[2]; |
| 570 } |
| 571 return path; |
| 572 } |
| 573 while (socket.available() > 0) { |
| 574 for (var event in _readEvents()) { |
| 575 if (event == null) continue; |
| 576 var path = getPath(event); |
| 577 if ((event[0] & FileSystemEvent.CREATE) != 0) { |
| 578 events.add(new FileSystemCreateEvent._(path)); |
| 579 } |
| 580 if ((event[0] & FileSystemEvent.MODIFY) != 0) { |
| 581 events.add(new FileSystemModifyEvent._(path, true)); |
| 582 } |
| 583 if ((event[0] & FileSystemEvent._MODIFY_ATTRIBUTES) != 0) { |
| 584 events.add(new FileSystemModifyEvent._(path, false)); |
| 585 } |
| 586 if ((event[0] & FileSystemEvent.MOVE) != 0) { |
| 587 int link = event[1]; |
| 588 if (link > 0) { |
| 589 if (pair.containsKey(link)) { |
| 590 events.add( |
| 591 new FileSystemMoveEvent._(getPath(pair[link]), path)); |
| 592 pair.remove(link); |
| 593 } else { |
| 594 pair[link] = event; |
| 595 } |
| 596 } else { |
| 597 events.add(new FileSystemMoveEvent._(path, null)); |
| 598 } |
| 599 } |
| 600 if ((event[0] & FileSystemEvent.DELETE) != 0) { |
| 601 events.add(new FileSystemDeleteEvent._(path)); |
| 602 } |
| 603 } |
| 604 } |
| 605 for (var event in pair.values) { |
| 606 events.add(new FileSystemMoveEvent._(getPath(event), null)); |
| 607 } |
| 608 } else if (event == RawSocketEvent.CLOSED) { |
| 609 } else if (event == RawSocketEvent.READ_CLOSED) { |
| 610 } else { |
| 611 assert(false); |
| 612 } |
| 613 return events; |
| 614 }) |
| 615 .where((event) => (event.type & _events) != 0) |
| 616 .listen(_controller.add, onDone: _cancel); |
| 617 } |
| 618 |
| 619 void _cancel() { |
| 620 _unwatchPath(); |
| 621 if (_subscription != null) { |
| 622 _subscription.cancel(); |
| 623 } |
| 624 } |
| 625 |
| 626 Stream<FileSystemEvent> get stream => _controller.stream; |
| 627 |
| 628 external int _watchPath(String path, int events, bool recursive); |
| 629 external void _unwatchPath(); |
| 630 external List _readEvents(); |
| 631 } |
OLD | NEW |