This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
8.5 KiB
339 lines
8.5 KiB
'use strict'; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports.default = WatchmanWatcher; |
|
|
|
var _fs; |
|
|
|
function _load_fs() { |
|
return _fs = _interopRequireDefault(require('fs')); |
|
} |
|
|
|
var _path; |
|
|
|
function _load_path() { |
|
return _path = _interopRequireDefault(require('path')); |
|
} |
|
|
|
var _assert; |
|
|
|
function _load_assert() { |
|
return _assert = _interopRequireDefault(require('assert')); |
|
} |
|
|
|
var _common; |
|
|
|
function _load_common() { |
|
return _common = _interopRequireDefault(require('sane/src/common')); |
|
} |
|
|
|
var _fbWatchman; |
|
|
|
function _load_fbWatchman() { |
|
return _fbWatchman = _interopRequireDefault(require('fb-watchman')); |
|
} |
|
|
|
var _events; |
|
|
|
function _load_events() { |
|
return _events = require('events'); |
|
} |
|
|
|
var _recrawlWarningDedupe; |
|
|
|
function _load_recrawlWarningDedupe() { |
|
return _recrawlWarningDedupe = _interopRequireDefault(require('sane/src/utils/recrawl-warning-dedupe')); |
|
} |
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
|
|
|
const CHANGE_EVENT = (_common || _load_common()).default.CHANGE_EVENT; /** |
|
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
|
|
const DELETE_EVENT = (_common || _load_common()).default.DELETE_EVENT; |
|
const ADD_EVENT = (_common || _load_common()).default.ADD_EVENT; |
|
const ALL_EVENT = (_common || _load_common()).default.ALL_EVENT; |
|
const SUB_NAME = 'sane-sub'; |
|
|
|
/** |
|
* Watches `dir`. |
|
* |
|
* @class PollWatcher |
|
* @param String dir |
|
* @param {Object} opts |
|
* @public |
|
*/ |
|
|
|
function WatchmanWatcher(dir, opts) { |
|
(_common || _load_common()).default.assignOptions(this, opts); |
|
this.root = (_path || _load_path()).default.resolve(dir); |
|
this.init(); |
|
} |
|
|
|
// eslint-disable-next-line no-proto |
|
WatchmanWatcher.prototype.__proto__ = (_events || _load_events()).EventEmitter.prototype; |
|
|
|
/** |
|
* Run the watchman `watch` command on the root and subscribe to changes. |
|
* |
|
* @private |
|
*/ |
|
|
|
WatchmanWatcher.prototype.init = function () { |
|
if (this.client) { |
|
this.client.removeAllListeners(); |
|
} |
|
|
|
const self = this; |
|
this.client = new (_fbWatchman || _load_fbWatchman()).default.Client(); |
|
this.client.on('error', error => { |
|
self.emit('error', error); |
|
}); |
|
this.client.on('subscription', this.handleChangeEvent.bind(this)); |
|
this.client.on('end', () => { |
|
console.warn('[sane] Warning: Lost connection to watchman, reconnecting..'); |
|
self.init(); |
|
}); |
|
|
|
this.watchProjectInfo = null; |
|
|
|
function getWatchRoot() { |
|
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root; |
|
} |
|
|
|
function onCapability(error, resp) { |
|
if (handleError(self, error)) { |
|
// The Watchman watcher is unusable on this system, we cannot continue |
|
return; |
|
} |
|
|
|
handleWarning(resp); |
|
|
|
self.capabilities = resp.capabilities; |
|
|
|
if (self.capabilities.relative_root) { |
|
self.client.command(['watch-project', getWatchRoot()], onWatchProject); |
|
} else { |
|
self.client.command(['watch', getWatchRoot()], onWatch); |
|
} |
|
} |
|
|
|
function onWatchProject(error, resp) { |
|
if (handleError(self, error)) { |
|
return; |
|
} |
|
|
|
handleWarning(resp); |
|
|
|
self.watchProjectInfo = { |
|
relativePath: resp.relative_path ? resp.relative_path : '', |
|
root: resp.watch |
|
}; |
|
|
|
self.client.command(['clock', getWatchRoot()], onClock); |
|
} |
|
|
|
function onWatch(error, resp) { |
|
if (handleError(self, error)) { |
|
return; |
|
} |
|
|
|
handleWarning(resp); |
|
|
|
self.client.command(['clock', getWatchRoot()], onClock); |
|
} |
|
|
|
function onClock(error, resp) { |
|
if (handleError(self, error)) { |
|
return; |
|
} |
|
|
|
handleWarning(resp); |
|
|
|
const options = { |
|
fields: ['name', 'exists', 'new'], |
|
since: resp.clock |
|
}; |
|
|
|
// If the server has the wildmatch capability available it supports |
|
// the recursive **/*.foo style match and we can offload our globs |
|
// to the watchman server. This saves both on data size to be |
|
// communicated back to us and compute for evaluating the globs |
|
// in our node process. |
|
if (self.capabilities.wildmatch) { |
|
if (self.globs.length === 0) { |
|
if (!self.dot) { |
|
// Make sure we honor the dot option if even we're not using globs. |
|
options.expression = ['match', '**', 'wholename', { |
|
includedotfiles: false |
|
}]; |
|
} |
|
} else { |
|
options.expression = ['anyof']; |
|
for (const i in self.globs) { |
|
options.expression.push(['match', self.globs[i], 'wholename', { |
|
includedotfiles: self.dot |
|
}]); |
|
} |
|
} |
|
} |
|
|
|
if (self.capabilities.relative_root) { |
|
options.relative_root = self.watchProjectInfo.relativePath; |
|
} |
|
|
|
self.client.command(['subscribe', getWatchRoot(), SUB_NAME, options], onSubscribe); |
|
} |
|
|
|
function onSubscribe(error, resp) { |
|
if (handleError(self, error)) { |
|
return; |
|
} |
|
|
|
handleWarning(resp); |
|
|
|
self.emit('ready'); |
|
} |
|
|
|
self.client.capabilityCheck({ |
|
optional: ['wildmatch', 'relative_root'] |
|
}, onCapability); |
|
}; |
|
|
|
/** |
|
* Handles a change event coming from the subscription. |
|
* |
|
* @param {Object} resp |
|
* @private |
|
*/ |
|
|
|
WatchmanWatcher.prototype.handleChangeEvent = function (resp) { |
|
(_assert || _load_assert()).default.equal(resp.subscription, SUB_NAME, 'Invalid subscription event.'); |
|
if (resp.is_fresh_instance) { |
|
this.emit('fresh_instance'); |
|
} |
|
if (resp.is_fresh_instance) { |
|
this.emit('fresh_instance'); |
|
} |
|
if (Array.isArray(resp.files)) { |
|
resp.files.forEach(this.handleFileChange, this); |
|
} |
|
}; |
|
|
|
/** |
|
* Handles a single change event record. |
|
* |
|
* @param {Object} changeDescriptor |
|
* @private |
|
*/ |
|
|
|
WatchmanWatcher.prototype.handleFileChange = function (changeDescriptor) { |
|
const self = this; |
|
let absPath; |
|
let relativePath; |
|
|
|
if (this.capabilities.relative_root) { |
|
relativePath = changeDescriptor.name; |
|
absPath = (_path || _load_path()).default.join(this.watchProjectInfo.root, this.watchProjectInfo.relativePath, relativePath); |
|
} else { |
|
absPath = (_path || _load_path()).default.join(this.root, changeDescriptor.name); |
|
relativePath = changeDescriptor.name; |
|
} |
|
|
|
if (!(self.capabilities.wildmatch && !this.hasIgnore) && !(_common || _load_common()).default.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)) { |
|
return; |
|
} |
|
|
|
if (!changeDescriptor.exists) { |
|
self.emitEvent(DELETE_EVENT, relativePath, self.root); |
|
} else { |
|
(_fs || _load_fs()).default.lstat(absPath, (error, stat) => { |
|
// Files can be deleted between the event and the lstat call |
|
// the most reliable thing to do here is to ignore the event. |
|
if (error && error.code === 'ENOENT') { |
|
return; |
|
} |
|
|
|
if (handleError(self, error)) { |
|
return; |
|
} |
|
|
|
const eventType = changeDescriptor.new ? ADD_EVENT : CHANGE_EVENT; |
|
|
|
// Change event on dirs are mostly useless. |
|
if (!(eventType === CHANGE_EVENT && stat.isDirectory())) { |
|
self.emitEvent(eventType, relativePath, self.root, stat); |
|
} |
|
}); |
|
} |
|
}; |
|
|
|
/** |
|
* Dispatches the event. |
|
* |
|
* @param {string} eventType |
|
* @param {string} filepath |
|
* @param {string} root |
|
* @param {fs.Stat} stat |
|
* @private |
|
*/ |
|
|
|
WatchmanWatcher.prototype.emitEvent = function (eventType, filepath, root, stat) { |
|
this.emit(eventType, filepath, root, stat); |
|
this.emit(ALL_EVENT, eventType, filepath, root, stat); |
|
}; |
|
|
|
/** |
|
* Closes the watcher. |
|
* |
|
* @param {function} callback |
|
* @private |
|
*/ |
|
|
|
WatchmanWatcher.prototype.close = function (callback) { |
|
this.client.removeAllListeners(); |
|
this.client.end(); |
|
callback && callback(null, true); |
|
}; |
|
|
|
/** |
|
* Handles an error and returns true if exists. |
|
* |
|
* @param {WatchmanWatcher} self |
|
* @param {Error} error |
|
* @private |
|
*/ |
|
|
|
function handleError(self, error) { |
|
if (error != null) { |
|
self.emit('error', error); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* Handles a warning in the watchman resp object. |
|
* |
|
* @param {object} resp |
|
* @private |
|
*/ |
|
|
|
function handleWarning(resp) { |
|
if ('warning' in resp) { |
|
if ((_recrawlWarningDedupe || _load_recrawlWarningDedupe()).default.isRecrawlWarningDupe(resp.warning)) { |
|
return true; |
|
} |
|
console.warn(resp.warning); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |