PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/metro-file-map/src/watchers

Просмотр файла: FallbackWatcher.js.flow

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 * @oncall react_native
 */

/**
 * Originally vendored from https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/node_watcher.js
 */

import type {
  ChangeEventMetadata,
  WatcherBackendChangeEvent,
} from '../flow-types';
import type {FSWatcher, Stats} from 'fs';

import {AbstractWatcher} from './AbstractWatcher';
import * as common from './common';
import fs from 'fs';
import os from 'os';
import path from 'path';
// $FlowFixMe[untyped-import] - Write libdefs for `walker`
import walker from 'walker';

const platform = os.platform();

const fsPromises = fs.promises;

const TOUCH_EVENT = common.TOUCH_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;

/**
 * This setting delays all events. It suppresses 'change' events that
 * immediately follow an 'add', and debounces successive 'change' events to
 * only emit the latest.
 */
const DEBOUNCE_MS = 100;

export default class FallbackWatcher extends AbstractWatcher {
  +_changeTimers: Map<string, TimeoutID> = new Map();
  +_dirRegistry: {
    [directory: string]: {[file: string]: true, __proto__: null},
    __proto__: null,
  } = Object.create(null);
  +watched: {[key: string]: FSWatcher, __proto__: null} = Object.create(null);

  async startWatching() {
    this._watchdir(this.root);

    await new Promise(resolve => {
      recReaddir(
        this.root,
        dir => {
          this._watchdir(dir);
        },
        filename => {
          this._register(filename, 'f');
        },
        symlink => {
          this._register(symlink, 'l');
        },
        () => {
          resolve();
        },
        this._checkedEmitError,
        this.ignored,
      );
    });
  }

  /**
   * Register files that matches our globs to know what to type of event to
   * emit in the future.
   *
   * Registry looks like the following:
   *
   *  dirRegister => Map {
   *    dirpath => Map {
   *       filename => true
   *    }
   *  }
   *
   *  Return false if ignored or already registered.
   */
  _register(filepath: string, type: ChangeEventMetadata['type']): boolean {
    const dir = path.dirname(filepath);
    const filename = path.basename(filepath);
    if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
      return false;
    }

    const relativePath = path.relative(this.root, filepath);
    if (
      this.doIgnore(relativePath) ||
      (type === 'f' &&
        !common.includedByGlob('f', this.globs, this.dot, relativePath))
    ) {
      return false;
    }

    if (!this._dirRegistry[dir]) {
      this._dirRegistry[dir] = Object.create(null);
    }

    this._dirRegistry[dir][filename] = true;

