1 | var fs = require('fs');
|
2 | var Utils = require('./HappyUtils');
|
3 | var assert = require('assert');
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | module.exports = function HappyFSCache(config) {
|
18 | var exports = {};
|
19 | var id = config.id;
|
20 | var cachePath = config.path;
|
21 | var cache = { context: {}, mtimes: {} };
|
22 | var generateSignature = config.generateSignature || getMTime;
|
23 |
|
24 | assert(typeof cachePath === 'string',
|
25 | "HappyFSCache requires a @path parameter that points to where it will be stored.");
|
26 |
|
27 | |
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | exports.load = function(currentContext) {
|
39 | var oldCache, staleEntryCount;
|
40 |
|
41 | cache.context = currentContext;
|
42 |
|
43 | assert(typeof currentContext === 'object' && !!currentContext,
|
44 | "HappyFSCache requires a @context parameter to work.");
|
45 |
|
46 | if (!Utils.isReadable(cachePath)) {
|
47 | if (config.verbose) {
|
48 | console.log('Happy[%s]: No cache was found, starting fresh.', id);
|
49 | }
|
50 |
|
51 | return false;
|
52 | }
|
53 |
|
54 | try {
|
55 | oldCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
56 | } catch(e) {
|
57 | oldCache = null;
|
58 | }
|
59 |
|
60 | if (!oldCache || toJSON(oldCache.context) !== toJSON(currentContext)) {
|
61 | if (config.verbose) {
|
62 | console.log('Happy[%s]: Cache is no longer valid, starting fresh.', id);
|
63 | }
|
64 |
|
65 | return false;
|
66 | }
|
67 |
|
68 | cache.mtimes = oldCache.mtimes;
|
69 | cache.context = currentContext;
|
70 |
|
71 | staleEntryCount = removeStaleEntries(cache.mtimes, generateSignature);
|
72 |
|
73 | if (config.verbose) {
|
74 | console.log('Happy[%s]: Loaded %d entries from cache. (%d were stale)',
|
75 | id,
|
76 | Object.keys(cache.mtimes).length,
|
77 | staleEntryCount
|
78 | );
|
79 | }
|
80 |
|
81 | return true;
|
82 | };
|
83 |
|
84 | exports.save = function() {
|
85 | fs.writeFileSync(cachePath, JSON.stringify(cache));
|
86 | };
|
87 |
|
88 | exports.getCompiledSourceCodePath = function(filePath) {
|
89 | return cache.mtimes[filePath] && cache.mtimes[filePath].compiledPath;
|
90 | };
|
91 |
|
92 | exports.getCompiledSourceMapPath = function(filePath) {
|
93 | return cache.mtimes[filePath] && cache.mtimes[filePath].compiledPath + '.map';
|
94 | };
|
95 |
|
96 | exports.hasChanged = function(filePath) {
|
97 | var nowMTime = generateSignature(filePath);
|
98 | var lastMTime = getSignatureAtCompilationTime(filePath);
|
99 |
|
100 | return nowMTime !== lastMTime;
|
101 | };
|
102 |
|
103 | exports.hasErrored = function(filePath) {
|
104 | return cache.mtimes[filePath] && cache.mtimes[filePath].error;
|
105 | };
|
106 |
|
107 | exports.invalidateEntryFor = function(filePath) {
|
108 | delete cache.mtimes[filePath];
|
109 | };
|
110 |
|
111 | exports.updateMTimeFor = function(filePath, compiledPath, error) {
|
112 | cache.mtimes[filePath] = {
|
113 | mtime: generateSignature(filePath),
|
114 | compiledPath: compiledPath,
|
115 | error: error
|
116 | };
|
117 | };
|
118 |
|
119 | exports.dump = function() {
|
120 | return cache;
|
121 | };
|
122 |
|
123 | function getSignatureAtCompilationTime(filePath) {
|
124 | if (cache.mtimes[filePath]) {
|
125 | return cache.mtimes[filePath].mtime;
|
126 | }
|
127 | }
|
128 |
|
129 | return exports;
|
130 | }
|
131 |
|
132 | function toJSON(object) {
|
133 | return JSON.stringify(object);
|
134 | }
|
135 |
|
136 | function getMTime(filePath) {
|
137 | try {
|
138 | return fs.statSync(filePath).mtime.getTime();
|
139 | }
|
140 | catch (e) {
|
141 | return -1;
|
142 | }
|
143 | }
|
144 |
|
145 | function removeStaleEntries(mtimes, generateSignature) {
|
146 | return Object.keys(mtimes).reduce(function(acc, filePath) {
|
147 | var entry = mtimes[filePath];
|
148 |
|
149 | if (isStale(entry, filePath)) {
|
150 | delete mtimes[filePath];
|
151 | return acc + 1;
|
152 | }
|
153 |
|
154 | return acc;
|
155 | }, 0);
|
156 |
|
157 | function isStale(entry, filePath) {
|
158 | return (
|
159 |
|
160 | !Utils.isReadable(filePath)
|
161 |
|
162 | || !Utils.isReadable(entry.compiledPath)
|
163 |
|
164 | || entry.mtime !== generateSignature(filePath)
|
165 | );
|
166 | }
|
167 | }
|