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. | |
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 Loading... | |
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 } | |
OLD | NEW |