    return true;
  }

  /**
   * Removes a file from the registry.
   */
  _unregister(filepath: string) {
    const dir = path.dirname(filepath);
    if (this._dirRegistry[dir]) {
      const filename = path.basename(filepath);
      delete this._dirRegistry[dir][filename];
    }
  }

  /**
   * Removes a dir from the registry.
   */
  _unregisterDir(dirpath: string): void {
    if (this._dirRegistry[dirpath]) {
      delete this._dirRegistry[dirpath];
    }
  }

  /**
   * Checks if a file or directory exists in the registry.
   */
  _registered(fullpath: string): boolean {
    const dir = path.dirname(fullpath);
    return !!(
      this._dirRegistry[fullpath] ||
      (this._dirRegistry[dir] &&
        this._dirRegistry[dir][path.basename(fullpath)])
    );
  }

  /**
   * Emit "error" event if it's not an ignorable event
   */
  _checkedEmitError: (error: Error) => void = error => {
    if (!isIgnorableFileError(error)) {
      this.emitError(error);
    }
  };

  /**
   * Watch a directory.
   */
  _watchdir: string => boolean = (dir: string) => {
    if (this.watched[dir]) {
      return false;
    }
    const watcher = fs.watch(dir, {persistent: true}, (event, filename) =>
      this._normalizeChange(dir, event, filename),
    );
    this.watched[dir] = watcher;

    watcher.on('error', this._checkedEmitError);

    if (this.root !== dir) {
      this._register(dir, 'd');
    }
    return true;
  };

  /**
   * Stop watching a directory.
   */
  async _stopWatching(dir: string): Promise<void> {
    if (this.watched[dir]) {
      await new Promise(resolve => {
        this.watched[dir].once('close', () => process.nextTick(resolve));
        this.watched[dir].close();
        delete this.watched[dir];
      });
    }
  }

  /**
   * End watching.
   */
  async stopWatching(): Promise<void> {
    await super.stopWatching();
    const promises = Object.keys(this.watched).map(dir =>
      this._stopWatching(dir),
    );
    await Promise.all(promises);
  }

  /**
   * On some platforms, as pointed out on the fs docs (most likely just win32)
   * the file argument might be missing from the fs event. Try to detect what
   * change by detecting if something was deleted or the most recent file change.
   */
  _detectChangedFile(
    dir: string,
    event: string,
    callback: (file: string) => void,
  ) {
    if (!this._dirRegistry[dir]) {
      return;
    }

    let found = false;
    let closest: ?$ReadOnly<{file: string, mtime: Stats['mtime']}> = null;
    let c = 0;
    Object.keys(this._dirRegistry[dir]).forEach((file, i, arr) => {
      fs.lstat(path.join(dir, file), (error, stat) => {
        if (found) {
          return;
        }

        if (error) {
          if (isIgnorableFileError(error)) {
            found = true;
            callback(file);
          } else {
            this.emitError(error);
          }
        } else {
          if (closest == null || stat.mtime > closest.mtime) {
            closest = {file, mtime: stat.mtime};
          }
          if (arr.length === ++c) {
            callback(closest.file);
          }
        }
      });
    });
  }

  /**
   * Normalize fs events and pass it on to be processed.
   */
  _normalizeChange(dir: string, event: string, file: string) {
    if (!file) {
      this._detectChangedFile(dir, event, actualFile => {
        if (actualFile) {
          this._processChange(dir, event, actualFile).catch(error =>
            this.emitError(error),
          );
        }
      });
    } else {
      this._processChange(dir, event, path.normalize(file)).catch(error =>
        this.emitError(error),
      );
    }
  }

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

    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') {
          return;
        }

        if (
          this.doIgnore(relativePath) ||
          !common.includedByGlob('d', this.globs, this.dot, relativePath)
        ) {
          return;
        }
        recReaddir(
          path.resolve(this.root, relativePath),
          (dir, stats) => {
            if (this._watchdir(dir)) {
              this._emitEvent({
                event: TOUCH_EVENT,
                relativePath: path.relative(this.root, dir),
                metadata: {
                  modifiedTime: stats.mtime.getTime(),
                  size: stats.size,
                  type: 'd',
                },
              });
            }
          },
          (file, stats) => {
            if (this._register(file, 'f')) {
              this._emitEvent({
                event: TOUCH_EVENT,
                relativePath: path.relative(this.root, file),
                metadata: {
                  modifiedTime: stats.mtime.getTime(),
                  size: stats.size,
                  type: 'f',
                },
              });
            }
          },
          (symlink, stats) => {
            if (this._register(symlink, 'l')) {
              this.emitFileEvent({
                event: TOUCH_EVENT,
                relativePath: path.relative(this.root, symlink),
                metadata: {
                  modifiedTime: stats.mtime.getTime(),
                  size: stats.size,
                  type: 'l',
                },
              });
            }
          },
          function endCallback() {},
          this._checkedEmitError,
          this.ignored,
        );
      } else {
        const type = common.typeFromStat(stat);
        if (type == null) {
          return;
        }
        const metadata: ChangeEventMetadata = {
          modifiedTime: stat.mtime.getTime(),
          size: stat.size,
          type,
        };
        if (registered) {
          this._emitEvent({event: TOUCH_EVENT, relativePath, metadata});
        } else {
          if (this._register(fullPath, type)) {
            this._emitEvent({event: TOUCH_EVENT, relativePath, metadata});
          }
        }
      }
    } catch (error) {
      if (!isIgnorableFileError(error)) {
        this.emitError(error);
        return;
      }
      this._unregister(fullPath);
      this._unregisterDir(fullPath);
      if (registered) {
        this._emitEvent({event: DELETE_EVENT, relativePath});
      }
      await this._stopWatching(fullPath);
    }
  }

  /**
   * Emits the given event after debouncing, to emit only the latest
   * information when we receive several events in quick succession. E.g.,
   * Linux emits two events for every new file.
   *
   * See also note above for DEBOUNCE_MS.
   */
  _emitEvent(change: Omit<WatcherBackendChangeEvent, 'root'>) {
    const {event, relativePath} = change;
    const key = event + '-' + relativePath;
    const existingTimer = this._changeTimers.get(key);
    if (existingTimer) {
      clearTimeout(existingTimer);
    }
    this._changeTimers.set(
      key,
      setTimeout(() => {
        this._changeTimers.delete(key);
        this.emitFileEvent(change);
      }, DEBOUNCE_MS),
    );
  }

  getPauseReason(): ?string {
    return null;
  }
}
/**
 * Determine if a given FS error can be ignored
 */
function isIgnorableFileError(error: Error | {code: string}) {
  return (
    error.code === 'ENOENT' ||
    // Workaround Windows EPERM on watched folder deletion, and when
    // reading locked files (pending further writes or pending deletion).
    // In such cases, we'll receive a subsequent event when the file is
    // deleted or ready to read.
    // https://github.com/facebook/metro/issues/1001
    // https://github.com/nodejs/node-v0.x-archive/issues/4337
    (error.code === 'EPERM' && platform === 'win32')
  );
}

/**
 * Traverse a directory recursively calling `callback` on every directory.
 */
function recReaddir(
  dir: string,
  dirCallback: (string, Stats) => void,
  fileCallback: (string, Stats) => void,
  symlinkCallback: (string, Stats) => void,
  endCallback: () => void,
  errorCallback: Error => void,
  ignored: ?RegExp,
) {
  const walk = walker(dir);
  if (ignored) {
    walk.filterDir(
      (currentDir: string) =>
        !common.posixPathMatchesPattern(ignored, currentDir),
    );
  }
  walk
    .on('dir', normalizeProxy(dirCallback))
    .on('file', normalizeProxy(fileCallback))
    .on('symlink', normalizeProxy(symlinkCallback))
    .on('error', errorCallback)
    .on('end', () => {
      if (platform === 'win32') {
        setTimeout(endCallback, 1000);
      } else {
        endCallback();
      }
    });
}

/**
 * Returns a callback that when called will normalize a path and call the
 * original callback
 */
function normalizeProxy<T>(
  callback: (filepath: string, stats: Stats) => T,
): (string, Stats) => T {
  return (filepath: string, stats: Stats) =>
    callback(path.normalize(filepath), stats);
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!