Skip to content

Commit

Permalink
Refactor watcher change handling to promises
Browse files Browse the repository at this point in the history
Summary:
Some lightweight modernising of `metro-file-map` watcher backends to allow the cleaner addition of an another async step (`readLink`), to follow.

(Re `graceful-fs` -> `fs` in `FSEventsWatcher` - this only affects the call to `lstat`, which is [wrapped by `graceful-fs`](https://l.facebook.com/l.php?u=https%3A%2F%2Fgithub.com%2Fisaacs%2Fnode-graceful-fs%2Fblob%2F1f19b0b467e4144260b397343cd60f37c5bdcfda%2Fpolyfills.js%23L292&h=AT1P8P4IV8T7USaqoeaSrADFV0TYg4fSes1ACxBJTf1xD0thUYMKLdwbJ9DsI-jp3yG-j52_EJE7rec2VOa0H_0k5Prll3CAT747p8IGaxvZrscX40tFFtsWIB4OAB695JLank07cGR4vH57l13fVuxo) *only* to fix a very old `guid`/`uid` reporting issue irrelevant to us - in short, `graceful-fs` isn't adding anything here).

Changelog: [Internal]

Reviewed By: jacdebug

Differential Revision: D41522808

fbshipit-source-id: 03819fc489710d3091505a83a3babfa5aceaf4a7
  • Loading branch information
robhogan authored and facebook-github-bot committed Dec 15, 2022
1 parent fa23b56 commit 2f42bc9
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 59 deletions.
57 changes: 30 additions & 27 deletions packages/metro-file-map/src/watchers/FSEventsWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
*/

import type {ChangeEventMetadata} from '../flow-types';
import type {Stats} from 'fs';
// $FlowFixMe[cannot-resolve-module] - Optional, Darwin only
import type {FSEvents} from 'fsevents';

// $FlowFixMe[untyped-import] - anymatch
import anymatch from 'anymatch';
import EventEmitter from 'events';
import * as fs from 'graceful-fs';
import {promises as fsPromises} from 'fs';
import * as path from 'path';
// $FlowFixMe[untyped-import] - walker
import walker from 'walker';
Expand Down Expand Up @@ -66,17 +67,17 @@ export default class FSEventsWatcher extends EventEmitter {
}

static _normalizeProxy(
callback: (normalizedPath: string, stats: fs.Stats) => void,
callback: (normalizedPath: string, stats: Stats) => void,
// $FlowFixMe[cannot-resolve-name]
): (filepath: string, stats: Stats) => void {
return (filepath: string, stats: fs.Stats): void =>
return (filepath: string, stats: Stats): void =>
callback(path.normalize(filepath), stats);
}

static _recReaddir(
dir: string,
dirCallback: (normalizedPath: string, stats: fs.Stats) => void,
fileCallback: (normalizedPath: string, stats: fs.Stats) => void,
dirCallback: (normalizedPath: string, stats: Stats) => void,
fileCallback: (normalizedPath: string, stats: Stats) => void,
// $FlowFixMe[unclear-type] Add types for callback
endCallback: Function,
// $FlowFixMe[unclear-type] Add types for callback
Expand Down Expand Up @@ -123,9 +124,11 @@ export default class FSEventsWatcher extends EventEmitter {

this.root = path.resolve(dir);

this.fsEventsWatchStopper = fsevents.watch(this.root, path =>
this._handleEvent(path),
);
this.fsEventsWatchStopper = fsevents.watch(this.root, path => {
this._handleEvent(path).catch(error => {
this.emit('error', error);
});
});

debug(`Watching ${this.root}`);

Expand Down Expand Up @@ -167,30 +170,17 @@ export default class FSEventsWatcher extends EventEmitter {
: this.dot || micromatch([relativePath], '**/*').length > 0;
}
_handleEvent(filepath: string) {
async _handleEvent(filepath: string) {
const relativePath = path.relative(this.root, filepath);
if (!this._isFileIncluded(relativePath)) {
return;
}
fs.lstat(filepath, (error, stat) => {
if (error && error.code !== 'ENOENT') {
this.emit('error', error);
return;
}
if (error) {
// Ignore files that aren't tracked and don't exist.
if (!this._tracked.has(filepath)) {
return;
}
this._emit(DELETE_EVENT, relativePath);
this._tracked.delete(filepath);
return;
}
try {
const stat = await fsPromises.lstat(filepath);
const type = typeFromStat(stat);
// Ignore files of an unrecognized type
if (!type) {
return;
}
Expand All @@ -206,7 +196,20 @@ export default class FSEventsWatcher extends EventEmitter {
this._tracked.add(filepath);
this._emit(ADD_EVENT, relativePath, metadata);
}
});
} catch (error) {
if (error?.code !== 'ENOENT') {
this.emit('error', error);
return;
}
// Ignore files that aren't tracked and don't exist.
if (!this._tracked.has(filepath)) {
return;
}
this._emit(DELETE_EVENT, relativePath);
this._tracked.delete(filepath);
}
}
/**
Expand Down
74 changes: 42 additions & 32 deletions packages/metro-file-map/src/watchers/NodeWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const fs = require('fs');
const platform = require('os').platform();
const path = require('path');

const fsPromises = fs.promises;

const CHANGE_EVENT = common.CHANGE_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;
const ADD_EVENT = common.ADD_EVENT;
Expand Down Expand Up @@ -243,29 +245,35 @@ module.exports = class NodeWatcher extends EventEmitter {
if (!file) {
this._detectChangedFile(dir, event, actualFile => {
if (actualFile) {
this._processChange(dir, event, actualFile);
this._processChange(dir, event, actualFile).catch(error =>
this.emit('error', error),
);
}
});
} else {
this._processChange(dir, event, path.normalize(file));
this._processChange(dir, event, path.normalize(file)).catch(error =>
this.emit('error', error),
);
}
}

/**
* Process changes.
*/
_processChange(dir: string, event: string, file: string) {
async _processChange(dir: string, event: string, file: string) {
const fullPath = path.join(dir, file);
const relativePath = path.join(path.relative(this.root, dir), file);

fs.lstat(fullPath, (error, stat) => {
if (error && error.code !== 'ENOENT') {
this.emit('error', error);
} else if (!error && stat.isDirectory()) {
const registered = this._registered(fullPath);

try {
const stat = await fsPromises.lstat(fullPath);
if (stat.isDirectory()) {
// win32 emits usless change events on dirs.
if (event === 'change') {
// win32 emits usless change events on dirs.
return;
}

if (
stat &&
common.isFileIncluded(
Expand Down Expand Up @@ -300,35 +308,37 @@ module.exports = class NodeWatcher extends EventEmitter {
this.ignored,
);
}
return;
} else {
const registered = this._registered(fullPath);
if (error && error.code === 'ENOENT') {
this._unregister(fullPath);
this._stopWatching(fullPath);
this._unregisterDir(fullPath);
if (registered) {
this._emitEvent(DELETE_EVENT, relativePath);
}
const type = common.typeFromStat(stat);
if (type == null) {
return;
}
const metadata = {
modifiedTime: stat.mtime.getTime(),
size: stat.size,
type,
};
if (registered) {
this._emitEvent(CHANGE_EVENT, relativePath, metadata);
} else {
const type = common.typeFromStat(stat);
if (type == null) {
return;
}
const metadata = {
modifiedTime: stat.mtime.getTime(),
size: stat.size,
type,
};
if (registered) {
this._emitEvent(CHANGE_EVENT, relativePath, metadata);
} else {
if (this._register(fullPath)) {
this._emitEvent(ADD_EVENT, relativePath, metadata);
}
if (this._register(fullPath)) {
this._emitEvent(ADD_EVENT, relativePath, metadata);
}
}
}
});
} catch (error) {
if (error?.code !== 'ENOENT') {
this.emit('error', error);
return;
}
this._unregister(fullPath);
this._stopWatching(fullPath);
this._unregisterDir(fullPath);
if (registered) {
this._emitEvent(DELETE_EVENT, relativePath);
}
}
}

/**
Expand Down

0 comments on commit 2f42bc9

Please sign in to comment.