Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
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.

NodeWatcher.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // vendored from https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/node_watcher.js
  2. 'use strict';
  3. const EventEmitter = require('events').EventEmitter;
  4. const fs = require('fs');
  5. const platform = require('os').platform();
  6. const path = require('path');
  7. const common = require('./common');
  8. /**
  9. * Constants
  10. */
  11. const DEFAULT_DELAY = common.DEFAULT_DELAY;
  12. const CHANGE_EVENT = common.CHANGE_EVENT;
  13. const DELETE_EVENT = common.DELETE_EVENT;
  14. const ADD_EVENT = common.ADD_EVENT;
  15. const ALL_EVENT = common.ALL_EVENT;
  16. /**
  17. * Export `NodeWatcher` class.
  18. * Watches `dir`.
  19. *
  20. * @class NodeWatcher
  21. * @param {String} dir
  22. * @param {Object} opts
  23. * @public
  24. */
  25. module.exports = class NodeWatcher extends EventEmitter {
  26. constructor(dir, opts) {
  27. super();
  28. common.assignOptions(this, opts);
  29. this.watched = Object.create(null);
  30. this.changeTimers = Object.create(null);
  31. this.dirRegistery = Object.create(null);
  32. this.root = path.resolve(dir);
  33. this.watchdir = this.watchdir.bind(this);
  34. this.register = this.register.bind(this);
  35. this.checkedEmitError = this.checkedEmitError.bind(this);
  36. this.watchdir(this.root);
  37. common.recReaddir(
  38. this.root,
  39. this.watchdir,
  40. this.register,
  41. this.emit.bind(this, 'ready'),
  42. this.checkedEmitError,
  43. this.ignored
  44. );
  45. }
  46. /**
  47. * Register files that matches our globs to know what to type of event to
  48. * emit in the future.
  49. *
  50. * Registery looks like the following:
  51. *
  52. * dirRegister => Map {
  53. * dirpath => Map {
  54. * filename => true
  55. * }
  56. * }
  57. *
  58. * @param {string} filepath
  59. * @return {boolean} whether or not we have registered the file.
  60. * @private
  61. */
  62. register(filepath) {
  63. const relativePath = path.relative(this.root, filepath);
  64. if (
  65. !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
  66. ) {
  67. return false;
  68. }
  69. const dir = path.dirname(filepath);
  70. if (!this.dirRegistery[dir]) {
  71. this.dirRegistery[dir] = Object.create(null);
  72. }
  73. const filename = path.basename(filepath);
  74. this.dirRegistery[dir][filename] = true;
  75. return true;
  76. }
  77. /**
  78. * Removes a file from the registery.
  79. *
  80. * @param {string} filepath
  81. * @private
  82. */
  83. unregister(filepath) {
  84. const dir = path.dirname(filepath);
  85. if (this.dirRegistery[dir]) {
  86. const filename = path.basename(filepath);
  87. delete this.dirRegistery[dir][filename];
  88. }
  89. }
  90. /**
  91. * Removes a dir from the registery.
  92. *
  93. * @param {string} dirpath
  94. * @private
  95. */
  96. unregisterDir(dirpath) {
  97. if (this.dirRegistery[dirpath]) {
  98. delete this.dirRegistery[dirpath];
  99. }
  100. }
  101. /**
  102. * Checks if a file or directory exists in the registery.
  103. *
  104. * @param {string} fullpath
  105. * @return {boolean}
  106. * @private
  107. */
  108. registered(fullpath) {
  109. const dir = path.dirname(fullpath);
  110. return (
  111. this.dirRegistery[fullpath] ||
  112. (this.dirRegistery[dir] &&
  113. this.dirRegistery[dir][path.basename(fullpath)])
  114. );
  115. }
  116. /**
  117. * Emit "error" event if it's not an ignorable event
  118. *
  119. * @param error
  120. * @private
  121. */
  122. checkedEmitError(error) {
  123. if (!isIgnorableFileError(error)) {
  124. this.emit('error', error);
  125. }
  126. }
  127. /**
  128. * Watch a directory.
  129. *
  130. * @param {string} dir
  131. * @private
  132. */
  133. watchdir(dir) {
  134. if (this.watched[dir]) {
  135. return;
  136. }
  137. const watcher = fs.watch(
  138. dir,
  139. {
  140. persistent: true
  141. },
  142. this.normalizeChange.bind(this, dir)
  143. );
  144. this.watched[dir] = watcher;
  145. watcher.on('error', this.checkedEmitError);
  146. if (this.root !== dir) {
  147. this.register(dir);
  148. }
  149. }
  150. /**
  151. * Stop watching a directory.
  152. *
  153. * @param {string} dir
  154. * @private
  155. */
  156. stopWatching(dir) {
  157. if (this.watched[dir]) {
  158. this.watched[dir].close();
  159. delete this.watched[dir];
  160. }
  161. }
  162. /**
  163. * End watching.
  164. *
  165. * @public
  166. */
  167. close() {
  168. Object.keys(this.watched).forEach(this.stopWatching, this);
  169. this.removeAllListeners();
  170. return Promise.resolve();
  171. }
  172. /**
  173. * On some platforms, as pointed out on the fs docs (most likely just win32)
  174. * the file argument might be missing from the fs event. Try to detect what
  175. * change by detecting if something was deleted or the most recent file change.
  176. *
  177. * @param {string} dir
  178. * @param {string} event
  179. * @param {string} file
  180. * @public
  181. */
  182. detectChangedFile(dir, event, callback) {
  183. if (!this.dirRegistery[dir]) {
  184. return;
  185. }
  186. let found = false;
  187. let closest = {
  188. mtime: 0
  189. };
  190. let c = 0;
  191. Object.keys(this.dirRegistery[dir]).forEach(function (file, i, arr) {
  192. fs.lstat(path.join(dir, file), (error, stat) => {
  193. if (found) {
  194. return;
  195. }
  196. if (error) {
  197. if (isIgnorableFileError(error)) {
  198. found = true;
  199. callback(file);
  200. } else {
  201. this.emit('error', error);
  202. }
  203. } else {
  204. if (stat.mtime > closest.mtime) {
  205. stat.file = file;
  206. closest = stat;
  207. }
  208. if (arr.length === ++c) {
  209. callback(closest.file);
  210. }
  211. }
  212. });
  213. }, this);
  214. }
  215. /**
  216. * Normalize fs events and pass it on to be processed.
  217. *
  218. * @param {string} dir
  219. * @param {string} event
  220. * @param {string} file
  221. * @public
  222. */
  223. normalizeChange(dir, event, file) {
  224. if (!file) {
  225. this.detectChangedFile(dir, event, actualFile => {
  226. if (actualFile) {
  227. this.processChange(dir, event, actualFile);
  228. }
  229. });
  230. } else {
  231. this.processChange(dir, event, path.normalize(file));
  232. }
  233. }
  234. /**
  235. * Process changes.
  236. *
  237. * @param {string} dir
  238. * @param {string} event
  239. * @param {string} file
  240. * @public
  241. */
  242. processChange(dir, event, file) {
  243. const fullPath = path.join(dir, file);
  244. const relativePath = path.join(path.relative(this.root, dir), file);
  245. fs.lstat(fullPath, (error, stat) => {
  246. if (error && error.code !== 'ENOENT') {
  247. this.emit('error', error);
  248. } else if (!error && stat.isDirectory()) {
  249. // win32 emits usless change events on dirs.
  250. if (event !== 'change') {
  251. this.watchdir(fullPath);
  252. if (
  253. common.isFileIncluded(
  254. this.globs,
  255. this.dot,
  256. this.doIgnore,
  257. relativePath
  258. )
  259. ) {
  260. this.emitEvent(ADD_EVENT, relativePath, stat);
  261. }
  262. }
  263. } else {
  264. const registered = this.registered(fullPath);
  265. if (error && error.code === 'ENOENT') {
  266. this.unregister(fullPath);
  267. this.stopWatching(fullPath);
  268. this.unregisterDir(fullPath);
  269. if (registered) {
  270. this.emitEvent(DELETE_EVENT, relativePath);
  271. }
  272. } else if (registered) {
  273. this.emitEvent(CHANGE_EVENT, relativePath, stat);
  274. } else {
  275. if (this.register(fullPath)) {
  276. this.emitEvent(ADD_EVENT, relativePath, stat);
  277. }
  278. }
  279. }
  280. });
  281. }
  282. /**
  283. * Triggers a 'change' event after debounding it to take care of duplicate
  284. * events on os x.
  285. *
  286. * @private
  287. */
  288. emitEvent(type, file, stat) {
  289. const key = type + '-' + file;
  290. const addKey = ADD_EVENT + '-' + file;
  291. if (type === CHANGE_EVENT && this.changeTimers[addKey]) {
  292. // Ignore the change event that is immediately fired after an add event.
  293. // (This happens on Linux).
  294. return;
  295. }
  296. clearTimeout(this.changeTimers[key]);
  297. this.changeTimers[key] = setTimeout(() => {
  298. delete this.changeTimers[key];
  299. if (type === ADD_EVENT && stat.isDirectory()) {
  300. // Recursively emit add events and watch for sub-files/folders
  301. common.recReaddir(
  302. path.resolve(this.root, file),
  303. function emitAddDir(dir, stats) {
  304. this.watchdir(dir);
  305. this.rawEmitEvent(ADD_EVENT, path.relative(this.root, dir), stats);
  306. }.bind(this),
  307. function emitAddFile(file, stats) {
  308. this.register(file);
  309. this.rawEmitEvent(ADD_EVENT, path.relative(this.root, file), stats);
  310. }.bind(this),
  311. function endCallback() {},
  312. this.checkedEmitError,
  313. this.ignored
  314. );
  315. } else {
  316. this.rawEmitEvent(type, file, stat);
  317. }
  318. }, DEFAULT_DELAY);
  319. }
  320. /**
  321. * Actually emit the events
  322. */
  323. rawEmitEvent(type, file, stat) {
  324. this.emit(type, file, this.root, stat);
  325. this.emit(ALL_EVENT, type, file, this.root, stat);
  326. }
  327. };
  328. /**
  329. * Determine if a given FS error can be ignored
  330. *
  331. * @private
  332. */
  333. function isIgnorableFileError(error) {
  334. return (
  335. error.code === 'ENOENT' || (error.code === 'EPERM' && platform === 'win32')
  336. );
  337. }