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

Side by Side Diff: sdk/lib/io/file_system_entity.dart

Issue 19263003: Add FileSystemWatcher class to dart:io. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Update Mac OS X impl. Created 7 years, 3 months 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) 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
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.
Søren Gjesse 2013/09/03 12:19:58 Maybe extend this comment with information on that
Anders Johnsen 2013/09/03 12:43:32 Done.
335 */
336 Stream<FileSystemEvent> watch({int events: FileSystemEvent.ALL,
337 bool recursive: false})
338 => new _FileSystemWatcher(_trimTrailingPathSeparators(path),
339 events,
340 recursive).stream;
341
342
320 /** 343 /**
321 * Finds the type of file system object that a path points to. Returns 344 * Finds the type of file system object that a path points to. Returns
322 * a [:Future<FileSystemEntityType>:] that completes with the result. 345 * a [:Future<FileSystemEntityType>:] that completes with the result.
323 * 346 *
324 * [FileSystemEntityType] has the constant instances FILE, DIRECTORY, 347 * [FileSystemEntityType] has the constant instances FILE, DIRECTORY,
325 * LINK, and NOT_FOUND. [type] will return LINK only if the optional 348 * LINK, and NOT_FOUND. [type] will return LINK only if the optional
326 * named argument [followLinks] is false, and [path] points to a link. 349 * 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 350 * 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 351 * 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, 352 * 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
383 (_getTypeSync(path, true) == FileSystemEntityType.FILE._type); 406 (_getTypeSync(path, true) == FileSystemEntityType.FILE._type);
384 407
385 /** 408 /**
386 * Synchronously checks if typeSync(path) returns 409 * Synchronously checks if typeSync(path) returns
387 * FileSystemEntityType.DIRECTORY. 410 * FileSystemEntityType.DIRECTORY.
388 */ 411 */
389 static bool isDirectorySync(String path) => 412 static bool isDirectorySync(String path) =>
390 (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY._type); 413 (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY._type);
391 414
392 415
393 static _throwIfError(Object result, String msg) { 416 static _throwIfError(Object result, String msg, [String path]) {
394 if (result is OSError) { 417 if (result is OSError) {
395 throw new FileException(msg, result); 418 throw new FileException(msg, result, path);
396 } else if (result is ArgumentError) { 419 } else if (result is ArgumentError) {
397 throw result; 420 throw result;
398 } 421 }
399 } 422 }
400 } 423
424 static String _trimTrailingPathSeparators(String path) {
425 // Don't handle argument errors here.
426 if (path is! String) return path;
427 if (Platform.operatingSystem == 'windows') {
428 while (path.length > 1 &&
429 (path.endsWith(Platform.pathSeparator) ||
430 path.endsWith('/'))) {
431 path = path.substring(0, path.length - 1);
432 }
433 } else {
434 while (path.length > 1 && path.endsWith(Platform.pathSeparator)) {
435 path = path.substring(0, path.length - 1);
436 }
437 }
438 return path;
439 }
440 }
441
442
443 /**
444 * Base event class emitted by FileSystemWatcher.
445 */
446 class FileSystemEvent {
447 static const int CREATE = 1 << 0;
448 static const int MODIFY = 1 << 1;
449 static const int DELETE = 1 << 2;
450 static const int MOVE = 1 << 3;
451 static const int ALL = CREATE | MODIFY | DELETE | MOVE;
452
453 static const int _MODIFY_ATTRIBUTES = 1 << 4;
454
455 /**
456 * The type of event. See [FileSystemEvent] for a list of events.
457 */
458 final int type;
459
460 /**
461 * The path that triggered the event. Depending on the platform and the
462 * FileSystemEntity, the path may be relative.
463 */
464 final String path;
465
466 FileSystemEvent._(this.type, this.path);
467 }
468
469
470 /**
471 * File system event for newly created file system objects.
472 */
473 class FileSystemCreateEvent extends FileSystemEvent {
474 FileSystemCreateEvent._(path)
475 : super._(FileSystemEvent.CREATE, path);
476
477 String toString() => "FileSystemCreateEvent('$path')";
478 }
479
480
481 /**
482 * File system event for modifications of file system objects.
483 */
484 class FileSystemModifyEvent extends FileSystemEvent {
485 /**
486 * If the content was changed and not only the attributes, [contentChanged]
487 * is `true`.
488 */
489 final bool contentChanged;
490
491 FileSystemModifyEvent._(path, this.contentChanged)
492 : super._(FileSystemEvent.MODIFY, path);
493
494 String toString() =>
495 "FileSystemModifyEvent('$path', contentChanged=$contentChanged)";
496 }
497
498
499 /**
500 * File system event for deletion of file system objects.
501 */
502 class FileSystemDeleteEvent extends FileSystemEvent {
503 FileSystemDeleteEvent._(path)
504 : super._(FileSystemEvent.DELETE, path);
505
506 String toString() => "FileSystemDeleteEvent('$path')";
507 }
508
509
510 /**
511 * File system event for moving of file system objects.
512 */
513 class FileSystemMoveEvent extends FileSystemEvent {
514 /**
515 * If the underlaying implementation is able to identify the destination of
516 * the moved file, [destination] will be set. Otherwise, it will be `null`.
517 */
518 final String destination;
519
520 FileSystemMoveEvent._(path, this.destination)
521 : super._(FileSystemEvent.MOVE, path);
522
523 String toString() {
524 var buffer = new StringBuffer();
525 buffer.write("FileSystemMoveEvent('$path'");
526 if (destination != null) buffer.write(", '$destination'");
527 buffer.write(')');
528 return buffer.toString();
529 }
530 }
531
532
533 class _FileSystemWatcher extends NativeFieldWrapperClass1 {
534 final String _path;
535 final int _events;
536 final bool _recursive;
537
538 StreamController _controller;
539 StreamSubscription _subscription;
540
541 _FileSystemWatcher(this._path, this._events, this._recursive) {
542 _controller = new StreamController(onListen: _listen, onCancel: _cancel);
543 }
544
545 void _listen() {
546 int socketId;
547 try {
548 socketId = _watchPath(_path, _events, identical(true, _recursive));
549 } catch (e) {
550 throw new FileException(
551 "Failed to watch path",
552 _path,
553 e);
554 }
555 var socket = new _RawSocket(new _NativeSocket.watch(socketId));
556 _subscription = socket.expand((event) {
557 var events = [];
558 var pair = {};
559 if (event == RawSocketEvent.READ) {
560 String getPath(event) {
561 var path = _path;
562 if (event[2] != null) {
563 path += Platform.pathSeparator;
564 path += event[2];
565 }
566 return path;
567 }
568 while (socket.available() > 0) {
569 for (var event in _readEvents()) {
570 if (event == null) continue;
571 var path = getPath(event);
572 if ((event[0] & FileSystemEvent.CREATE) != 0) {
573 events.add(new FileSystemCreateEvent._(path));
574 }
575 if ((event[0] & FileSystemEvent.MODIFY) != 0) {
576 events.add(new FileSystemModifyEvent._(path, true));
577 }
578 if ((event[0] & FileSystemEvent._MODIFY_ATTRIBUTES) != 0) {
579 events.add(new FileSystemModifyEvent._(path, false));
580 }
581 if ((event[0] & FileSystemEvent.MOVE) != 0) {
582 int link = event[1];
583 if (link > 0) {
584 if (pair.containsKey(link)) {
585 events.add(
586 new FileSystemMoveEvent._(getPath(pair[link]), path));
587 pair.remove(link);
588 } else {
589 pair[link] = event;
590 }
591 } else {
592 events.add(new FileSystemMoveEvent._(path, null));
593 }
594 }
595 if ((event[0] & FileSystemEvent.DELETE) != 0) {
596 events.add(new FileSystemDeleteEvent._(path));
597 }
598 }
599 }
600 for (var event in pair.values) {
601 events.add(new FileSystemMoveEvent._(getPath(event), null));
602 }
603 } else if (event == RawSocketEvent.CLOSED) {
604 } else if (event == RawSocketEvent.READ_CLOSED) {
605 } else {
606 assert(false);
607 }
608 return events;
609 })
610 .where((event) => (event.type & _events) != 0)
611 .listen(_controller.add, onDone: _cancel);
Søren Gjesse 2013/09/03 12:19:58 Are we expecting onDone here without the subscript
Anders Johnsen 2013/09/03 12:43:32 I think this can be triggered in some weird cases
612 }
613
614 void _cancel() {
615 _controller.close();
Søren Gjesse 2013/09/03 12:19:58 Should canceling the subscription generate onDone?
Anders Johnsen 2013/09/03 12:43:32 It can't. This call is redundant, removing.
616 _unwatchPath();
617 if (_subscription != null) {
618 _subscription.cancel();
619 }
620 }
621
622 Stream<FileSystemEvent> get stream => _controller.stream;
623
624 external int _watchPath(String path, int events, bool recursive);
625 external void _unwatchPath();
626 external List _readEvents();
627 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698