Front end of the Slack clone application.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. 'use strict';
  2. var EventEmitter = require('events').EventEmitter;
  3. var fs = require('fs');
  4. var sysPath = require('path');
  5. var asyncEach = require('async-each');
  6. var anymatch = require('anymatch');
  7. var globParent = require('glob-parent');
  8. var isGlob = require('is-glob');
  9. var isAbsolute = require('path-is-absolute');
  10. var inherits = require('inherits');
  11. var NodeFsHandler = require('./lib/nodefs-handler');
  12. var FsEventsHandler = require('./lib/fsevents-handler');
  13. var arrify = function(value) {
  14. if (value == null) return [];
  15. return Array.isArray(value) ? value : [value];
  16. };
  17. var flatten = function(list, result) {
  18. if (result == null) result = [];
  19. list.forEach(function(item) {
  20. if (Array.isArray(item)) {
  21. flatten(item, result);
  22. } else {
  23. result.push(item);
  24. }
  25. });
  26. return result;
  27. };
  28. // Little isString util for use in Array#every.
  29. var isString = function(thing) {
  30. return typeof thing === 'string';
  31. };
  32. // Public: Main class.
  33. // Watches files & directories for changes.
  34. //
  35. // * _opts - object, chokidar options hash
  36. //
  37. // Emitted events:
  38. // `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
  39. //
  40. // Examples
  41. //
  42. // var watcher = new FSWatcher()
  43. // .add(directories)
  44. // .on('add', path => console.log('File', path, 'was added'))
  45. // .on('change', path => console.log('File', path, 'was changed'))
  46. // .on('unlink', path => console.log('File', path, 'was removed'))
  47. // .on('all', (event, path) => console.log(path, ' emitted ', event))
  48. //
  49. function FSWatcher(_opts) {
  50. EventEmitter.call(this);
  51. var opts = {};
  52. // in case _opts that is passed in is a frozen object
  53. if (_opts) for (var opt in _opts) opts[opt] = _opts[opt];
  54. this._watched = Object.create(null);
  55. this._closers = Object.create(null);
  56. this._ignoredPaths = Object.create(null);
  57. Object.defineProperty(this, '_globIgnored', {
  58. get: function() { return Object.keys(this._ignoredPaths); }
  59. });
  60. this.closed = false;
  61. this._throttled = Object.create(null);
  62. this._symlinkPaths = Object.create(null);
  63. function undef(key) {
  64. return opts[key] === undefined;
  65. }
  66. // Set up default options.
  67. if (undef('persistent')) opts.persistent = true;
  68. if (undef('ignoreInitial')) opts.ignoreInitial = false;
  69. if (undef('ignorePermissionErrors')) opts.ignorePermissionErrors = false;
  70. if (undef('interval')) opts.interval = 100;
  71. if (undef('binaryInterval')) opts.binaryInterval = 300;
  72. if (undef('disableGlobbing')) opts.disableGlobbing = false;
  73. this.enableBinaryInterval = opts.binaryInterval !== opts.interval;
  74. // Enable fsevents on OS X when polling isn't explicitly enabled.
  75. if (undef('useFsEvents')) opts.useFsEvents = !opts.usePolling;
  76. // If we can't use fsevents, ensure the options reflect it's disabled.
  77. if (!FsEventsHandler.canUse()) opts.useFsEvents = false;
  78. // Use polling on Mac if not using fsevents.
  79. // Other platforms use non-polling fs.watch.
  80. if (undef('usePolling') && !opts.useFsEvents) {
  81. opts.usePolling = process.platform === 'darwin';
  82. }
  83. // Global override (useful for end-developers that need to force polling for all
  84. // instances of chokidar, regardless of usage/dependency depth)
  85. var envPoll = process.env.CHOKIDAR_USEPOLLING;
  86. if (envPoll !== undefined) {
  87. var envLower = envPoll.toLowerCase();
  88. if (envLower === 'false' || envLower === '0') {
  89. opts.usePolling = false;
  90. } else if (envLower === 'true' || envLower === '1') {
  91. opts.usePolling = true;
  92. } else {
  93. opts.usePolling = !!envLower
  94. }
  95. }
  96. var envInterval = process.env.CHOKIDAR_INTERVAL;
  97. if (envInterval) {
  98. opts.interval = parseInt(envInterval);
  99. }
  100. // Editor atomic write normalization enabled by default with fs.watch
  101. if (undef('atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents;
  102. if (opts.atomic) this._pendingUnlinks = Object.create(null);
  103. if (undef('followSymlinks')) opts.followSymlinks = true;
  104. if (undef('awaitWriteFinish')) opts.awaitWriteFinish = false;
  105. if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {};
  106. var awf = opts.awaitWriteFinish;
  107. if (awf) {
  108. if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000;
  109. if (!awf.pollInterval) awf.pollInterval = 100;
  110. this._pendingWrites = Object.create(null);
  111. }
  112. if (opts.ignored) opts.ignored = arrify(opts.ignored);
  113. this._isntIgnored = function(path, stat) {
  114. return !this._isIgnored(path, stat);
  115. }.bind(this);
  116. var readyCalls = 0;
  117. this._emitReady = function() {
  118. if (++readyCalls >= this._readyCount) {
  119. this._emitReady = Function.prototype;
  120. this._readyEmitted = true;
  121. // use process.nextTick to allow time for listener to be bound
  122. process.nextTick(this.emit.bind(this, 'ready'));
  123. }
  124. }.bind(this);
  125. this.options = opts;
  126. // You’re frozen when your heart’s not open.
  127. Object.freeze(opts);
  128. }
  129. inherits(FSWatcher, EventEmitter);
  130. // Common helpers
  131. // --------------
  132. // Private method: Normalize and emit events
  133. //
  134. // * event - string, type of event
  135. // * path - string, file or directory path
  136. // * val[1..3] - arguments to be passed with event
  137. //
  138. // Returns the error if defined, otherwise the value of the
  139. // FSWatcher instance's `closed` flag
  140. FSWatcher.prototype._emit = function(event, path, val1, val2, val3) {
  141. if (this.options.cwd) path = sysPath.relative(this.options.cwd, path);
  142. var args = [event, path];
  143. if (val3 !== undefined) args.push(val1, val2, val3);
  144. else if (val2 !== undefined) args.push(val1, val2);
  145. else if (val1 !== undefined) args.push(val1);
  146. var awf = this.options.awaitWriteFinish;
  147. if (awf && this._pendingWrites[path]) {
  148. this._pendingWrites[path].lastChange = new Date();
  149. return this;
  150. }
  151. if (this.options.atomic) {
  152. if (event === 'unlink') {
  153. this._pendingUnlinks[path] = args;
  154. setTimeout(function() {
  155. Object.keys(this._pendingUnlinks).forEach(function(path) {
  156. this.emit.apply(this, this._pendingUnlinks[path]);
  157. this.emit.apply(this, ['all'].concat(this._pendingUnlinks[path]));
  158. delete this._pendingUnlinks[path];
  159. }.bind(this));
  160. }.bind(this), typeof this.options.atomic === "number"
  161. ? this.options.atomic
  162. : 100);
  163. return this;
  164. } else if (event === 'add' && this._pendingUnlinks[path]) {
  165. event = args[0] = 'change';
  166. delete this._pendingUnlinks[path];
  167. }
  168. }
  169. var emitEvent = function() {
  170. this.emit.apply(this, args);
  171. if (event !== 'error') this.emit.apply(this, ['all'].concat(args));
  172. }.bind(this);
  173. if (awf && (event === 'add' || event === 'change') && this._readyEmitted) {
  174. var awfEmit = function(err, stats) {
  175. if (err) {
  176. event = args[0] = 'error';
  177. args[1] = err;
  178. emitEvent();
  179. } else if (stats) {
  180. // if stats doesn't exist the file must have been deleted
  181. if (args.length > 2) {
  182. args[2] = stats;
  183. } else {
  184. args.push(stats);
  185. }
  186. emitEvent();
  187. }
  188. };
  189. this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);
  190. return this;
  191. }
  192. if (event === 'change') {
  193. if (!this._throttle('change', path, 50)) return this;
  194. }
  195. if (
  196. this.options.alwaysStat && val1 === undefined &&
  197. (event === 'add' || event === 'addDir' || event === 'change')
  198. ) {
  199. var fullPath = this.options.cwd ? sysPath.join(this.options.cwd, path) : path;
  200. fs.stat(fullPath, function(error, stats) {
  201. // Suppress event when fs.stat fails, to avoid sending undefined 'stat'
  202. if (error || !stats) return;
  203. args.push(stats);
  204. emitEvent();
  205. });
  206. } else {
  207. emitEvent();
  208. }
  209. return this;
  210. };
  211. // Private method: Common handler for errors
  212. //
  213. // * error - object, Error instance
  214. //
  215. // Returns the error if defined, otherwise the value of the
  216. // FSWatcher instance's `closed` flag
  217. FSWatcher.prototype._handleError = function(error) {
  218. var code = error && error.code;
  219. var ipe = this.options.ignorePermissionErrors;
  220. if (error &&
  221. code !== 'ENOENT' &&
  222. code !== 'ENOTDIR' &&
  223. (!ipe || (code !== 'EPERM' && code !== 'EACCES'))
  224. ) this.emit('error', error);
  225. return error || this.closed;
  226. };
  227. // Private method: Helper utility for throttling
  228. //
  229. // * action - string, type of action being throttled
  230. // * path - string, path being acted upon
  231. // * timeout - int, duration of time to suppress duplicate actions
  232. //
  233. // Returns throttle tracking object or false if action should be suppressed
  234. FSWatcher.prototype._throttle = function(action, path, timeout) {
  235. if (!(action in this._throttled)) {
  236. this._throttled[action] = Object.create(null);
  237. }
  238. var throttled = this._throttled[action];
  239. if (path in throttled) return false;
  240. function clear() {
  241. delete throttled[path];
  242. clearTimeout(timeoutObject);
  243. }
  244. var timeoutObject = setTimeout(clear, timeout);
  245. throttled[path] = {timeoutObject: timeoutObject, clear: clear};
  246. return throttled[path];
  247. };
  248. // Private method: Awaits write operation to finish
  249. //
  250. // * path - string, path being acted upon
  251. // * threshold - int, time in milliseconds a file size must be fixed before
  252. // acknowledgeing write operation is finished
  253. // * awfEmit - function, to be called when ready for event to be emitted
  254. // Polls a newly created file for size variations. When files size does not
  255. // change for 'threshold' milliseconds calls callback.
  256. FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit) {
  257. var timeoutHandler;
  258. var fullPath = path;
  259. if (this.options.cwd && !isAbsolute(path)) {
  260. fullPath = sysPath.join(this.options.cwd, path);
  261. }
  262. var now = new Date();
  263. var awaitWriteFinish = (function (prevStat) {
  264. fs.stat(fullPath, function(err, curStat) {
  265. if (err) {
  266. if (err.code !== 'ENOENT') awfEmit(err);
  267. return;
  268. }
  269. var now = new Date();
  270. if (prevStat && curStat.size != prevStat.size) {
  271. this._pendingWrites[path].lastChange = now;
  272. }
  273. if (now - this._pendingWrites[path].lastChange >= threshold) {
  274. delete this._pendingWrites[path];
  275. awfEmit(null, curStat);
  276. } else {
  277. timeoutHandler = setTimeout(
  278. awaitWriteFinish.bind(this, curStat),
  279. this.options.awaitWriteFinish.pollInterval
  280. );
  281. }
  282. }.bind(this));
  283. }.bind(this));
  284. if (!(path in this._pendingWrites)) {
  285. this._pendingWrites[path] = {
  286. lastChange: now,
  287. cancelWait: function() {
  288. delete this._pendingWrites[path];
  289. clearTimeout(timeoutHandler);
  290. return event;
  291. }.bind(this)
  292. };
  293. timeoutHandler = setTimeout(
  294. awaitWriteFinish.bind(this),
  295. this.options.awaitWriteFinish.pollInterval
  296. );
  297. }
  298. };
  299. // Private method: Determines whether user has asked to ignore this path
  300. //
  301. // * path - string, path to file or directory
  302. // * stats - object, result of fs.stat
  303. //
  304. // Returns boolean
  305. var dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/;
  306. FSWatcher.prototype._isIgnored = function(path, stats) {
  307. if (this.options.atomic && dotRe.test(path)) return true;
  308. if (!this._userIgnored) {
  309. var cwd = this.options.cwd;
  310. var ignored = this.options.ignored;
  311. if (cwd && ignored) {
  312. ignored = ignored.map(function (path) {
  313. if (typeof path !== 'string') return path;
  314. return isAbsolute(path) ? path : sysPath.join(cwd, path);
  315. });
  316. }
  317. var paths = arrify(ignored)
  318. .filter(function(path) {
  319. return typeof path === 'string' && !isGlob(path);
  320. }).map(function(path) {
  321. return path + '/**';
  322. });
  323. this._userIgnored = anymatch(
  324. this._globIgnored.concat(ignored).concat(paths)
  325. );
  326. }
  327. return this._userIgnored([path, stats]);
  328. };
  329. // Private method: Provides a set of common helpers and properties relating to
  330. // symlink and glob handling
  331. //
  332. // * path - string, file, directory, or glob pattern being watched
  333. // * depth - int, at any depth > 0, this isn't a glob
  334. //
  335. // Returns object containing helpers for this path
  336. var replacerRe = /^\.[\/\\]/;
  337. FSWatcher.prototype._getWatchHelpers = function(path, depth) {
  338. path = path.replace(replacerRe, '');
  339. var watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path);
  340. var fullWatchPath = sysPath.resolve(watchPath);
  341. var hasGlob = watchPath !== path;
  342. var globFilter = hasGlob ? anymatch(path) : false;
  343. var follow = this.options.followSymlinks;
  344. var globSymlink = hasGlob && follow ? null : false;
  345. var checkGlobSymlink = function(entry) {
  346. // only need to resolve once
  347. // first entry should always have entry.parentDir === ''
  348. if (globSymlink == null) {
  349. globSymlink = entry.fullParentDir === fullWatchPath ? false : {
  350. realPath: entry.fullParentDir,
  351. linkPath: fullWatchPath
  352. };
  353. }
  354. if (globSymlink) {
  355. return entry.fullPath.replace(globSymlink.realPath, globSymlink.linkPath);
  356. }
  357. return entry.fullPath;
  358. };
  359. var entryPath = function(entry) {
  360. return sysPath.join(watchPath,
  361. sysPath.relative(watchPath, checkGlobSymlink(entry))
  362. );
  363. };
  364. var filterPath = function(entry) {
  365. if (entry.stat && entry.stat.isSymbolicLink()) return filterDir(entry);
  366. var resolvedPath = entryPath(entry);
  367. return (!hasGlob || globFilter(resolvedPath)) &&
  368. this._isntIgnored(resolvedPath, entry.stat) &&
  369. (this.options.ignorePermissionErrors ||
  370. this._hasReadPermissions(entry.stat));
  371. }.bind(this);
  372. var getDirParts = function(path) {
  373. if (!hasGlob) return false;
  374. var parts = sysPath.relative(watchPath, path).split(/[\/\\]/);
  375. return parts;
  376. };
  377. var dirParts = getDirParts(path);
  378. if (dirParts && dirParts.length > 1) dirParts.pop();
  379. var unmatchedGlob;
  380. var filterDir = function(entry) {
  381. if (hasGlob) {
  382. var entryParts = getDirParts(checkGlobSymlink(entry));
  383. var globstar = false;
  384. unmatchedGlob = !dirParts.every(function(part, i) {
  385. if (part === '**') globstar = true;
  386. return globstar || !entryParts[i] || anymatch(part, entryParts[i]);
  387. });
  388. }
  389. return !unmatchedGlob && this._isntIgnored(entryPath(entry), entry.stat);
  390. }.bind(this);
  391. return {
  392. followSymlinks: follow,
  393. statMethod: follow ? 'stat' : 'lstat',
  394. path: path,
  395. watchPath: watchPath,
  396. entryPath: entryPath,
  397. hasGlob: hasGlob,
  398. globFilter: globFilter,
  399. filterPath: filterPath,
  400. filterDir: filterDir
  401. };
  402. };
  403. // Directory helpers
  404. // -----------------
  405. // Private method: Provides directory tracking objects
  406. //
  407. // * directory - string, path of the directory
  408. //
  409. // Returns the directory's tracking object
  410. FSWatcher.prototype._getWatchedDir = function(directory) {
  411. var dir = sysPath.resolve(directory);
  412. var watcherRemove = this._remove.bind(this);
  413. if (!(dir in this._watched)) this._watched[dir] = {
  414. _items: Object.create(null),
  415. add: function(item) {
  416. if (item !== '.' && item !== '..') this._items[item] = true;
  417. },
  418. remove: function(item) {
  419. delete this._items[item];
  420. if (!this.children().length) {
  421. fs.readdir(dir, function(err) {
  422. if (err) watcherRemove(sysPath.dirname(dir), sysPath.basename(dir));
  423. });
  424. }
  425. },
  426. has: function(item) {return item in this._items;},
  427. children: function() {return Object.keys(this._items);}
  428. };
  429. return this._watched[dir];
  430. };
  431. // File helpers
  432. // ------------
  433. // Private method: Check for read permissions
  434. // Based on this answer on SO: http://stackoverflow.com/a/11781404/1358405
  435. //
  436. // * stats - object, result of fs.stat
  437. //
  438. // Returns boolean
  439. FSWatcher.prototype._hasReadPermissions = function(stats) {
  440. return Boolean(4 & parseInt(((stats && stats.mode) & 0x1ff).toString(8)[0], 10));
  441. };
  442. // Private method: Handles emitting unlink events for
  443. // files and directories, and via recursion, for
  444. // files and directories within directories that are unlinked
  445. //
  446. // * directory - string, directory within which the following item is located
  447. // * item - string, base path of item/directory
  448. //
  449. // Returns nothing
  450. FSWatcher.prototype._remove = function(directory, item) {
  451. // if what is being deleted is a directory, get that directory's paths
  452. // for recursive deleting and cleaning of watched object
  453. // if it is not a directory, nestedDirectoryChildren will be empty array
  454. var path = sysPath.join(directory, item);
  455. var fullPath = sysPath.resolve(path);
  456. var isDirectory = this._watched[path] || this._watched[fullPath];
  457. // prevent duplicate handling in case of arriving here nearly simultaneously
  458. // via multiple paths (such as _handleFile and _handleDir)
  459. if (!this._throttle('remove', path, 100)) return;
  460. // if the only watched file is removed, watch for its return
  461. var watchedDirs = Object.keys(this._watched);
  462. if (!isDirectory && !this.options.useFsEvents && watchedDirs.length === 1) {
  463. this.add(directory, item, true);
  464. }
  465. // This will create a new entry in the watched object in either case
  466. // so we got to do the directory check beforehand
  467. var nestedDirectoryChildren = this._getWatchedDir(path).children();
  468. // Recursively remove children directories / files.
  469. nestedDirectoryChildren.forEach(function(nestedItem) {
  470. this._remove(path, nestedItem);
  471. }, this);
  472. // Check if item was on the watched list and remove it
  473. var parent = this._getWatchedDir(directory);
  474. var wasTracked = parent.has(item);
  475. parent.remove(item);
  476. // If we wait for this file to be fully written, cancel the wait.
  477. var relPath = path;
  478. if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);
  479. if (this.options.awaitWriteFinish && this._pendingWrites[relPath]) {
  480. var event = this._pendingWrites[relPath].cancelWait();
  481. if (event === 'add') return;
  482. }
  483. // The Entry will either be a directory that just got removed
  484. // or a bogus entry to a file, in either case we have to remove it
  485. delete this._watched[path];
  486. delete this._watched[fullPath];
  487. var eventName = isDirectory ? 'unlinkDir' : 'unlink';
  488. if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);
  489. // Avoid conflicts if we later create another file with the same name
  490. if (!this.options.useFsEvents) {
  491. this._closePath(path);
  492. }
  493. };
  494. FSWatcher.prototype._closePath = function(path) {
  495. if (!this._closers[path]) return;
  496. this._closers[path]();
  497. delete this._closers[path];
  498. this._getWatchedDir(sysPath.dirname(path)).remove(sysPath.basename(path));
  499. }
  500. // Public method: Adds paths to be watched on an existing FSWatcher instance
  501. // * paths - string or array of strings, file/directory paths and/or globs
  502. // * _origAdd - private boolean, for handling non-existent paths to be watched
  503. // * _internal - private boolean, indicates a non-user add
  504. // Returns an instance of FSWatcher for chaining.
  505. FSWatcher.prototype.add = function(paths, _origAdd, _internal) {
  506. var cwd = this.options.cwd;
  507. this.closed = false;
  508. paths = flatten(arrify(paths));
  509. if (!paths.every(isString)) {
  510. throw new TypeError('Non-string provided as watch path: ' + paths);
  511. }
  512. if (cwd) paths = paths.map(function(path) {
  513. if (isAbsolute(path)) {
  514. return path;
  515. } else if (path[0] === '!') {
  516. return '!' + sysPath.join(cwd, path.substring(1));
  517. } else {
  518. return sysPath.join(cwd, path);
  519. }
  520. });
  521. // set aside negated glob strings
  522. paths = paths.filter(function(path) {
  523. if (path[0] === '!') {
  524. this._ignoredPaths[path.substring(1)] = true;
  525. } else {
  526. // if a path is being added that was previously ignored, stop ignoring it
  527. delete this._ignoredPaths[path];
  528. delete this._ignoredPaths[path + '/**'];
  529. // reset the cached userIgnored anymatch fn
  530. // to make ignoredPaths changes effective
  531. this._userIgnored = null;
  532. return true;
  533. }
  534. }, this);
  535. if (this.options.useFsEvents && FsEventsHandler.canUse()) {
  536. if (!this._readyCount) this._readyCount = paths.length;
  537. if (this.options.persistent) this._readyCount *= 2;
  538. paths.forEach(this._addToFsEvents, this);
  539. } else {
  540. if (!this._readyCount) this._readyCount = 0;
  541. this._readyCount += paths.length;
  542. asyncEach(paths, function(path, next) {
  543. this._addToNodeFs(path, !_internal, 0, 0, _origAdd, function(err, res) {
  544. if (res) this._emitReady();
  545. next(err, res);
  546. }.bind(this));
  547. }.bind(this), function(error, results) {
  548. results.forEach(function(item) {
  549. if (!item || this.closed) return;
  550. this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));
  551. }, this);
  552. }.bind(this));
  553. }
  554. return this;
  555. };
  556. // Public method: Close watchers or start ignoring events from specified paths.
  557. // * paths - string or array of strings, file/directory paths and/or globs
  558. // Returns instance of FSWatcher for chaining.
  559. FSWatcher.prototype.unwatch = function(paths) {
  560. if (this.closed) return this;
  561. paths = flatten(arrify(paths));
  562. paths.forEach(function(path) {
  563. // convert to absolute path unless relative path already matches
  564. if (!isAbsolute(path) && !this._closers[path]) {
  565. if (this.options.cwd) path = sysPath.join(this.options.cwd, path);
  566. path = sysPath.resolve(path);
  567. }
  568. this._closePath(path);
  569. this._ignoredPaths[path] = true;
  570. if (path in this._watched) {
  571. this._ignoredPaths[path + '/**'] = true;
  572. }
  573. // reset the cached userIgnored anymatch fn
  574. // to make ignoredPaths changes effective
  575. this._userIgnored = null;
  576. }, this);
  577. return this;
  578. };
  579. // Public method: Close watchers and remove all listeners from watched paths.
  580. // Returns instance of FSWatcher for chaining.
  581. FSWatcher.prototype.close = function() {
  582. if (this.closed) return this;
  583. this.closed = true;
  584. Object.keys(this._closers).forEach(function(watchPath) {
  585. this._closers[watchPath]();
  586. delete this._closers[watchPath];
  587. }, this);
  588. this._watched = Object.create(null);
  589. this.removeAllListeners();
  590. return this;
  591. };
  592. // Public method: Expose list of watched paths
  593. // Returns object w/ dir paths as keys and arrays of contained paths as values.
  594. FSWatcher.prototype.getWatched = function() {
  595. var watchList = {};
  596. Object.keys(this._watched).forEach(function(dir) {
  597. var key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir;
  598. watchList[key || '.'] = Object.keys(this._watched[dir]._items).sort();
  599. }.bind(this));
  600. return watchList;
  601. };
  602. // Attach watch handler prototype methods
  603. function importHandler(handler) {
  604. Object.keys(handler.prototype).forEach(function(method) {
  605. FSWatcher.prototype[method] = handler.prototype[method];
  606. });
  607. }
  608. importHandler(NodeFsHandler);
  609. if (FsEventsHandler.canUse()) importHandler(FsEventsHandler);
  610. // Export FSWatcher class
  611. exports.FSWatcher = FSWatcher;
  612. // Public function: Instantiates watcher with paths to be tracked.
  613. // * paths - string or array of strings, file/directory paths and/or globs
  614. // * options - object, chokidar options
  615. // Returns an instance of FSWatcher for chaining.
  616. exports.watch = function(paths, options) {
  617. return new FSWatcher(options).add(paths);
  618. };