From d8436bb63583470bfaba231ed93749468c474395 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 5 Jul 2019 18:52:40 -0400 Subject: [PATCH] Convert tools/fs/safe-watcher.js to TypeScript. This completes the TypeScript conversion of the tools/fs directory. :tada: --- tools/fs/optimistic.ts | 2 +- tools/fs/safe-watcher.ts | 85 ++++++++++++++----------- tools/fs/watch.ts | 2 +- tools/runners/run-app.js | 2 +- tools/tests/old/test-bundler-assets.js | 2 +- tools/tests/old/test-bundler-options.js | 2 +- 6 files changed, 54 insertions(+), 41 deletions(-) diff --git a/tools/fs/optimistic.ts b/tools/fs/optimistic.ts index 341719e1a7..2993987c33 100644 --- a/tools/fs/optimistic.ts +++ b/tools/fs/optimistic.ts @@ -2,7 +2,7 @@ import assert from "assert"; import { wrap, OptimisticWrapperFunction } from "optimism"; import ignore from "ignore"; import { Profile } from "../tool-env/profile.js"; -import { watch } from "./safe-watcher.js"; +import { watch } from "./safe-watcher"; import { sha1 } from "./watch"; import { pathSep, diff --git a/tools/fs/safe-watcher.ts b/tools/fs/safe-watcher.ts index a6b86b4411..327e1778ce 100644 --- a/tools/fs/safe-watcher.ts +++ b/tools/fs/safe-watcher.ts @@ -1,15 +1,15 @@ -import * as watchLibrary from "pathwatcher"; +import { FSWatcher, Stats } from "fs"; import { Profile } from "../tool-env/profile.js"; import { statOrNull, - pathDirname, pathResolve, convertToOSPath, - convertToStandardPath, watchFile, unwatchFile, } from "./files"; +const watchLibrary = require("pathwatcher"); + // Set METEOR_WATCH_FORCE_POLLING environment variable to a truthy value to // force the use of files.watchFile instead of watchLibrary.watch. var WATCHER_ENABLED = ! JSON.parse( @@ -26,16 +26,25 @@ if (process.env.METEOR_WATCH_PRIORITIZE_CHANGED && } var DEFAULT_POLLING_INTERVAL = - ~~process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 5000; + +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 5000); var NO_WATCHER_POLLING_INTERVAL = - ~~process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 500; + +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 500); // This may seems like a long time to wait before actually closing the // file watchers, but it's to our advantage if they survive restarts. const WATCHER_CLEANUP_DELAY_MS = 30000; -const entries = Object.create(null); +type EntryCallback = (event: string) => void; + +interface Entry { + callbacks: Set; + rewatch: () => void; + release: (callback: EntryCallback) => void; + close: () => void; +} + +const entries: Record = Object.create(null); // Pathwatcher complains (using console.error, ugh) if you try to watch // two files with the same stat.ino number but different paths, so we have @@ -47,7 +56,7 @@ const entriesByIno = new Map; // practice it should never grow large enough for that to matter. const changedPaths = new Set; -function hasPriority(absPath) { +function hasPriority(absPath: string) { // If we're not prioritizing changed files, then all files have // priority, which means they should be watched with native file // watchers if the platform supports them. If we are prioritizing @@ -57,7 +66,7 @@ function hasPriority(absPath) { : true; } -function acquireWatcher(absPath, callback) { +function acquireWatcher(absPath: string, callback: EntryCallback) { const entry = entries[absPath] || ( entries[absPath] = startNewWatcher(absPath)); @@ -73,11 +82,10 @@ function acquireWatcher(absPath, callback) { return entry; } -function startNewWatcher(absPath) { +function startNewWatcher(absPath: string): Entry { const stat = statOrNull(absPath); - const ino = stat && stat.ino; - if (ino > 0 && entriesByIno.has(ino)) { - const entry = entriesByIno.get(ino); + if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) { + const entry = entriesByIno.get(stat.ino); if (entries[absPath] === entry) { return entry; } @@ -87,16 +95,16 @@ function startNewWatcher(absPath) { if (watcher) { watcher.close(); watcher = null; - if (ino > 0) { - entriesByIno.delete(ino); + if (stat && stat.ino > 0) { + entriesByIno.delete(stat.ino); } } } - let lastWatcherEventTime = +new Date; - const callbacks = new Set; - let watcherCleanupTimer = null; - let watcher; + let lastWatcherEventTime = Date.now(); + const callbacks = new Set(); + let watcherCleanupTimer: ReturnType | null = null; + let watcher: FSWatcher | null = null; // Determines the polling interval to be used for the fs.watchFile-based // safety net that works on all platforms and file systems. @@ -125,7 +133,7 @@ function startNewWatcher(absPath) { return NO_WATCHER_POLLING_INTERVAL; } - function fire(event) { + function fire(event: string) { if (event !== "change") { // When we receive a "delete" or "rename" event, the watcher is // probably not going to generate any more notifications for this @@ -144,12 +152,12 @@ function startNewWatcher(absPath) { rewatch(); } - callbacks.forEach(cb => cb.call(this, event)); + callbacks.forEach(cb => cb(event)); } - function watchWrapper(event) { - lastWatcherEventTime = +new Date; - fire.call(this, event); + function watchWrapper(event: string) { + lastWatcherEventTime = Date.now(); + fire(event); // It's tempting to call unwatchFile(absPath, watchFileWrapper) here, // but previous watcher success is no guarantee of future watcher @@ -183,9 +191,7 @@ function startNewWatcher(absPath) { statWatch(absPath, getPollingInterval(), watchFileWrapper); } - function watchFileWrapper(...args) { - const [newStat, oldStat] = args; - + function watchFileWrapper(newStat: Stats, oldStat: Stats) { if (newStat.ino === 0 && oldStat.ino === 0 && +newStat.mtime === +oldStat.mtime) { @@ -196,8 +202,8 @@ function startNewWatcher(absPath) { // If a watcher event fired in the last polling interval, ignore // this event. - if (new Date - lastWatcherEventTime > getPollingInterval()) { - fire.call(this, "change"); + if (Date.now() - lastWatcherEventTime > getPollingInterval()) { + fire("change"); } } @@ -205,7 +211,7 @@ function startNewWatcher(absPath) { callbacks, rewatch, - release(callback) { + release(callback: EntryCallback) { if (! entries[absPath]) { return; } @@ -217,7 +223,10 @@ function startNewWatcher(absPath) { // Once there are no more callbacks in the Set, close both watchers // and nullify the shared data. - clearTimeout(watcherCleanupTimer); + if (watcherCleanupTimer) { + clearTimeout(watcherCleanupTimer); + } + watcherCleanupTimer = setTimeout(() => { if (callbacks.size > 0) { // If another callback was added while the timer was pending, we @@ -243,8 +252,8 @@ function startNewWatcher(absPath) { } }; - if (ino > 0) { - entriesByIno.set(ino, entry); + if (stat && stat.ino > 0) { + entriesByIno.set(stat.ino, entry); } return entry; @@ -261,7 +270,11 @@ export function closeAllWatchers() { const statWatchers = Object.create(null); -function statWatch(absPath, interval, callback) { +function statWatch( + absPath: string, + interval: number, + callback: (current: Stats, previous: Stats) => void, +) { const oldWatcher = statWatchers[absPath]; while (oldWatcher) { @@ -321,7 +334,7 @@ function statWatch(absPath, interval, callback) { return oldWatcher; } -function watchLibraryWatch(absPath, callback) { +function watchLibraryWatch(absPath: string, callback: EntryCallback) { if (WATCHER_ENABLED) { try { return watchLibrary.watch(convertToOSPath(absPath), callback); @@ -339,7 +352,7 @@ let suggestedRaisingWatchLimit = false; // This function is async so that archinfo.host() (which may call // utils.execFileSync) will run in a Fiber. -async function maybeSuggestRaisingWatchLimit(error) { +async function maybeSuggestRaisingWatchLimit(error: Error & { errno: number }) { var constants = require('constants'); var archinfo = require('../utils/archinfo.js'); if (! suggestedRaisingWatchLimit && @@ -373,7 +386,7 @@ async function maybeSuggestRaisingWatchLimit(error) { export const watch = Profile( "safeWatcher.watch", - (absPath, callback) => { + (absPath: string, callback: EntryCallback) => { const entry = acquireWatcher(absPath, callback); return { close() { diff --git a/tools/fs/watch.ts b/tools/fs/watch.ts index 4235fae2a6..2dd87e9af9 100644 --- a/tools/fs/watch.ts +++ b/tools/fs/watch.ts @@ -1,6 +1,6 @@ import { Stats, FSWatcher } from "fs"; import * as files from "./files"; -import * as safeWatcher from "./safe-watcher.js"; +import * as safeWatcher from "./safe-watcher"; import { createHash } from "crypto"; import { coalesce } from "../utils/func-utils.js"; import { Profile } from "../tool-env/profile.js"; diff --git a/tools/runners/run-app.js b/tools/runners/run-app.js index 6bb975b0c9..5c86eb5838 100644 --- a/tools/runners/run-app.js +++ b/tools/runners/run-app.js @@ -14,7 +14,7 @@ var Profile = require('../tool-env/profile.js').Profile; var release = require('../packaging/release.js'); import { pluginVersionsFromStarManifest } from '../cordova/index.js'; import { CordovaBuilder } from '../cordova/builder.js'; -import { closeAllWatchers } from "../fs/safe-watcher.js"; +import { closeAllWatchers } from "../fs/safe-watcher"; import { eachline } from "../utils/eachline.js"; import { loadIsopackage } from '../tool-env/isopackets.js'; diff --git a/tools/tests/old/test-bundler-assets.js b/tools/tests/old/test-bundler-assets.js index c4eabd3b92..0668ee6300 100644 --- a/tools/tests/old/test-bundler-assets.js +++ b/tools/tests/old/test-bundler-assets.js @@ -10,7 +10,7 @@ var release = require('../../packaging/release.js'); var catalog = require('../../packaging/catalog/catalog.js'); var buildmessage = require('../../utils/buildmessage.js'); var projectContextModule = require('../../project-context.js'); -var safeWatcher = require("../../fs/safe-watcher.js"); +var safeWatcher = require("../../fs/safe-watcher"); var lastTmpDir = null; var tmpDir = function () { diff --git a/tools/tests/old/test-bundler-options.js b/tools/tests/old/test-bundler-options.js index 267dbde6ec..c9aeaaf65e 100644 --- a/tools/tests/old/test-bundler-options.js +++ b/tools/tests/old/test-bundler-options.js @@ -9,7 +9,7 @@ var catalog = require('../../packaging/catalog/catalog.js'); var buildmessage = require('../../utils/buildmessage.js'); var isopackets = require('../../tool-env/isopackets.js'); var projectContextModule = require('../../project-context.js'); -var safeWatcher = require("../../fs/safe-watcher.js"); +var safeWatcher = require("../../fs/safe-watcher"); var lastTmpDir = null; var tmpDir = function () {