UNPKG

4.58 kBJavaScriptView Raw
1var fs = require('fs');
2var path = require('path');
3var mkdirp = require('mkdirp')
4var walkSync = require('walk-sync');
5var quickTemp = require('quick-temp')
6var Writer = require('broccoli-writer');
7var helpers = require('broccoli-kitchen-sink-helpers')
8
9CachingWriter.prototype = Object.create(Writer.prototype);
10CachingWriter.prototype.constructor = CachingWriter;
11function CachingWriter (inputTree, options) {
12 if (!(this instanceof CachingWriter)) return new CachingWriter(inputTree, options);
13
14 this.inputTree = inputTree;
15
16 options = options || {};
17
18 for (var key in options) {
19 if (options.hasOwnProperty(key)) {
20 this[key] = options[key]
21 }
22 }
23};
24
25CachingWriter.prototype.getCacheDir = function () {
26 return quickTemp.makeOrReuse(this, 'tmpCacheDir')
27}
28
29CachingWriter.prototype.getCleanCacheDir = function () {
30 return quickTemp.makeOrRemake(this, 'tmpCacheDir')
31}
32
33CachingWriter.prototype.write = function (readTree, destDir) {
34 var self = this
35
36 return readTree(this.inputTree).then(function (srcDir) {
37 var inputTreeKeys = keysForTree(srcDir);
38 var inputTreeHash = helpers.hashStrings(inputTreeKeys);
39
40 if (inputTreeHash !== self._cacheHash) {
41 self.updateCache(srcDir, self.getCleanCacheDir());
42
43 self._cacheHash = inputTreeHash;
44 self._cacheTreeKeys = inputTreeKeys;
45 }
46
47 linkFromCache(self.getCacheDir(), destDir);
48 })
49};
50
51CachingWriter.prototype.cleanup = function () {
52 quickTemp.remove(this, 'tmpCacheDir')
53 Writer.prototype.cleanup.call(this)
54}
55
56CachingWriter.prototype.updateCache = function (srcDir, destDir) {
57 throw new Error('You must implement updateCache.');
58}
59
60module.exports = CachingWriter;
61
62function linkFromCache(srcDir, destDir) {
63 var files = walkSync(srcDir);
64 var length = files.length;
65 var file;
66
67 for (var i = 0; i < length; i++) {
68 file = files[i];
69
70 var srcFile = path.join(srcDir, file);
71 var stats = fs.statSync(srcFile);
72
73 if (stats.isDirectory()) { continue; }
74
75 if (!stats.isFile()) { throw new Error('Can not link non-file.'); }
76
77 destFile = path.join(destDir, file);
78 mkdirp.sync(path.dirname(destFile));
79 fs.linkSync(srcFile, destFile);
80 }
81}
82
83function keysForTree (fullPath, options) {
84 options = options || {}
85
86 var _stack = options._stack
87 var _followSymlink = options._followSymlink
88 var relativePath = options.relativePath || '.'
89 var stats
90 var statKeys
91
92 try {
93 if (_followSymlink) {
94 stats = fs.statSync(fullPath)
95 } else {
96 stats = fs.lstatSync(fullPath)
97 }
98 } catch (err) {
99 console.warn('Warning: failed to stat ' + fullPath)
100 // fullPath has probably ceased to exist. Leave `stats` undefined and
101 // proceed hashing.
102 }
103 var childKeys = []
104 if (stats) {
105 statKeys = ['stats', stats.mode, stats.size]
106 } else {
107 statKeys = ['stat failed']
108 }
109 if (stats && stats.isDirectory()) {
110 var fileIdentity = stats.dev + '\x00' + stats.ino
111 if (_stack != null && _stack.indexOf(fileIdentity) !== -1) {
112 console.warn('Symlink directory loop detected at ' + fullPath + ' (note: loop detection may have false positives on Windows)')
113 } else {
114 if (_stack != null) _stack = _stack.concat([fileIdentity])
115 var entries
116 try {
117 entries = fs.readdirSync(fullPath).sort()
118 } catch (err) {
119 console.warn('Warning: Failed to read directory ' + fullPath)
120 console.warn(err.stack)
121 childKeys = ['readdir failed']
122 // That's all there is to say about this directory.
123 }
124 if (entries != null) {
125 for (var i = 0; i < entries.length; i++) {
126
127 var keys = keysForTree(path.join(fullPath, entries[i]), {
128 _stack: _stack,
129 relativePath: path.join(relativePath, entries[i])
130 })
131 childKeys = childKeys.concat(keys)
132 }
133 }
134 }
135 } else if (stats && stats.isSymbolicLink()) {
136 if (_stack == null) {
137 // From here on in the traversal, we need to guard against symlink
138 // directory loops. _stack is kept null in the absence of symlinks to we
139 // don't have to deal with Windows for now, as long as it doesn't use
140 // symlinks.
141 _stack = []
142 }
143 childKeys = keysForTree(fullPath, {_stack: _stack, relativePath: relativePath, _followSymlink: true}) // follow symlink
144 statKeys.push(stats.mtime.getTime())
145 } else if (stats && stats.isFile()) {
146 statKeys.push(stats.mtime.getTime())
147 }
148
149 // Perhaps we should not use basename to infer the file name
150 return ['path', relativePath]
151 .concat(statKeys)
152 .concat(childKeys)
153}