OpenVeo Publish server

API Docs for: 8.0.0
Show:

File: app/server/watcher/DirectoryWatcher.js

  1. 'use strict';
  2.  
  3. /**
  4. * @module watcher
  5. */
  6.  
  7. var path = require('path');
  8. var util = require('util');
  9. var events = require('events');
  10. var fs = require('fs');
  11. var async = require('async');
  12. var openVeoApi = require('@openveo/api');
  13. var DirectoryFsWatcher = process.requirePublish('app/server/watcher/DirectoryFsWatcher.js');
  14. var WatcherError = process.requirePublish('app/server/watcher/WatcherError.js');
  15.  
  16. /**
  17. * Fired when an error occurred.
  18. *
  19. * @event error
  20. * @param {WatcherError} The error
  21. */
  22.  
  23. /**
  24. * Fired when a new resource (file or directory) has been added to the watched folder.
  25. *
  26. * @event create
  27. * @param {String} Path of the added resource
  28. */
  29.  
  30. /**
  31. * Fired when a resource (file or directory) has been deleted from the watched folder.
  32. *
  33. * @event delete
  34. * @param {String} Path of the resource before it has been removed
  35. */
  36.  
  37. /**
  38. * Fired when a directory is added to the watched directory.
  39. *
  40. * Fired after "create" event in case the directory is added to the watched directory.
  41. *
  42. * @event watch
  43. * @param {String} Path of the directory
  44. */
  45.  
  46. /**
  47. * Defines a directory watcher to watch for changes inside a directory and its sub directories.
  48. *
  49. * @class DirectoryWatcher
  50. * @constructor
  51. * @param {Array} directoryPath The absolute path of the directory to watch
  52. * @param {Object} [options] Watcher options
  53. * @param {Number} [options.stabilityThreshold] Number of milliseconds to wait before considering a file
  54. * as stable
  55. */
  56. function DirectoryWatcher(directoryPath, options) {
  57. Object.defineProperties(this, {
  58.  
  59. /**
  60. * The absolute path of the watched directory.
  61. *
  62. * @property directoryPath
  63. * @type String
  64. * @final
  65. */
  66. directoryPath: {
  67. value: path.resolve(directoryPath)
  68. },
  69.  
  70. /**
  71. * List of watchers for this directory and its sub directories.
  72. *
  73. * @property fsWatchers
  74. * @type Array
  75. * @final
  76. */
  77. fsWatchers: {
  78. value: []
  79. },
  80.  
  81. /**
  82. * Watcher options.
  83. *
  84. * @property options
  85. * @type Object
  86. * @final
  87. */
  88. options: {
  89. value: options
  90. }
  91.  
  92. });
  93. }
  94.  
  95. module.exports = DirectoryWatcher;
  96. util.inherits(DirectoryWatcher, events.EventEmitter);
  97.  
  98. /**
  99. * Checks if a sub directory is actually being watched.
  100. *
  101. * @method isWatched
  102. * @private
  103. * @param {String} directoryPath The absolute path of the directory to check
  104. * @return {Boolean} true if directory is actually being watched, false otherwise
  105. */
  106. function isWatched(directoryPath) {
  107. directoryPath = path.resolve(directoryPath);
  108.  
  109. for (var i = 0; i < this.fsWatchers.length; i++) {
  110. if (directoryPath === this.fsWatchers[i].directoryPath)
  111. return true;
  112. }
  113.  
  114. return false;
  115. }
  116.  
  117. /**
  118. * Creates a watcher on the given directory.
  119. *
  120. * @method createWatcher
  121. * @private
  122. * @async
  123. * @param {String} directoryPath The absolute path of the directory to watch
  124. * @param {Function} callback The function to call when its done
  125. * - **Error** An error if something went wrong
  126. */
  127. function createWatcher(directoryPath, callback) {
  128.  
  129. // Make sure the directory is not already watched before going any further
  130. if (isWatched.call(this, directoryPath)) return;
  131.  
  132. var directoryFsWatcher = new DirectoryFsWatcher(directoryPath, this.options);
  133.  
  134. // Listen to new resources in directory or sub directories
  135. directoryFsWatcher.on('create', (function(resourcePath) {
  136. this.emit('create', resourcePath);
  137.  
  138. // Analyze the new resource to add a watcher on it if it is a directory
  139. fs.stat(resourcePath, (function(error, stats) {
  140.  
  141. // If resource has not been found (ENOENT) it's because, meanwhile, it has been removed
  142. if (error) {
  143. if (error.code !== 'ENOENT')
  144. return this.emit('error', new WatcherError(error.message, error.code, resourcePath));
  145. } else if (stats.isDirectory())
  146. createWatcher.call(this, resourcePath);
  147.  
  148. }).bind(this));
  149. }).bind(this));
  150.  
  151. // Listen to removed resources
  152. directoryFsWatcher.on('delete', (function(resourcePath) {
  153.  
  154. // Stop watching the resource if it was a directory
  155. this.close(resourcePath);
  156. this.emit('delete', resourcePath);
  157. }).bind(this));
  158.  
  159. // Listen to watcher errors
  160. directoryFsWatcher.on('error', (function(error) {
  161. this.emit('error', error);
  162. }).bind(this));
  163.  
  164. this.fsWatchers.push(directoryFsWatcher);
  165.  
  166. directoryFsWatcher.watch((function(error) {
  167. if (!error) this.emit('watch', directoryPath);
  168.  
  169. callback && callback(error);
  170. }).bind(this));
  171. }
  172.  
  173. /**
  174. * Watches a directory and all its sub directories.
  175. *
  176. * @method watch
  177. * @async
  178. * @param {Function} callback The function to call when its done
  179. * - **Error** An error if something went wrong
  180. */
  181. DirectoryWatcher.prototype.watch = function(callback) {
  182.  
  183. // Get all resources of the directory (recursively)
  184. openVeoApi.fileSystem.readdir(this.directoryPath, (function(error, resources) {
  185. if (error) return callback(error);
  186.  
  187. var asyncTasks = [];
  188.  
  189. // Watch sub directories for changes
  190. resources.forEach((function(resource) {
  191. if (resource.isDirectory()) {
  192.  
  193. // Resource is a sub directory
  194. // Watch it
  195. asyncTasks.push((function(callback) {
  196. createWatcher.call(this, resource.path, callback);
  197. }).bind(this));
  198.  
  199. }
  200. }).bind(this));
  201.  
  202. // Finally watch the directory itself
  203. asyncTasks.push((function(callback) {
  204. createWatcher.call(this, this.directoryPath, callback);
  205. }).bind(this));
  206.  
  207. async.parallel(asyncTasks, callback);
  208. }).bind(this));
  209.  
  210. };
  211.  
  212. /**
  213. * Stops watching the directory and all its sub directories.
  214. *
  215. * @method close
  216. * @param {String} [directoryPath] Absolute path of the directory to stop watching. If not specified
  217. * the directory and all its sub directories won't be watched anymore
  218. */
  219. DirectoryWatcher.prototype.close = function(directoryPath) {
  220. var index = -1;
  221. for (var i = 0; i < this.fsWatchers.length; i++) {
  222. var fsWatcher = this.fsWatchers[i];
  223.  
  224. if (directoryPath) {
  225. if (directoryPath === fsWatcher.directoryPath) {
  226. index = i;
  227. fsWatcher.close();
  228. break;
  229. }
  230. } else {
  231. fsWatcher.close();
  232. }
  233. }
  234.  
  235. if (directoryPath) {
  236. if (index > -1) this.fsWatchers.splice(index, 1);
  237. } else
  238. this.fsWatchers.splice(0, this.fsWatchers.length);
  239. };
  240.