From a5f217fd516d530dfcb2e3c0cb7ca8e7686855b9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 23 Jun 2017 09:33:47 -0400 Subject: [PATCH] WIP work on rewatching child directories --- spec/native-watcher-registry-spec.js | 62 +++++++++++++++++++++++++++- src/native-watcher-registry.js | 35 ++++++++++------ 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/spec/native-watcher-registry-spec.js b/spec/native-watcher-registry-spec.js index 206aefc76..107e0efd3 100644 --- a/spec/native-watcher-registry-spec.js +++ b/spec/native-watcher-registry-spec.js @@ -212,8 +212,66 @@ describe('NativeWatcherRegistry', function () { expect(stoppedNode).toBe(true) }) - it("keeps a parent watcher that's still running") + it('reassigns new child watchers when a parent watcher is stopped', async function () { + const CHILD0 = new MockNative('child0') + const CHILD1 = new MockNative('child1') + const PARENT = new MockNative('parent') - it('reassigns new child watchers when a parent watcher is stopped') + const parentDir = path.join('parent') + const childDir0 = path.join(parentDir, 'child0') + const childDir1 = path.join(parentDir, 'child1') + + createNative = dir => { + if (dir === parentDir) { + return PARENT + } else if (dir === childDir0) { + return CHILD0 + } else if (dir === childDir1) { + return CHILD1 + } else { + throw new Error(`Unexpected directory ${dir}`) + } + } + + const parentWatcher = new MockWatcher(parentDir) + const childWatcher0 = new MockWatcher(childDir0) + const childWatcher1 = new MockWatcher(childDir1) + + await registry.attach(parentWatcher) + await Promise.all([ + registry.attach(childWatcher0), + registry.attach(childWatcher1) + ]) + + // All three watchers should share the parent watcher's native watcher. + expect(parentWatcher.native).toBe(PARENT) + expect(childWatcher0.native).toBe(PARENT) + expect(childWatcher1.native).toBe(PARENT) + + // Stopping the parent should detach and recreate the child watchers. + // (Here, they'll be the same watcher instances used before, because of the fake createNative implementation.) + PARENT.stop() + + expect(childWatcher0.native).toBe(CHILD0) + expect(childWatcher1.native).toBe(CHILD1) + + expect(registry.tree.lookup(['parent']).when({ + parent: () => false, + missing: () => false, + children: () => true + })).toBe(true) + + expect(registry.tree.lookup(['parent', 'child0']).when({ + parent: () => true, + missing: () => false, + children: () => false + })).toBe(true) + + expect(registry.tree.lookup(['parent', 'child1']).when({ + parent: () => true, + missing: () => false, + children: () => false + })).toBe(true) + }) }) }) diff --git a/src/native-watcher-registry.js b/src/native-watcher-registry.js index 95a5a6483..3d83bbf82 100644 --- a/src/native-watcher-registry.js +++ b/src/native-watcher-registry.js @@ -21,7 +21,7 @@ class RegistryNode { // exist. lookup (pathSegments) { if (pathSegments.length === 0) { - return new ChildrenResult(this.leaves()) + return new ChildrenResult(this.leaves([])) } const child = this.children[pathSegments[0]] @@ -85,13 +85,17 @@ class RegistryNode { return Object.keys(this.children).length === 0 ? null : this } - // Private: Discover all {RegistryWatcherNode} instances beneath this tree node. + // Private: Discover all {RegistryWatcherNode} instances beneath this tree node and the child paths + // that they are watching. // - // Returns: A possibly empty {Array} of {RegistryWatcherNode} instances that are the descendants of this node. - leaves () { + // * `prefix` {Array} of intermediate path segments to prepend to the resulting child paths. + // + // Returns: A possibly empty {Array} of `{node, path}` objects describing {RegistryWatcherNode} + // instances beneath this node. + leaves (prefix) { const results = [] for (const p of Object.keys(this.children)) { - results.push(...this.children[p].leaves()) + results.push(...this.children[p].leaves(prefix + [p])) } return results } @@ -104,8 +108,11 @@ class RegistryWatcherNode { // Private: Allocate a new node to track a {NativeWatcher}. // // * `nativeWatcher` An existing {NativeWatcher} instance. - constructor (nativeWatcher) { + // * `childPaths` {Array} of child directories that are currently the responsibility of this + // {NativeWatcher}, if any + constructor (nativeWatcher, childPaths) { this.nativeWatcher = nativeWatcher + this.childPaths = new Set(childPaths) } // Private: Accessor for the {NativeWatcher}. @@ -133,9 +140,11 @@ class RegistryWatcherNode { // Private: Discover this {RegistryWatcherNode} instance. // - // Returns: An {Array} containing this node. - leaves () { - return [this] + // * `prefix` {Array} of intermediate path segments to prepend to the resulting child paths. + // + // Returns: An {Array} containing a `{node, path}` object describing this node. + leaves (prefix) { + return [{node: this, path: prefix}] } } @@ -243,9 +252,9 @@ export default class NativeWatcherRegistry { const normalizedDirectory = await watcher.getNormalizedPathPromise() const pathSegments = normalizedDirectory.split(path.sep).filter(segment => segment.length > 0) - const attachToNew = () => { + const attachToNew = (childPaths) => { const native = this.createNative(normalizedDirectory) - const leaf = new RegistryWatcherNode(native) + const leaf = new RegistryWatcherNode(native, childPaths) this.tree = this.tree.insert(pathSegments, leaf) const sub = native.onWillStop(() => { @@ -268,7 +277,7 @@ export default class NativeWatcherRegistry { watcher.attachToNative(native, subpath) }, children: children => { - const newNative = attachToNew() + const newNative = attachToNew([]) // One or more NativeWatchers exist on child directories of the requested path. for (let i = 0; i < children.length; i++) { @@ -281,7 +290,7 @@ export default class NativeWatcherRegistry { childNative.stop() } }, - missing: attachToNew + missing: () => attachToNew([]) }) } }