OpenVeo Publish server

API Docs for: 8.0.0
Show:

File: app/server/watcher/DirectoryWatcher.js

'use strict';

/**
 * @module watcher
 */

var path = require('path');
var util = require('util');
var events = require('events');
var fs = require('fs');
var async = require('async');
var openVeoApi = require('@openveo/api');
var DirectoryFsWatcher = process.requirePublish('app/server/watcher/DirectoryFsWatcher.js');
var WatcherError = process.requirePublish('app/server/watcher/WatcherError.js');

/**
 * Fired when an error occurred.
 *
 * @event error
 * @param {WatcherError} The error
 */

/**
 * Fired when a new resource (file or directory) has been added to the watched folder.
 *
 * @event create
 * @param {String} Path of the added resource
 */

/**
 * Fired when a resource (file or directory) has been deleted from the watched folder.
 *
 * @event delete
 * @param {String} Path of the resource before it has been removed
 */

/**
 * Fired when a directory is added to the watched directory.
 *
 * Fired after "create" event in case the directory is added to the watched directory.
 *
 * @event watch
 * @param {String} Path of the directory
 */

/**
 * Defines a directory watcher to watch for changes inside a directory and its sub directories.
 *
 * @class DirectoryWatcher
 * @constructor
 * @param {Array} directoryPath The absolute path of the directory to watch
 * @param {Object} [options] Watcher options
 * @param {Number} [options.stabilityThreshold] Number of milliseconds to wait before considering a file
 * as stable
 */
function DirectoryWatcher(directoryPath, options) {
  Object.defineProperties(this, {

    /**
     * The absolute path of the watched directory.
     *
     * @property directoryPath
     * @type String
     * @final
     */
    directoryPath: {
      value: path.resolve(directoryPath)
    },

    /**
     * List of watchers for this directory and its sub directories.
     *
     * @property fsWatchers
     * @type Array
     * @final
     */
    fsWatchers: {
      value: []
    },

    /**
     * Watcher options.
     *
     * @property options
     * @type Object
     * @final
     */
    options: {
      value: options
    }

  });
}

module.exports = DirectoryWatcher;
util.inherits(DirectoryWatcher, events.EventEmitter);

/**
 * Checks if a sub directory is actually being watched.
 *
 * @method isWatched
 * @private
 * @param {String} directoryPath The absolute path of the directory to check
 * @return {Boolean} true if directory is actually being watched, false otherwise
 */
function isWatched(directoryPath) {
  directoryPath = path.resolve(directoryPath);

  for (var i = 0; i < this.fsWatchers.length; i++) {
    if (directoryPath === this.fsWatchers[i].directoryPath)
      return true;
  }

  return false;
}

/**
 * Creates a watcher on the given directory.
 *
 * @method createWatcher
 * @private
 * @async
 * @param {String} directoryPath The absolute path of the directory to watch
 * @param {Function} callback The function to call when its done
 *  - **Error** An error if something went wrong
 */
function createWatcher(directoryPath, callback) {

  // Make sure the directory is not already watched before going any further
  if (isWatched.call(this, directoryPath)) return;

  var directoryFsWatcher = new DirectoryFsWatcher(directoryPath, this.options);

  // Listen to new resources in directory or sub directories
  directoryFsWatcher.on('create', (function(resourcePath) {
    this.emit('create', resourcePath);

    // Analyze the new resource to add a watcher on it if it is a directory
    fs.stat(resourcePath, (function(error, stats) {

      // If resource has not been found (ENOENT) it's because, meanwhile, it has been removed
      if (error) {
        if (error.code !== 'ENOENT')
          return this.emit('error', new WatcherError(error.message, error.code, resourcePath));
      } else if (stats.isDirectory())
        createWatcher.call(this, resourcePath);

    }).bind(this));
  }).bind(this));

  // Listen to removed resources
  directoryFsWatcher.on('delete', (function(resourcePath) {

    // Stop watching the resource if it was a directory
    this.close(resourcePath);
    this.emit('delete', resourcePath);
  }).bind(this));

  // Listen to watcher errors
  directoryFsWatcher.on('error', (function(error) {
    this.emit('error', error);
  }).bind(this));

  this.fsWatchers.push(directoryFsWatcher);

  directoryFsWatcher.watch((function(error) {
    if (!error) this.emit('watch', directoryPath);

    callback && callback(error);
  }).bind(this));
}

/**
 * Watches a directory and all its sub directories.
 *
 * @method watch
 * @async
 * @param {Function} callback The function to call when its done
 *  - **Error** An error if something went wrong
 */
DirectoryWatcher.prototype.watch = function(callback) {

  // Get all resources of the directory (recursively)
  openVeoApi.fileSystem.readdir(this.directoryPath, (function(error, resources) {
    if (error) return callback(error);

    var asyncTasks = [];

    // Watch sub directories for changes
    resources.forEach((function(resource) {
      if (resource.isDirectory()) {

        // Resource is a sub directory
        // Watch it
        asyncTasks.push((function(callback) {
          createWatcher.call(this, resource.path, callback);
        }).bind(this));

      }
    }).bind(this));

    // Finally watch the directory itself
    asyncTasks.push((function(callback) {
      createWatcher.call(this, this.directoryPath, callback);
    }).bind(this));

    async.parallel(asyncTasks, callback);
  }).bind(this));

};

/**
 * Stops watching the directory and all its sub directories.
 *
 * @method close
 * @param {String} [directoryPath] Absolute path of the directory to stop watching. If not specified
 * the directory and all its sub directories won't be watched anymore
 */
DirectoryWatcher.prototype.close = function(directoryPath) {
  var index = -1;
  for (var i = 0; i < this.fsWatchers.length; i++) {
    var fsWatcher = this.fsWatchers[i];

    if (directoryPath) {
      if (directoryPath === fsWatcher.directoryPath) {
        index = i;
        fsWatcher.close();
        break;
      }
    } else {
      fsWatcher.close();
    }
  }

  if (directoryPath) {
    if (index > -1) this.fsWatchers.splice(index, 1);
  } else
    this.fsWatchers.splice(0, this.fsWatchers.length);
};