UNPKG

7.61 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7class Storage {
8 constructor(duration) {
9 this.duration = duration;
10 this.running = new Map();
11 this.data = new Map();
12 this.levels = [];
13 if(duration > 0) {
14 this.levels.push(new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set());
15 for(let i = 8000; i < duration; i += 500)
16 this.levels.push(new Set());
17 }
18 this.count = 0;
19 this.interval = null;
20 this.needTickCheck = false;
21 this.nextTick = null;
22 this.passive = true;
23 this.tick = this.tick.bind(this);
24 }
25
26 ensureTick() {
27 if(!this.interval && this.duration > 0 && !this.nextTick)
28 this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length));
29 }
30
31 finished(name, err, result) {
32 const callbacks = this.running.get(name);
33 this.running.delete(name);
34 if(this.duration > 0) {
35 this.data.set(name, [err, result]);
36 const levelData = this.levels[0];
37 this.count -= levelData.size;
38 levelData.add(name);
39 this.count += levelData.size;
40 this.ensureTick();
41 }
42 for(let i = 0; i < callbacks.length; i++) {
43 callbacks[i](err, result);
44 }
45 }
46
47 finishedSync(name, err, result) {
48 if(this.duration > 0) {
49 this.data.set(name, [err, result]);
50 const levelData = this.levels[0];
51 this.count -= levelData.size;
52 levelData.add(name);
53 this.count += levelData.size;
54 this.ensureTick();
55 }
56 }
57
58 provide(name, provider, callback) {
59 if(typeof name !== "string") {
60 callback(new TypeError("path must be a string"));
61 return;
62 }
63 let running = this.running.get(name);
64 if(running) {
65 running.push(callback);
66 return;
67 }
68 if(this.duration > 0) {
69 this.checkTicks();
70 const data = this.data.get(name);
71 if(data) {
72 return process.nextTick(() => {
73 callback.apply(null, data);
74 });
75 }
76 }
77 this.running.set(name, running = [callback]);
78 provider(name, (err, result) => {
79 this.finished(name, err, result);
80 });
81 }
82
83 provideSync(name, provider) {
84 if(typeof name !== "string") {
85 throw new TypeError("path must be a string");
86 }
87 if(this.duration > 0) {
88 this.checkTicks();
89 const data = this.data.get(name);
90 if(data) {
91 if(data[0])
92 throw data[0];
93 return data[1];
94 }
95 }
96 let result;
97 try {
98 result = provider(name);
99 } catch(e) {
100 this.finishedSync(name, e);
101 throw e;
102 }
103 this.finishedSync(name, null, result);
104 return result;
105 }
106
107 tick() {
108 const decay = this.levels.pop();
109 for(let item of decay) {
110 this.data.delete(item);
111 }
112 this.count -= decay.size;
113 decay.clear();
114 this.levels.unshift(decay);
115 if(this.count === 0) {
116 clearInterval(this.interval);
117 this.interval = null;
118 this.nextTick = null;
119 return true;
120 } else if(this.nextTick) {
121 this.nextTick += Math.floor(this.duration / this.levels.length);
122 const time = new Date().getTime();
123 if(this.nextTick > time) {
124 this.nextTick = null;
125 this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length));
126 return true;
127 }
128 } else if(this.passive) {
129 clearInterval(this.interval);
130 this.interval = null;
131 this.nextTick = new Date().getTime() + Math.floor(this.duration / this.levels.length);
132 } else {
133 this.passive = true;
134 }
135 }
136
137 checkTicks() {
138 this.passive = false;
139 if(this.nextTick) {
140 while(!this.tick());
141 }
142 }
143
144 purge(what) {
145 if(!what) {
146 this.count = 0;
147 clearInterval(this.interval);
148 this.nextTick = null;
149 this.data.clear();
150 this.levels.forEach(level => {
151 level.clear();
152 });
153 } else if(typeof what === "string") {
154 for(let key of this.data.keys()) {
155 if(key.startsWith(what))
156 this.data.delete(key);
157 }
158 } else {
159 for(let i = what.length - 1; i >= 0; i--) {
160 this.purge(what[i]);
161 }
162 }
163 }
164}
165
166module.exports = class CachedInputFileSystem {
167 constructor(fileSystem, duration) {
168 this.fileSystem = fileSystem;
169 this._statStorage = new Storage(duration);
170 this._readdirStorage = new Storage(duration);
171 this._readFileStorage = new Storage(duration);
172 this._readJsonStorage = new Storage(duration);
173 this._readlinkStorage = new Storage(duration);
174
175 this._stat = this.fileSystem.stat ? this.fileSystem.stat.bind(this.fileSystem) : null;
176 if(!this._stat) this.stat = null;
177
178 this._statSync = this.fileSystem.statSync ? this.fileSystem.statSync.bind(this.fileSystem) : null;
179 if(!this._statSync) this.statSync = null;
180
181 this._readdir = this.fileSystem.readdir ? this.fileSystem.readdir.bind(this.fileSystem) : null;
182 if(!this._readdir) this.readdir = null;
183
184 this._readdirSync = this.fileSystem.readdirSync ? this.fileSystem.readdirSync.bind(this.fileSystem) : null;
185 if(!this._readdirSync) this.readdirSync = null;
186
187 this._readFile = this.fileSystem.readFile ? this.fileSystem.readFile.bind(this.fileSystem) : null;
188 if(!this._readFile) this.readFile = null;
189
190 this._readFileSync = this.fileSystem.readFileSync ? this.fileSystem.readFileSync.bind(this.fileSystem) : null;
191 if(!this._readFileSync) this.readFileSync = null;
192
193 if(this.fileSystem.readJson) {
194 this._readJson = this.fileSystem.readJson.bind(this.fileSystem);
195 } else if(this.readFile) {
196 this._readJson = (path, callback) => {
197 this.readFile(path, (err, buffer) => {
198 if(err) return callback(err);
199 let data;
200 try {
201 data = JSON.parse(buffer.toString("utf-8"));
202 } catch(e) {
203 return callback(e);
204 }
205 callback(null, data);
206 });
207 };
208 } else {
209 this.readJson = null;
210 }
211 if(this.fileSystem.readJsonSync) {
212 this._readJsonSync = this.fileSystem.readJsonSync.bind(this.fileSystem);
213 } else if(this.readFileSync) {
214 this._readJsonSync = (path) => {
215 const buffer = this.readFileSync(path);
216 const data = JSON.parse(buffer.toString("utf-8"));
217 return data;
218 };
219 } else {
220 this.readJsonSync = null;
221 }
222
223 this._readlink = this.fileSystem.readlink ? this.fileSystem.readlink.bind(this.fileSystem) : null;
224 if(!this._readlink) this.readlink = null;
225
226 this._readlinkSync = this.fileSystem.readlinkSync ? this.fileSystem.readlinkSync.bind(this.fileSystem) : null;
227 if(!this._readlinkSync) this.readlinkSync = null;
228 }
229
230 stat(path, callback) {
231 this._statStorage.provide(path, this._stat, callback);
232 }
233
234 readdir(path, callback) {
235 this._readdirStorage.provide(path, this._readdir, callback);
236 }
237
238 readFile(path, callback) {
239 this._readFileStorage.provide(path, this._readFile, callback);
240 }
241
242 readJson(path, callback) {
243 this._readJsonStorage.provide(path, this._readJson, callback);
244 }
245
246 readlink(path, callback) {
247 this._readlinkStorage.provide(path, this._readlink, callback);
248 }
249
250 statSync(path) {
251 return this._statStorage.provideSync(path, this._statSync);
252 }
253
254 readdirSync(path) {
255 return this._readdirStorage.provideSync(path, this._readdirSync);
256 }
257
258 readFileSync(path) {
259 return this._readFileStorage.provideSync(path, this._readFileSync);
260 }
261
262 readJsonSync(path) {
263 return this._readJsonStorage.provideSync(path, this._readJsonSync);
264 }
265
266 readlinkSync(path) {
267 return this._readlinkStorage.provideSync(path, this._readlinkSync);
268 }
269
270 purge(what) {
271 this._statStorage.purge(what);
272 this._readdirStorage.purge(what);
273 this._readFileStorage.purge(what);
274 this._readlinkStorage.purge(what);
275 this._readJsonStorage.purge(what);
276 }
277};