-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement the ability to register custom watcher implementations
- Loading branch information
Showing
5 changed files
with
210 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import '../watcher.dart'; | ||
|
||
/// Defines a way to create a custom watcher instead of the default ones. | ||
/// | ||
/// This will be used when a [DirectoryWatcher] or [FileWatcher] would be | ||
/// created and will take precedence over the default ones. | ||
abstract class CustomWatcherFactory { | ||
/// Uniquely identify this watcher. | ||
String get id; | ||
|
||
/// Tries to create a [DirectoryWatcher] for the provided path. | ||
/// | ||
/// Should return `null` if the path is not supported by this factory. | ||
DirectoryWatcher createDirectoryWatcher(String path, {Duration pollingDelay}); | ||
|
||
/// Tries to create a [FileWatcher] for the provided path. | ||
/// | ||
/// Should return `null` if the path is not supported by this factory. | ||
FileWatcher createFileWatcher(String path, {Duration pollingDelay}); | ||
} | ||
|
||
/// Registers a custom watcher. | ||
/// | ||
/// It's only allowed to register a watcher once per [id]. The [supportsPath] | ||
/// will be called to determine if the [createWatcher] should be used instead of | ||
/// the built-in watchers. | ||
/// | ||
/// Note that we will try [CustomWatcherFactory] one by one in the order they | ||
/// were registered. | ||
void registerCustomWatcherFactory(CustomWatcherFactory customFactory) { | ||
if (_customWatcherFactories.containsKey(customFactory.id)) { | ||
throw ArgumentError('A custom watcher with id `${customFactory.id}` ' | ||
'has already been registered'); | ||
} | ||
_customWatcherFactories[customFactory.id] = customFactory; | ||
} | ||
|
||
/// Unregisters a custom watcher and returns it (returns `null` if it was never | ||
/// registered). | ||
CustomWatcherFactory unregisterCustomWatcherFactory(String id) => | ||
_customWatcherFactories.remove(id); | ||
|
||
Iterable<CustomWatcherFactory> get customWatcherFactories => | ||
_customWatcherFactories.values; | ||
|
||
final _customWatcherFactories = <String, CustomWatcherFactory>{}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import 'dart:async'; | ||
import 'dart:io'; | ||
|
||
import 'package:test/test.dart'; | ||
import 'package:watcher/watcher.dart'; | ||
|
||
void main() { | ||
_MemFs memFs; | ||
|
||
setUp(() { | ||
memFs = _MemFs(); | ||
registerCustomWatcherFactory(_MemFsWatcherFactory(memFs)); | ||
}); | ||
|
||
tearDown(() async { | ||
await memFs.clear(); | ||
unregisterCustomWatcherFactory('MemFs'); | ||
}); | ||
|
||
test('notifes for files', () async { | ||
var watcher = FileWatcher('file.txt'); | ||
|
||
var completer = Completer<WatchEvent>(); | ||
watcher.events.listen((event) => completer.complete(event)); | ||
await watcher.ready; | ||
memFs.add('file.txt'); | ||
var event = await completer.future; | ||
|
||
expect(event.type, ChangeType.ADD); | ||
expect(event.path, 'file.txt'); | ||
}); | ||
|
||
test('notifes for directories', () async { | ||
var watcher = DirectoryWatcher('dir'); | ||
|
||
var completer = Completer<WatchEvent>(); | ||
watcher.events.listen((event) => completer.complete(event)); | ||
await watcher.ready; | ||
memFs.add('dir'); | ||
var event = await completer.future; | ||
|
||
expect(event.type, ChangeType.ADD); | ||
expect(event.path, 'dir'); | ||
}); | ||
|
||
test('unregister works', () async { | ||
var memFactory = _MemFsWatcherFactory(memFs); | ||
unregisterCustomWatcherFactory(memFactory.id); | ||
|
||
var completer = Completer<dynamic>(); | ||
var watcher = FileWatcher('file.txt'); | ||
watcher.events.listen((e) {}, onError: (e) => completer.complete(e)); | ||
await watcher.ready; | ||
memFs.add('file.txt'); | ||
var result = await completer.future; | ||
|
||
expect(result, isA<FileSystemException>()); | ||
}); | ||
|
||
test('registering twice throws', () async { | ||
expect(() => registerCustomWatcherFactory(_MemFsWatcherFactory(memFs)), | ||
throwsA(isA<ArgumentError>())); | ||
}); | ||
} | ||
|
||
class _MemFs { | ||
final _streams = <String, Set<StreamController<WatchEvent>>>{}; | ||
|
||
StreamController<WatchEvent> watchStream(String path) { | ||
var controller = StreamController<WatchEvent>(); | ||
_streams.putIfAbsent(path, () => {}).add(controller); | ||
return controller; | ||
} | ||
|
||
void add(String path) { | ||
var controllers = _streams[path]; | ||
if (controllers != null) { | ||
for (var controller in controllers) { | ||
controller.add(WatchEvent(ChangeType.ADD, path)); | ||
} | ||
} | ||
} | ||
|
||
void remove(String path) { | ||
var controllers = _streams[path]; | ||
if (controllers != null) { | ||
for (var controller in controllers) { | ||
controller.add(WatchEvent(ChangeType.REMOVE, path)); | ||
} | ||
} | ||
} | ||
|
||
void clear() async { | ||
for (var controllers in _streams.values) { | ||
for (var controller in controllers) { | ||
await controller.close(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
class _MemFsWatcher implements FileWatcher, DirectoryWatcher, Watcher { | ||
final String _path; | ||
final StreamController<WatchEvent> _controller; | ||
|
||
_MemFsWatcher(this._path, this._controller); | ||
|
||
@override | ||
String get path => _path; | ||
|
||
@override | ||
String get directory => throw UnsupportedError('directory is not supported'); | ||
|
||
@override | ||
Stream<WatchEvent> get events => _controller.stream; | ||
|
||
@override | ||
bool get isReady => true; | ||
|
||
@override | ||
Future<void> get ready async {} | ||
} | ||
|
||
class _MemFsWatcherFactory implements CustomWatcherFactory { | ||
final _MemFs _memFs; | ||
_MemFsWatcherFactory(this._memFs); | ||
|
||
@override | ||
String get id => 'MemFs'; | ||
|
||
@override | ||
DirectoryWatcher createDirectoryWatcher(String path, | ||
{Duration pollingDelay}) => | ||
_MemFsWatcher(path, _memFs.watchStream(path)); | ||
|
||
@override | ||
FileWatcher createFileWatcher(String path, {Duration pollingDelay}) => | ||
_MemFsWatcher(path, _memFs.watchStream(path)); | ||
} |