| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // part of "common_patch.dart"; |
| |
| @patch |
| class _File { |
| @patch |
| @pragma("vm:external-name", "File_Exists") |
| external static _exists(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_Create") |
| external static _create(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_CreateLink") |
| external static _createLink( |
| _Namespace namespace, Uint8List rawPath, String target); |
| @patch |
| @pragma("vm:external-name", "File_LinkTarget") |
| external static _linkTarget(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_Delete") |
| external static _deleteNative(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_DeleteLink") |
| external static _deleteLinkNative(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_Rename") |
| external static _rename( |
| _Namespace namespace, Uint8List oldPath, String newPath); |
| @patch |
| @pragma("vm:external-name", "File_RenameLink") |
| external static _renameLink( |
| _Namespace namespace, Uint8List oldPath, String newPath); |
| @patch |
| @pragma("vm:external-name", "File_Copy") |
| external static _copy( |
| _Namespace namespace, Uint8List oldPath, String newPath); |
| @patch |
| @pragma("vm:external-name", "File_LengthFromPath") |
| external static _lengthFromPath(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_LastModified") |
| external static _lastModified(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_SetLastModified") |
| external static _setLastModified( |
| _Namespace namespace, Uint8List rawPath, int millis); |
| @patch |
| @pragma("vm:external-name", "File_LastAccessed") |
| external static _lastAccessed(_Namespace namespace, Uint8List rawPath); |
| @patch |
| @pragma("vm:external-name", "File_SetLastAccessed") |
| external static _setLastAccessed( |
| _Namespace namespace, Uint8List rawPath, int millis); |
| @patch |
| @pragma("vm:external-name", "File_Open") |
| external static _open(_Namespace namespace, Uint8List rawPath, int mode); |
| @patch |
| @pragma("vm:external-name", "File_OpenStdio") |
| external static int _openStdio(int fd); |
| } |
| |
| @patch |
| class _RandomAccessFileOps { |
| @patch |
| factory _RandomAccessFileOps(int pointer) => |
| new _RandomAccessFileOpsImpl(pointer); |
| } |
| |
| @pragma("vm:entry-point") |
| class _RandomAccessFileOpsImpl extends NativeFieldWrapperClass1 |
| implements _RandomAccessFileOps { |
| _RandomAccessFileOpsImpl._(); |
| |
| factory _RandomAccessFileOpsImpl(int pointer) => |
| new _RandomAccessFileOpsImpl._().._setPointer(pointer); |
| |
| @pragma("vm:external-name", "File_SetPointer") |
| external void _setPointer(int pointer); |
| @pragma("vm:external-name", "File_GetPointer") |
| external int getPointer(); |
| @pragma("vm:external-name", "File_GetFD") |
| external int get fd; |
| @pragma("vm:external-name", "File_Close") |
| external int close(); |
| @pragma("vm:external-name", "File_ReadByte") |
| external readByte(); |
| @pragma("vm:external-name", "File_Read") |
| external read(int bytes); |
| @pragma("vm:external-name", "File_ReadInto") |
| external readInto(List<int> buffer, int start, int? end); |
| @pragma("vm:external-name", "File_WriteByte") |
| external writeByte(int value); |
| @pragma("vm:external-name", "File_WriteFrom") |
| external writeFrom(List<int> buffer, int start, int? end); |
| @pragma("vm:external-name", "File_Position") |
| external position(); |
| @pragma("vm:external-name", "File_SetPosition") |
| external setPosition(int position); |
| @pragma("vm:external-name", "File_Truncate") |
| external truncate(int length); |
| @pragma("vm:external-name", "File_Length") |
| external length(); |
| @pragma("vm:external-name", "File_Flush") |
| external flush(); |
| @pragma("vm:external-name", "File_Lock") |
| external lock(int lock, int start, int end); |
| } |
| |
| class _WatcherPath { |
| final int pathId; |
| final String path; |
| final int events; |
| int count = 0; |
| _WatcherPath(this.pathId, this.path, this.events); |
| } |
| |
| @patch |
| abstract class _FileSystemWatcher { |
| void _pathWatchedEnd(); |
| |
| static int? _id; |
| static final Map<int, _WatcherPath> _idMap = {}; |
| |
| final String _path; |
| final int _events; |
| final bool _recursive; |
| |
| _WatcherPath? _watcherPath; |
| |
| final StreamController<FileSystemEvent> _broadcastController = |
| new StreamController<FileSystemEvent>.broadcast(); |
| |
| @patch |
| static Stream<FileSystemEvent> _watch( |
| String path, int events, bool recursive) { |
| if (Platform.isLinux || Platform.isAndroid) { |
| return new _InotifyFileSystemWatcher(path, events, recursive)._stream; |
| } |
| if (Platform.isWindows) { |
| return new _Win32FileSystemWatcher(path, events, recursive)._stream; |
| } |
| if (Platform.isMacOS) { |
| return new _FSEventStreamFileSystemWatcher(path, events, recursive) |
| ._stream; |
| } |
| throw new FileSystemException( |
| "File system watching is not supported on this platform"); |
| } |
| |
| _FileSystemWatcher._(this._path, this._events, this._recursive) { |
| if (!isSupported) { |
| throw new FileSystemException( |
| "File system watching is not supported on this platform", _path); |
| } |
| _broadcastController |
| ..onListen = _listen |
| ..onCancel = _cancel; |
| } |
| |
| Stream<FileSystemEvent> get _stream => _broadcastController.stream; |
| |
| void _listen() { |
| if (_id == null) { |
| try { |
| _id = _initWatcher(); |
| _newWatcher(); |
| } on dynamic catch (e) { |
| _broadcastController.addError(new FileSystemException( |
| "Failed to initialize file system entity watcher", null, e)); |
| _broadcastController.close(); |
| return; |
| } |
| } |
| var pathId; |
| try { |
| pathId = |
| _watchPath(_id!, _Namespace._namespace, _path, _events, _recursive); |
| } on dynamic catch (e) { |
| _broadcastController |
| .addError(new FileSystemException("Failed to watch path", _path, e)); |
| _broadcastController.close(); |
| return; |
| } |
| if (!_idMap.containsKey(pathId)) { |
| _idMap[pathId] = new _WatcherPath(pathId, _path, _events); |
| } |
| _watcherPath = _idMap[pathId]; |
| _watcherPath!.count++; |
| _pathWatched().pipe(_broadcastController); |
| } |
| |
| void _cancel() { |
| final watcherPath = _watcherPath; |
| if (watcherPath != null) { |
| assert(watcherPath.count > 0); |
| watcherPath.count--; |
| if (watcherPath.count == 0) { |
| var pathId = watcherPath.pathId; |
| // DirectoryWatchHandle(aka pathId) might be closed already initiated |
| // by issueReadEvent for example. When that happens, appropriate closeEvent |
| // will arrive to us and we will remove this pathId from _idMap. If that |
| // happens we should not try to close it again as pathId is no |
| // longer usable(the memory it points to might be released) |
| if (_idMap.containsKey(pathId)) { |
| _unwatchPath(_id!, pathId); |
| _pathWatchedEnd(); |
| _idMap.remove(pathId); |
| } |
| } |
| _watcherPath = null; |
| } |
| final id = _id; |
| if (_idMap.isEmpty && id != null) { |
| _closeWatcher(id); |
| _doneWatcher(); |
| _id = null; |
| } |
| } |
| |
| // Called when (and after) a new watcher instance is created and available. |
| void _newWatcher() {} |
| // Called when a watcher is no longer needed. |
| void _doneWatcher() {} |
| // Called when a new path is being watched. |
| Stream _pathWatched(); |
| // Called when a path is no longer being watched. |
| void _donePathWatched() {} |
| |
| static _WatcherPath _pathFromPathId(int pathId) { |
| return _idMap[pathId]!; |
| } |
| |
| static Stream _listenOnSocket(int socketId, int id, int pathId) { |
| final nativeSocket = _NativeSocket.watch(socketId); |
| final rawSocket = _RawSocket(nativeSocket); |
| return rawSocket.expand((event) { |
| var stops = []; |
| var events = []; |
| var pair = {}; |
| if (event == RawSocketEvent.read) { |
| String getPath(event) { |
| var path = _pathFromPathId(event[4]).path; |
| if (event[2] != null && event[2].isNotEmpty) { |
| path += Platform.pathSeparator; |
| path += event[2]; |
| } |
| return path; |
| } |
| |
| bool getIsDir(event) { |
| if (Platform.isWindows) { |
| // Windows does not get 'isDir' as part of the event. |
| // Links should also be skipped. |
| return FileSystemEntity.isDirectorySync(getPath(event)) && |
| !FileSystemEntity.isLinkSync(getPath(event)); |
| } |
| return (event[0] & FileSystemEvent._isDir) != 0; |
| } |
| |
| void add(id, event) { |
| if ((event.type & _pathFromPathId(id).events) == 0) return; |
| events.add([id, event]); |
| } |
| |
| void rewriteMove(event, isDir) { |
| if (event[3]) { |
| add(event[4], new FileSystemCreateEvent._(getPath(event), isDir)); |
| } else { |
| add(event[4], new FileSystemDeleteEvent._(getPath(event), false)); |
| } |
| } |
| |
| int eventCount; |
| do { |
| eventCount = 0; |
| for (var event in _readEvents(id, pathId)) { |
| if (event == null) continue; |
| eventCount++; |
| int pathId = event[4]; |
| if (!_idMap.containsKey(pathId)) { |
| // Path is no longer being wathed. |
| continue; |
| } |
| bool isDir = getIsDir(event); |
| var path = getPath(event); |
| if ((event[0] & FileSystemEvent.create) != 0) { |
| add(event[4], new FileSystemCreateEvent._(path, isDir)); |
| } |
| if ((event[0] & FileSystemEvent.modify) != 0) { |
| add(event[4], new FileSystemModifyEvent._(path, isDir, true)); |
| } |
| if ((event[0] & FileSystemEvent._modifyAttributes) != 0) { |
| add(event[4], new FileSystemModifyEvent._(path, isDir, false)); |
| } |
| if ((event[0] & FileSystemEvent.move) != 0) { |
| int link = event[1]; |
| if (link > 0) { |
| pair.putIfAbsent(pathId, () => {}); |
| if (pair[pathId].containsKey(link)) { |
| add( |
| event[4], |
| new FileSystemMoveEvent._( |
| getPath(pair[pathId][link]), isDir, path)); |
| pair[pathId].remove(link); |
| } else { |
| pair[pathId][link] = event; |
| } |
| } else { |
| rewriteMove(event, isDir); |
| } |
| } |
| if ((event[0] & FileSystemEvent.delete) != 0) { |
| add(event[4], new FileSystemDeleteEvent._(path, false)); |
| } |
| if ((event[0] & FileSystemEvent._deleteSelf) != 0) { |
| add(event[4], new FileSystemDeleteEvent._(path, false)); |
| // Signal done event. |
| stops.add([event[4], null]); |
| } |
| } |
| } while (eventCount > 0); |
| // Be sure to clear this manually, as the sockets are not read through |
| // the _NativeSocket interface. |
| nativeSocket.available = 0; |
| for (var map in pair.values) { |
| for (var event in map.values) { |
| rewriteMove(event, getIsDir(event)); |
| } |
| } |
| } else if (event == RawSocketEvent.closed) { |
| // After this point we should not try to do anything with pathId as |
| // the handle it represented is closed and gone now. |
| if (_idMap.containsKey(pathId)) { |
| _idMap.remove(pathId); |
| if (_idMap.isEmpty && _id != null) { |
| _closeWatcher(_id!); |
| _id = null; |
| } |
| } |
| } else if (event == RawSocketEvent.readClosed) { |
| // If Directory watcher buffer overflows, it will send an readClosed event. |
| // Normal closing will cancel stream subscription so that path is |
| // no longer being watched, not present in _idMap. |
| if (_idMap.containsKey(pathId)) { |
| var path = _pathFromPathId(pathId).path; |
| _idMap.remove(pathId); |
| if (_idMap.isEmpty && _id != null) { |
| _closeWatcher(_id!); |
| _id = null; |
| } |
| throw FileSystemException( |
| 'Directory watcher closed unexpectedly', path); |
| } |
| } else { |
| assert(false); |
| } |
| events.addAll(stops); |
| return events; |
| }); |
| } |
| |
| @patch |
| @pragma("vm:external-name", "FileSystemWatcher_IsSupported") |
| external static bool get isSupported; |
| |
| @pragma("vm:external-name", "FileSystemWatcher_InitWatcher") |
| external static int _initWatcher(); |
| @pragma("vm:external-name", "FileSystemWatcher_CloseWatcher") |
| external static void _closeWatcher(int id); |
| |
| @pragma("vm:external-name", "FileSystemWatcher_WatchPath") |
| external static int _watchPath( |
| int id, _Namespace namespace, String path, int events, bool recursive); |
| @pragma("vm:external-name", "FileSystemWatcher_UnwatchPath") |
| external static void _unwatchPath(int id, int path_id); |
| @pragma("vm:external-name", "FileSystemWatcher_ReadEvents") |
| external static List _readEvents(int id, int path_id); |
| @pragma("vm:external-name", "FileSystemWatcher_GetSocketId") |
| external static int _getSocketId(int id, int path_id); |
| } |
| |
| class _InotifyFileSystemWatcher extends _FileSystemWatcher { |
| static final Map<int, StreamController> _idMap = {}; |
| static late StreamSubscription _subscription; |
| |
| _InotifyFileSystemWatcher(path, events, recursive) |
| : super._(path, events, recursive); |
| |
| void _newWatcher() { |
| int id = _FileSystemWatcher._id!; |
| _subscription = |
| _FileSystemWatcher._listenOnSocket(id, id, 0).listen((event) { |
| if (_idMap.containsKey(event[0])) { |
| if (event[1] != null) { |
| _idMap[event[0]]!.add(event[1]); |
| } else { |
| _idMap[event[0]]!.close(); |
| } |
| } |
| }); |
| } |
| |
| void _doneWatcher() { |
| _subscription.cancel(); |
| } |
| |
| Stream _pathWatched() { |
| var pathId = _watcherPath!.pathId; |
| if (!_idMap.containsKey(pathId)) { |
| _idMap[pathId] = new StreamController<FileSystemEvent>.broadcast(); |
| } |
| return _idMap[pathId]!.stream; |
| } |
| |
| void _pathWatchedEnd() { |
| var pathId = _watcherPath!.pathId; |
| if (!_idMap.containsKey(pathId)) return; |
| _idMap[pathId]!.close(); |
| _idMap.remove(pathId); |
| } |
| } |
| |
| class _Win32FileSystemWatcher extends _FileSystemWatcher { |
| late StreamSubscription _subscription; |
| late StreamController _controller; |
| |
| _Win32FileSystemWatcher(path, events, recursive) |
| : super._(path, events, recursive); |
| |
| Stream _pathWatched() { |
| var pathId = _watcherPath!.pathId; |
| _controller = new StreamController<FileSystemEvent>(); |
| _subscription = |
| _FileSystemWatcher._listenOnSocket(pathId, 0, pathId).listen((event) { |
| assert(event[0] == pathId); |
| if (event[1] != null) { |
| _controller.add(event[1]); |
| } else { |
| _controller.close(); |
| } |
| }); |
| return _controller.stream; |
| } |
| |
| void _pathWatchedEnd() { |
| _subscription.cancel(); |
| _controller.close(); |
| } |
| } |
| |
| class _FSEventStreamFileSystemWatcher extends _FileSystemWatcher { |
| late StreamSubscription _subscription; |
| late StreamController _controller; |
| |
| _FSEventStreamFileSystemWatcher(path, events, recursive) |
| : super._(path, events, recursive); |
| |
| Stream _pathWatched() { |
| var pathId = _watcherPath!.pathId; |
| var socketId = _FileSystemWatcher._getSocketId(0, pathId); |
| _controller = new StreamController<FileSystemEvent>(); |
| _subscription = |
| _FileSystemWatcher._listenOnSocket(socketId, 0, pathId).listen((event) { |
| if (event[1] != null) { |
| _controller.add(event[1]); |
| } else { |
| _controller.close(); |
| } |
| }); |
| return _controller.stream; |
| } |
| |
| void _pathWatchedEnd() { |
| _subscription.cancel(); |
| _controller.close(); |
| } |
| } |
| |
| @pragma("vm:entry-point", "call") |
| Uint8List _makeUint8ListView(Uint8List source, int offsetInBytes, int length) { |
| return new Uint8List.view(source.buffer, offsetInBytes, length); |
| } |