%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/lib/internal/fs/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/lib/internal/fs/recursive_watch.js

'use strict';

const {
  Promise,
  SafeMap,
  SafeSet,
  StringPrototypeStartsWith,
  SymbolAsyncIterator,
} = primordials;

const { EventEmitter } = require('events');
const assert = require('internal/assert');
const {
  AbortError,
  codes: {
    ERR_INVALID_ARG_VALUE,
  },
} = require('internal/errors');
const { getValidatedPath } = require('internal/fs/utils');
const { kFSWatchStart, StatWatcher } = require('internal/fs/watchers');
const { kEmptyObject } = require('internal/util');
const { validateBoolean, validateAbortSignal } = require('internal/validators');
const {
  basename: pathBasename,
  join: pathJoin,
  relative: pathRelative,
  resolve: pathResolve,
} = require('path');

let internalSync;

function lazyLoadFsSync() {
  internalSync ??= require('fs');
  return internalSync;
}

let kResistStopPropagation;

class FSWatcher extends EventEmitter {
  #options = null;
  #closed = false;
  #files = new SafeMap();
  #watchers = new SafeMap();
  #symbolicFiles = new SafeSet();
  #rootPath = pathResolve();
  #watchingFile = false;

  constructor(options = kEmptyObject) {
    super();

    assert(typeof options === 'object');

    const { persistent, recursive, signal, encoding } = options;

    // TODO(anonrig): Add non-recursive support to non-native-watcher for IBMi & AIX support.
    if (recursive != null) {
      validateBoolean(recursive, 'options.recursive');
    }

    if (persistent != null) {
      validateBoolean(persistent, 'options.persistent');
    }

    if (signal != null) {
      validateAbortSignal(signal, 'options.signal');
    }

    if (encoding != null) {
      // This is required since on macOS and Windows it throws ERR_INVALID_ARG_VALUE
      if (typeof encoding !== 'string') {
        throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding');
      }
    }

    this.#options = { persistent, recursive, signal, encoding };
  }

  close() {
    if (this.#closed) {
      return;
    }

    this.#closed = true;

    for (const file of this.#files.keys()) {
      this.#watchers.get(file).close();
      this.#watchers.delete(file);
    }

    this.#files.clear();
    this.#symbolicFiles.clear();
    this.emit('close');
  }

  #unwatchFiles(file) {
    this.#symbolicFiles.delete(file);

    for (const filename of this.#files.keys()) {
      if (StringPrototypeStartsWith(filename, file)) {
        this.#files.delete(filename);
        this.#watchers.get(filename).close();
        this.#watchers.delete(filename);
      }
    }
  }

  #watchFolder(folder) {
    const { readdirSync } = lazyLoadFsSync();

    try {
      const files = readdirSync(folder, {
        withFileTypes: true,
      });

      for (const file of files) {
        if (this.#closed) {
          break;
        }

        const f = pathJoin(folder, file.name);

        if (!this.#files.has(f)) {
          this.emit('change', 'rename', pathRelative(this.#rootPath, f));

          if (file.isSymbolicLink()) {
            this.#symbolicFiles.add(f);
          }

          this.#watchFile(f);
          if (file.isDirectory() && !file.isSymbolicLink()) {
            this.#watchFolder(f);
          }
        }
      }
    } catch (error) {
      this.emit('error', error);
    }
  }

  #watchFile(file) {
    if (this.#closed) {
      return;
    }

    const { watch, statSync } = lazyLoadFsSync();

    if (this.#files.has(file)) {
      return;
    }

    {
      const existingStat = statSync(file);
      this.#files.set(file, existingStat);
    }

    const watcher = watch(file, {
      persistent: this.#options.persistent,
    }, (eventType, filename) => {
      const existingStat = this.#files.get(file);
      const currentStats = statSync(file);

      this.#files.set(file, currentStats);

      if (currentStats.birthtimeMs === 0 && existingStat.birthtimeMs !== 0) {
        // The file is now deleted
        this.#files.delete(file);
        this.#watchers.delete(file);
        watcher.close();
        this.emit('change', 'rename', pathRelative(this.#rootPath, file));
        this.#unwatchFiles(file);
      } else if (file === this.#rootPath && this.#watchingFile) {
        // This case will only be triggered when watching a file with fs.watch
        this.emit('change', 'change', pathBasename(file));
      } else if (this.#symbolicFiles.has(file)) {
        // Stats from watchFile does not return correct value for currentStats.isSymbolicLink()
        // Since it is only valid when using fs.lstat(). Therefore, check the existing symbolic files.
        this.emit('change', 'rename', pathRelative(this.#rootPath, file));
      } else if (currentStats.isDirectory()) {
        this.#watchFolder(file);
      } else {
        // Watching a directory will trigger a change event for child files)
        this.emit('change', 'change', pathRelative(this.#rootPath, file));
      }
    });
    this.#watchers.set(file, watcher);
  }

  [kFSWatchStart](filename) {
    filename = pathResolve(getValidatedPath(filename));

    try {
      const file = lazyLoadFsSync().statSync(filename);

      this.#rootPath = filename;
      this.#closed = false;
      this.#watchingFile = file.isFile();

      this.#watchFile(filename);
      if (file.isDirectory()) {
        this.#watchFolder(filename);
      }
    } catch (error) {
      if (error.code === 'ENOENT') {
        error.filename = filename;
        throw error;
      }
    }

  }

  ref() {
    this.#files.forEach((file) => {
      if (file instanceof StatWatcher) {
        file.ref();
      }
    });
  }

  unref() {
    this.#files.forEach((file) => {
      if (file instanceof StatWatcher) {
        file.unref();
      }
    });
  }

  [SymbolAsyncIterator]() {
    const { signal } = this.#options;
    const promiseExecutor = signal == null ?
      (resolve) => {
        this.once('change', (eventType, filename) => {
          resolve({ __proto__: null, value: { eventType, filename } });
        });
      } : (resolve, reject) => {
        const onAbort = () => {
          this.close();
          reject(new AbortError(undefined, { cause: signal.reason }));
        };
        if (signal.aborted) return onAbort();
        kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
        signal.addEventListener('abort', onAbort, { __proto__: null, once: true, [kResistStopPropagation]: true });
        this.once('change', (eventType, filename) => {
          signal.removeEventListener('abort', onAbort);
          resolve({ __proto__: null, value: { eventType, filename } });
        });
      };
    return {
      next: () => (this.#closed ?
        { __proto__: null, done: true } :
        new Promise(promiseExecutor)),
      return: () => {
        this.close();
        return { __proto__: null, done: true };
      },
      [SymbolAsyncIterator]() { return this; },
    };
  }
}

module.exports = {
  FSWatcher,
  kFSWatchStart,
};

Zerion Mini Shell 1.0