UNPKG

13.4 kBJavaScriptView Raw
1var async = require("async");
2var path = require("path");
3var fs = require("fs");
4
5var util = require("../util/util");
6var renditions = require("../util/renditions");
7
8/**
9 * Helper functions for Dust Tags
10 *
11 * @type {Function}
12 */
13exports = module.exports = function(dust)
14{
15 var r = {};
16
17 // used to check whether a parameter is defined, meaning it has a value and the value is NON EMPTY.
18 var isDefined = r.isDefined = function(thing)
19 {
20 return ( (typeof(thing) !== "undefined") && (thing !== "") );
21 };
22
23 /**
24 * Determines whether to use the fragment cache. We use this cache if we're instructed to and if we're in
25 * production model.
26 *
27 * @returns {boolean}
28 */
29 var isFragmentCacheEnabled = r.isFragmentCacheEnabled = function()
30 {
31 if (!process.configuration.duster) {
32 process.configuration.duster = {};
33 }
34 if (!process.configuration.duster.fragments) {
35 process.configuration.duster.fragments = {};
36 }
37 if (typeof(process.configuration.duster.fragments.cache) === "undefined") {
38 process.configuration.duster.fragments.cache = true;
39 }
40
41 if (process.env.FORCE_CLOUDCMS_DUST_FRAGMENT_CACHE === "true")
42 {
43 process.configuration.duster.fragments.cache = true;
44 }
45 else if (process.env.FORCE_CLOUDCMS_DUST_FRAGMENT_CACHE === "false")
46 {
47 process.configuration.duster.fragments.cache = false;
48 }
49
50 if (process.env.CLOUDCMS_APPSERVER_MODE !== "production") {
51 process.configuration.duster.fragments.cache = false;
52 }
53
54 return process.configuration.duster.fragments.cache;
55 };
56
57
58 var resolveVariables = r.resolveVariables = function(variables, context, callback)
59 {
60 if (!variables) {
61 callback();
62 return;
63 }
64
65 if (variables.length === 0)
66 {
67 callback(null, []);
68 return;
69 }
70
71 var resolvedVariables = [];
72
73 var fns = [];
74 for (var i = 0; i < variables.length; i++)
75 {
76 var fn = function(variable) {
77 return function(done) {
78
79 dust.renderSource("" + variable, context, function (err, value) {
80
81 if (err) {
82 done(err);
83 return;
84 }
85
86 value = value.trim();
87
88 resolvedVariables.push(value);
89 done();
90 });
91
92 }
93 }(variables[i]);
94 fns.push(fn);
95 }
96
97 async.series(fns, function(err) {
98 callback(err, resolvedVariables);
99 });
100 };
101
102 /**
103 * Helper function that sets the dust cursor to flushable.
104 * This is to get around an apparent bug with dust:
105 *
106 * https://github.com/linkedin/dustjs/issues/303
107 *
108 * @param chunk
109 * @param callback
110 * @returns {*}
111 */
112 var map = r.map = function(chunk, callback)
113 {
114 var cursor = chunk.map(function(branch) {
115 callback(branch);
116 });
117 cursor.flushable = true;
118
119 return cursor;
120 };
121
122 /**
123 * Helper function to end the chunk. This is in place because it's unclear exactly what is needed to counter
124 * the issue mentioned in:
125 *
126 * https://github.com/linkedin/dustjs/issues/303
127 *
128 * At one point, it seemed that some throttling of the end() call was required. It may still be at some point.
129 * So for now, we use this helper method to end() since it lets us inject our own behaviors if needed.
130 *
131 * @param chunk
132 * @param context
133 * @param err
134 */
135 var end = r.end = function(chunk, context, err)
136 {
137 if (err)
138 {
139 chunk.setError(err);
140 }
141 else
142 {
143 chunk.end();
144 }
145 };
146
147 var _MARK_INSIGHT = r._MARK_INSIGHT = function(node, result)
148 {
149 if (result)
150 {
151 result.insightNode = node.getRepositoryId() + "/" + node.getBranchId() + "/" + node.getId();
152 }
153 else
154 {
155 console.log("WARN: result node should not be null");
156 console.trace();
157 }
158 };
159
160
161 //
162 // tracker related stuff
163 //
164
165 var buildRequirements = r.buildRequirements = function(context, requirements)
166 {
167 var badKeys = [];
168
169 for (var key in requirements)
170 {
171 if (!requirements[key])
172 {
173 badKeys.push(key);
174 }
175 }
176
177 for (var i = 0; i < badKeys.length; i++)
178 {
179 delete requirements[badKeys[i]];
180 }
181
182 // add in stuff from request if available
183 var req = context.get("req");
184 if (req)
185 {
186 if (req.repositoryId)
187 {
188 requirements["repository"] = req.repositoryId;
189 }
190
191 if (req.branchId)
192 {
193 requirements["branch"] = req.branchId;
194 }
195 }
196
197 return requirements;
198 };
199
200 var handleCacheFragmentRead = r.handleCacheFragmentRead = function(context, fragmentId, requirements, callback)
201 {
202 if (!isFragmentCacheEnabled())
203 {
204 return callback();
205 }
206
207 var req = context.get("req");
208
209 var contentStore = req.stores.content;
210
211 var fragmentsBasePath = context.get("_fragments_base_path");
212 if (!fragmentsBasePath) {
213 fragmentsBasePath = path.join("duster", "repositories", req.repositoryId, "branches", req.branchId, "fragments");
214 }
215
216 // fragment cache key
217 var fragmentCacheKey = util.generateFragmentCacheKey(fragmentId, requirements);
218
219 // disk location
220 var fragmentFilePath = path.join(fragmentsBasePath, fragmentCacheKey, "fragment.html");
221 util.safeReadStream(contentStore, fragmentFilePath, function(err, stream) {
222 callback(err, stream, fragmentFilePath, fragmentCacheKey);
223 });
224 };
225
226 var handleCacheFragmentWrite = r.handleCacheFragmentWrite = function(context, fragmentDescriptor, fragmentDependencies, requirements, text, callback)
227 {
228 // if fragment cache not enabled, return right away
229 if (!isFragmentCacheEnabled())
230 {
231 return callback();
232 }
233
234 var req = context.get("req");
235
236 var contentStore = req.stores.content;
237
238 var fragmentsBasePath = context.get("_fragments_base_path");
239 if (!fragmentsBasePath) {
240 fragmentsBasePath = path.join("duster", "repositories", req.repositoryId, "branches", req.branchId, "fragments");
241 }
242
243 // fragment cache key
244 var fragmentCacheKey = util.generateFragmentCacheKey(fragmentDescriptor.fragmentId, requirements);
245
246 // store this
247 fragmentDescriptor.fragmentCacheKey = fragmentCacheKey;
248
249 // mark the rendition
250 if (fragmentDependencies)
251 {
252 console.log("marking rendition from dust. fragmentId: " + fragmentDescriptor.fragmentId);
253 renditions.markRendition(req, fragmentDescriptor, fragmentDependencies, function (err) {
254
255 // if we got an error writing the rendition, then we have to roll back and invalidate disk cache
256 if (err)
257 {
258 var fragmentFolderPath = path.join(fragmentsBasePath, fragmentCacheKey);
259
260 console.log("Caught error on fragment markRendition, invalidating: " + fragmentFolderPath + ", err:" + err);
261
262 _handleCacheFragmentInvalidate(contentStore, fragmentFolderPath, function() {
263 // done
264 });
265 }
266
267 });
268 }
269
270 // disk location
271 var fragmentFilePath = path.join(fragmentsBasePath, fragmentCacheKey, "fragment.html");
272 contentStore.writeFile(fragmentFilePath, text, function(err) {
273 callback(err, fragmentFilePath, fragmentCacheKey);
274 });
275 };
276
277 var handleCacheFragmentInvalidate = r.handleCacheFragmentInvalidate = function(host, fragmentsBasePath, fragmentCacheKey, callback)
278 {
279 var fragmentFolderPath = path.join(fragmentsBasePath, fragmentCacheKey);
280
281 // list all of the hosts
282 var stores = require("../middleware/stores/stores");
283 stores.produce(host, function (err, stores) {
284
285 if (err) {
286 return callback(err, fragmentFolderPath);
287 }
288
289 _handleCacheFragmentInvalidate(stores.content, fragmentFolderPath, function(err, fragmentFolderPath) {
290 return callback(null, fragmentFolderPath);
291 });
292 });
293 };
294
295 var _handleCacheFragmentInvalidate = function(contentStore, fragmentFolderPath, callback)
296 {
297 contentStore.existsDirectory(fragmentFolderPath, function (exists) {
298
299 if (!exists) {
300 return callback();
301 }
302
303 contentStore.removeDirectory(fragmentFolderPath, function () {
304 callback();
305 });
306 });
307 };
308
309 var loadFragment = r.loadFragment = function(context, fragmentId, requirements, callback)
310 {
311 if (!isFragmentCacheEnabled())
312 {
313 return callback();
314 }
315
316 if (!fragmentId)
317 {
318 return callback();
319 }
320
321 var req = context.get("req");
322
323 handleCacheFragmentRead(context, fragmentId, requirements, function(err, readStream, readPath, fragmentCacheKey) {
324
325 if (!err && readStream)
326 {
327 var req = context.get("req");
328
329 // yes, we found it in cache, so we'll simply pipe it back from disk
330 req.log("Fragment Cache Hit: " + req.url + "#" + fragmentCacheKey);
331
332 // read stream in
333 var bufs = [];
334 readStream.on('data', function(d){ bufs.push(d);});
335 readStream.on('end', function(){
336
337 var fragment = "" + Buffer.concat(bufs).toString();
338
339 callback(null, fragment);
340 });
341
342 return;
343 }
344
345 callback({
346 "message": "Unable to read fragment: " + fragmentId
347 });
348
349 });
350 };
351
352 var renderFragment = r.renderFragment = function(context, fragmentId, requirements, chunk, bodies, callback)
353 {
354 if (!isFragmentCacheEnabled() || !fragmentId)
355 {
356 chunk.render(bodies.block, context);
357 end(chunk, context);
358
359 return callback();
360 }
361
362 var c = chunk.capture(bodies.block, context, function(text, chunk2) {
363
364 // we leave a timeout here so that things can catch up internally within Dust (more oddness!)
365 setTimeout(function() {
366
367 // for reasons that have everything to do with Dust oddness...
368 // write to "c" and be sure to end "c" then "chunk2"
369 c.write(text);
370 c.end();
371 chunk2.end();
372 // NOTE: normally, we'd expect to do something like this
373 //chunk2.write(text);
374 //end(chunk2);
375 // but this doesn't work and it has something to do with dust.map and chunk.capture intermingling weirdly
376
377 // now let's cache it
378 var fragmentDescriptor = {};
379 var pageDescriptor = context.get("_page_descriptor");
380 if (pageDescriptor) {
381 fragmentDescriptor = util.clone(pageDescriptor, true);
382 }
383 fragmentDescriptor.fragmentId = fragmentId;
384 fragmentDescriptor.scope = "FRAGMENT";
385
386 var tracker = context.get("__tracker");
387
388 var fragmentDependencies = {};
389 fragmentDependencies.requires = tracker.requires;
390 fragmentDependencies.produces = tracker.produces;
391
392 // write to cache
393 handleCacheFragmentWrite(context, fragmentDescriptor, fragmentDependencies, requirements, text, function(err, writtenPath, writtenCacheKey) {
394
395 var req = context.get("req");
396
397 if (err) {
398 req.log("Fragment Cache Write Failed for URL: " + req.url + ", err: " + JSON.stringify(err, null, " "));
399 return callback(err);
400 }
401
402 req.log("Fragment Cache Write: " + req.url + "#" + writtenCacheKey);
403
404 callback();
405 });
406 }, 1);
407 });
408
409 return c;
410 };
411
412 var addHelpers = r.addHelpers = function(app, dust, filepaths, callback) {
413
414 var fns = [];
415 for (var i = 0; i < filepaths.length; i++)
416 {
417 var fn = function(filepath, app, dust) {
418 return function(done) {
419
420 require(filepath)(app, dust, function() {
421 //console.log("Loaded dust helper: " + filepath);
422 done();
423 });
424 }
425 }(filepaths[i], app, dust);
426 fns.push(fn);
427 }
428
429 async.series(fns, function() {
430 callback();
431 });
432
433 };
434
435 return r;
436};