1 | var fs = require('fs');
|
2 | var path = require('path');
|
3 | var mkdirp = require('mkdirp')
|
4 | var walkSync = require('walk-sync');
|
5 | var quickTemp = require('quick-temp')
|
6 | var Writer = require('broccoli-writer');
|
7 | var helpers = require('broccoli-kitchen-sink-helpers')
|
8 |
|
9 | CachingWriter.prototype = Object.create(Writer.prototype);
|
10 | CachingWriter.prototype.constructor = CachingWriter;
|
11 | function 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 |
|
25 | CachingWriter.prototype.getCacheDir = function () {
|
26 | return quickTemp.makeOrReuse(this, 'tmpCacheDir')
|
27 | }
|
28 |
|
29 | CachingWriter.prototype.getCleanCacheDir = function () {
|
30 | return quickTemp.makeOrRemake(this, 'tmpCacheDir')
|
31 | }
|
32 |
|
33 | CachingWriter.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 |
|
51 | CachingWriter.prototype.cleanup = function () {
|
52 | quickTemp.remove(this, 'tmpCacheDir')
|
53 | Writer.prototype.cleanup.call(this)
|
54 | }
|
55 |
|
56 | CachingWriter.prototype.updateCache = function (srcDir, destDir) {
|
57 | throw new Error('You must implement updateCache.');
|
58 | }
|
59 |
|
60 | module.exports = CachingWriter;
|
61 |
|
62 | function 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 |
|
83 | function 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 |
|
101 |
|
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 |
|
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 |
|
138 |
|
139 |
|
140 |
|
141 | _stack = []
|
142 | }
|
143 | childKeys = keysForTree(fullPath, {_stack: _stack, relativePath: relativePath, _followSymlink: true})
|
144 | statKeys.push(stats.mtime.getTime())
|
145 | } else if (stats && stats.isFile()) {
|
146 | statKeys.push(stats.mtime.getTime())
|
147 | }
|
148 |
|
149 |
|
150 | return ['path', relativePath]
|
151 | .concat(statKeys)
|
152 | .concat(childKeys)
|
153 | }
|