cache.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. var assert = require("assert");
  2. var Q = require("q");
  3. var fs = require("fs");
  4. var path = require("path");
  5. var util = require("./util");
  6. var EventEmitter = require("events").EventEmitter;
  7. var hasOwn = Object.prototype.hasOwnProperty;
  8. /**
  9. * ReadFileCache is an EventEmitter subclass that caches file contents in
  10. * memory so that subsequent calls to readFileP return the same contents,
  11. * regardless of any changes in the underlying file.
  12. */
  13. function ReadFileCache(sourceDir, charset) {
  14. assert.ok(this instanceof ReadFileCache);
  15. assert.strictEqual(typeof sourceDir, "string");
  16. this.charset = charset;
  17. EventEmitter.call(this);
  18. Object.defineProperties(this, {
  19. sourceDir: { value: sourceDir },
  20. sourceCache: { value: {} }
  21. });
  22. }
  23. util.inherits(ReadFileCache, EventEmitter);
  24. var RFCp = ReadFileCache.prototype;
  25. /**
  26. * Read a file from the cache if possible, else from disk.
  27. */
  28. RFCp.readFileP = function(relativePath) {
  29. var cache = this.sourceCache;
  30. relativePath = path.normalize(relativePath);
  31. return hasOwn.call(cache, relativePath)
  32. ? cache[relativePath]
  33. : this.noCacheReadFileP(relativePath);
  34. };
  35. /**
  36. * Read (or re-read) a file without using the cache.
  37. *
  38. * The new contents are stored in the cache for any future calls to
  39. * readFileP.
  40. */
  41. RFCp.noCacheReadFileP = function(relativePath) {
  42. relativePath = path.normalize(relativePath);
  43. var added = !hasOwn.call(this.sourceCache, relativePath);
  44. var promise = this.sourceCache[relativePath] = util.readFileP(
  45. path.join(this.sourceDir, relativePath), this.charset);
  46. if (added) {
  47. this.emit("added", relativePath);
  48. }
  49. return promise;
  50. };
  51. /**
  52. * If you have reason to believe the contents of a file have changed, call
  53. * this method to re-read the file and compare the new contents to the
  54. * cached contents. If the new contents differ from the contents of the
  55. * cache, the "changed" event will be emitted.
  56. */
  57. RFCp.reportPossiblyChanged = function(relativePath) {
  58. var self = this;
  59. var cached = self.readFileP(relativePath);
  60. var fresh = self.noCacheReadFileP(relativePath);
  61. Q.spread([
  62. cached.catch(orNull),
  63. fresh.catch(orNull)
  64. ], function(oldData, newData) {
  65. if (oldData !== newData) {
  66. self.emit("changed", relativePath);
  67. }
  68. }).done();
  69. };
  70. /**
  71. * Invoke the given callback for all files currently known to the
  72. * ReadFileCache, and invoke it in the future when any new files become
  73. * known to the cache.
  74. */
  75. RFCp.subscribe = function(callback, context) {
  76. for (var relativePath in this.sourceCache) {
  77. if (hasOwn.call(this.sourceCache, relativePath)) {
  78. callback.call(context || null, relativePath);
  79. }
  80. }
  81. this.on("added", function(relativePath) {
  82. callback.call(context || null, relativePath);
  83. });
  84. };
  85. /**
  86. * Avoid memory leaks by removing listeners and emptying the cache.
  87. */
  88. RFCp.clear = function() {
  89. this.removeAllListeners();
  90. for (var relativePath in this.sourceCache) {
  91. delete this.sourceCache[relativePath];
  92. }
  93. };
  94. function orNull(err) {
  95. return null;
  96. }
  97. exports.ReadFileCache = ReadFileCache;