/* * gaze * https://github.com/shama/gaze * * Copyright (c) 2013 Kyle Robinson Young * Licensed under the MIT license. */ 'use strict'; // libs var util = require('util'); var EE = require('events').EventEmitter; var fs = require('fs'); var path = require('path'); var globule = require('globule'); var helper = require('./helper'); // shim setImmediate for node v0.8 var setImmediate = require('timers').setImmediate; if (typeof setImmediate !== 'function') { setImmediate = process.nextTick; } // globals var delay = 10; // `Gaze` EventEmitter object to return in the callback function Gaze(patterns, opts, done) { var self = this; EE.call(self); // If second arg is the callback if (typeof opts === 'function') { done = opts; opts = {}; } // Default options opts = opts || {}; opts.mark = true; opts.interval = opts.interval || 100; opts.debounceDelay = opts.debounceDelay || 500; opts.cwd = opts.cwd || process.cwd(); this.options = opts; // Default done callback done = done || function() {}; // Remember our watched dir:files this._watched = Object.create(null); // Store watchers this._watchers = Object.create(null); // Store watchFile listeners this._pollers = Object.create(null); // Store patterns this._patterns = []; // Cached events for debouncing this._cached = Object.create(null); // Set maxListeners if (this.options.maxListeners) { this.setMaxListeners(this.options.maxListeners); Gaze.super_.prototype.setMaxListeners(this.options.maxListeners); delete this.options.maxListeners; } // Initialize the watch on files if (patterns) { this.add(patterns, done); } // keep the process alive this._keepalive = setInterval(function() {}, 200); return this; } util.inherits(Gaze, EE); // Main entry point. Start watching and call done when setup module.exports = function gaze(patterns, opts, done) { return new Gaze(patterns, opts, done); }; module.exports.Gaze = Gaze; // Override the emit function to emit `all` events // and debounce on duplicate events per file Gaze.prototype.emit = function() { var self = this; var args = arguments; var e = args[0]; var filepath = args[1]; var timeoutId; // If not added/deleted/changed/renamed then just emit the event if (e.slice(-2) !== 'ed') { Gaze.super_.prototype.emit.apply(self, args); return this; } // Detect rename event, if added and previous deleted is in the cache if (e === 'added') { Object.keys(this._cached).forEach(function(oldFile) { if (self._cached[oldFile].indexOf('deleted') !== -1) { args[0] = e = 'renamed'; [].push.call(args, oldFile); delete self._cached[oldFile]; return false; } }); } // If cached doesnt exist, create a delay before running the next // then emit the event var cache = this._cached[filepath] || []; if (cache.indexOf(e) === -1) { helper.objectPush(self._cached, filepath, e); clearTimeout(timeoutId); timeoutId = setTimeout(function() { delete self._cached[filepath]; }, this.options.debounceDelay); // Emit the event and `all` event Gaze.super_.prototype.emit.apply(self, args); Gaze.super_.prototype.emit.apply(self, ['all', e].concat([].slice.call(args, 1))); } // Detect if new folder added to trigger for matching files within folder if (e === 'added') { if (helper.isDir(filepath)) { fs.readdirSync(filepath).map(function(file) { return path.join(filepath, file); }).filter(function(file) { return globule.isMatch(self._patterns, file, self.options); }).forEach(function(file) { self.emit('added', file); }); } } return this; }; // Close watchers Gaze.prototype.close = function(_reset) { var self = this; _reset = _reset === false ? false : true; Object.keys(self._watchers).forEach(function(file) { self._watchers[file].close(); }); self._watchers = Object.create(null); Object.keys(this._watched).forEach(function(dir) { self._unpollDir(dir); }); if (_reset) { self._watched = Object.create(null); setTimeout(function() { self.emit('end'); self.removeAllListeners(); clearInterval(self._keepalive); }, delay + 100); } return self; }; // Add file patterns to be watched Gaze.prototype.add = function(files, done) { if (typeof files === 'string') { files = [files]; } this._patterns = helper.unique.apply(null, [this._patterns, files]); files = globule.find(this._patterns, this.options); this._addToWatched(files); this.close(false); this._initWatched(done); }; // Dont increment patterns and dont call done if nothing added Gaze.prototype._internalAdd = function(file, done) { var files = []; if (helper.isDir(file)) { files = [helper.markDir(file)].concat(globule.find(this._patterns, this.options)); } else { if (globule.isMatch(this._patterns, file, this.options)) { files = [file]; } } if (files.length > 0) { this._addToWatched(files); this.close(false); this._initWatched(done); } }; // Remove file/dir from `watched` Gaze.prototype.remove = function(file) { var self = this; if (this._watched[file]) { // is dir, remove all files this._unpollDir(file); delete this._watched[file]; } else { // is a file, find and remove Object.keys(this._watched).forEach(function(dir) { var index = self._watched[dir].indexOf(file); if (index !== -1) { self._unpollFile(file); self._watched[dir].splice(index, 1); return false; } }); } if (this._watchers[file]) { this._watchers[file].close(); } return this; }; // Return watched files Gaze.prototype.watched = function() { return this._watched; }; // Returns `watched` files with relative paths to process.cwd() Gaze.prototype.relative = function(dir, unixify) { var self = this; var relative = Object.create(null); var relDir, relFile, unixRelDir; var cwd = this.options.cwd || process.cwd(); if (dir === '') { dir = '.'; } dir = helper.markDir(dir); unixify = unixify || false; Object.keys(this._watched).forEach(function(dir) { relDir = path.relative(cwd, dir) + path.sep; if (relDir === path.sep) { relDir = '.'; } unixRelDir = unixify ? helper.unixifyPathSep(relDir) : relDir; relative[unixRelDir] = self._watched[dir].map(function(file) { relFile = path.relative(path.join(cwd, relDir) || '', file || ''); if (helper.isDir(file)) { relFile = helper.markDir(relFile); } if (unixify) { relFile = helper.unixifyPathSep(relFile); } return relFile; }); }); if (dir && unixify) { dir = helper.unixifyPathSep(dir); } return dir ? relative[dir] || [] : relative; }; // Adds files and dirs to watched Gaze.prototype._addToWatched = function(files) { for (var i = 0; i < files.length; i++) { var file = files[i]; var filepath = path.resolve(this.options.cwd, file); var dirname = (helper.isDir(file)) ? filepath : path.dirname(filepath); dirname = helper.markDir(dirname); // If a new dir is added if (helper.isDir(file) && !(filepath in this._watched)) { helper.objectPush(this._watched, filepath, []); } if (file.slice(-1) === '/') { filepath += path.sep; } helper.objectPush(this._watched, path.dirname(filepath) + path.sep, filepath); // add folders into the mix var readdir = fs.readdirSync(dirname); for (var j = 0; j < readdir.length; j++) { var dirfile = path.join(dirname, readdir[j]); if (fs.lstatSync(dirfile).isDirectory()) { helper.objectPush(this._watched, dirname, dirfile + path.sep); } } } return this; }; Gaze.prototype._watchDir = function(dir, done) { var self = this; var timeoutId; try { this._watchers[dir] = fs.watch(dir, function(event) { // race condition. Let's give the fs a little time to settle down. so we // don't fire events on non existent files. clearTimeout(timeoutId); timeoutId = setTimeout(function() { // race condition. Ensure that this directory is still being watched // before continuing. if ((dir in self._watchers) && fs.existsSync(dir)) { done(null, dir); } }, delay + 100); }); } catch (err) { return this._handleError(err); } return this; }; Gaze.prototype._unpollFile = function(file) { if (this._pollers[file]) { fs.unwatchFile(file, this._pollers[file] ); delete this._pollers[file]; } return this; }; Gaze.prototype._unpollDir = function(dir) { this._unpollFile(dir); for (var i = 0; i < this._watched[dir].length; i++) { this._unpollFile(this._watched[dir][i]); } }; Gaze.prototype._pollFile = function(file, done) { var opts = { persistent: true, interval: this.options.interval }; if (!this._pollers[file]) { this._pollers[file] = function(curr, prev) { done(null, file); }; try { fs.watchFile(file, opts, this._pollers[file]); } catch (err) { return this._handleError(err); } } return this; }; // Initialize the actual watch on `watched` files Gaze.prototype._initWatched = function(done) { var self = this; var cwd = this.options.cwd || process.cwd(); var curWatched = Object.keys(self._watched); // if no matching files if (curWatched.length < 1) { // Defer to emitting to give a chance to attach event handlers. setImmediate(function () { self.emit('ready', self); if (done) { done.call(self, null, self); } self.emit('nomatch'); }); return; } helper.forEachSeries(curWatched, function(dir, next) { dir = dir || ''; var files = self._watched[dir]; // Triggered when a watched dir has an event self._watchDir(dir, function(event, dirpath) { var relDir = cwd === dir ? '.' : path.relative(cwd, dir); relDir = relDir || ''; fs.readdir(dirpath, function(err, current) { if (err) { return self.emit('error', err); } if (!current) { return; } try { // append path.sep to directories so they match previous. current = current.map(function(curPath) { if (fs.existsSync(path.join(dir, curPath)) && fs.lstatSync(path.join(dir, curPath)).isDirectory()) { return curPath + path.sep; } else { return curPath; } }); } catch (err) { // race condition-- sometimes the file no longer exists } // Get watched files for this dir var previous = self.relative(relDir); // If file was deleted previous.filter(function(file) { return current.indexOf(file) < 0; }).forEach(function(file) { if (!helper.isDir(file)) { var filepath = path.join(dir, file); self.remove(filepath); self.emit('deleted', filepath); } }); // If file was added current.filter(function(file) { return previous.indexOf(file) < 0; }).forEach(function(file) { // Is it a matching pattern? var relFile = path.join(relDir, file); // Add to watch then emit event self._internalAdd(relFile, function() { self.emit('added', path.join(dir, file)); }); }); }); }); // Watch for change/rename events on files files.forEach(function(file) { if (helper.isDir(file)) { return; } self._pollFile(file, function(err, filepath) { // Only emit changed if the file still exists // Prevents changed/deleted duplicate events if (fs.existsSync(filepath)) { self.emit('changed', filepath); } }); }); next(); }, function() { // Return this instance of Gaze // delay before ready solves a lot of issues setTimeout(function() { self.emit('ready', self); if (done) { done.call(self, null, self); } }, delay + 100); }); }; // If an error, handle it here Gaze.prototype._handleError = function(err) { if (err.code === 'EMFILE') { return this.emit('error', new Error('EMFILE: Too many opened files.')); } return this.emit('error', err); };
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19553 | swellard | Move and rename clients | ||
//guest/perforce_software/helix-web-services/main/source/clients/2016.1.0/javascript/node_modules/gaze/lib/gaze.js | |||||
#1 | 18810 | tjuricek |
First-pass at JavaScript client SDK. JavaScript requires Node with Gulp to "browserfy" the library. It's the easiest way I found to use the swagger-js project; bundle up a wrapping method. There is no JavaScript reference guide. The swagger-js doesn't really document what they do very well, actually. Overall I'm not particularly impressed by swagger-js, it was hard to even figure out what the right method syntax was. We may want to invest time in doing it better. This required setting CORS response headers, which are currently defaulted to a fairly insecure setting. |