1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | 'use strict';
|
26 |
|
27 | const fs = require('fs-extra');
|
28 | const path = require('path');
|
29 | const minimatch = require('minimatch');
|
30 | const XXHash = require('xxhashjs');
|
31 | const HttpReader = require('./Readers/HttpReader');
|
32 | const GithubReader = require('./Readers/GithubReader');
|
33 | const BitbucketServerReader = require('./Readers/BitbucketServerReader');
|
34 | const AzureReposReader = require('./Readers/AzureReposReader');
|
35 |
|
36 | const DEFAULT_EXCLUDE_FILE_NAME = 'builder-cache.exclude';
|
37 | const CACHED_READERS = [GithubReader, BitbucketServerReader, AzureReposReader, HttpReader];
|
38 | const CACHE_LIFETIME = 1;
|
39 | const HASH_SEED = 0xE1EC791C;
|
40 | const MAX_FILENAME_LENGTH = 250;
|
41 |
|
42 |
|
43 | class FileCache {
|
44 |
|
45 | constructor(machine) {
|
46 | this._cacheDir = '.' + path.sep + '.builder-cache';
|
47 | this._excludeList = [];
|
48 | this._machine = machine;
|
49 | this._outdateTime = CACHE_LIFETIME * 86400000;
|
50 | this._useCache = false;
|
51 | }
|
52 |
|
53 | |
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | _getCachedPath(link) {
|
62 | link = link.replace(/^bitbucket-server\:/, 'bitbucket-server#');
|
63 | link = link.replace(/^github\:/, 'github#');
|
64 | link = link.replace(/^git-azure-repos\:/, 'git-azure-repos#');
|
65 | link = link.replace(/\:\/\//, '#');
|
66 | link = link.replace(/\//g, '-');
|
67 | const parts = link.match(/^([^\?]*)(\?(.*))?$/);
|
68 | if (parts && parts[3]) {
|
69 | link = parts[1] + XXHash.h64(parts[3], HASH_SEED);
|
70 | }
|
71 | if (link.length > MAX_FILENAME_LENGTH) {
|
72 | const startPart = link.substr(0, 100);
|
73 | const endPart = link.substr(link.length - 100);
|
74 | const middlePart = XXHash.h64(link, HASH_SEED);
|
75 | link = startPart + endPart + middlePart;
|
76 | }
|
77 | return path.join(this._cacheDir, link);
|
78 | }
|
79 |
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 | _cacheFile(filePath, content) {
|
86 | const cachedPath = this._getCachedPath(filePath);
|
87 | try {
|
88 | fs.ensureDirSync(path.dirname(cachedPath));
|
89 | fs.writeFileSync(cachedPath, content);
|
90 | } catch (err) {
|
91 | this._machine.logger.error(err);
|
92 | }
|
93 | }
|
94 |
|
95 | |
96 |
|
97 |
|
98 |
|
99 |
|
100 | _findFile(link) {
|
101 | const finalPath = this._getCachedPath(link);
|
102 | return fs.existsSync(finalPath) ? finalPath : false;
|
103 | }
|
104 |
|
105 | |
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | _isCachedReader(reader) {
|
112 | return CACHED_READERS.some((cachedReader) => (reader instanceof cachedReader));
|
113 | }
|
114 |
|
115 | |
116 |
|
117 |
|
118 |
|
119 |
|
120 | _isExcludedFromCache(includedPath) {
|
121 | return this._excludeList.some((regexp) => regexp.test(includedPath));
|
122 | }
|
123 |
|
124 | _toBeCached(includePath) {
|
125 | return this.useCache && !this._isExcludedFromCache(includePath);
|
126 | }
|
127 |
|
128 | |
129 |
|
130 |
|
131 |
|
132 |
|
133 | _isCacheFileOutdate(pathname) {
|
134 | const stat = fs.statSync(pathname);
|
135 | return Date.now() - stat.mtime > this._outdateTime;
|
136 | }
|
137 |
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | read(reader, includePath, dependencies, context) {
|
146 |
|
147 | const includePathParsed = reader.parsePath(includePath);
|
148 | const originalReader = reader;
|
149 |
|
150 | let needCache = false;
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | const depCondition = (!dependencies || dependencies.get(includePath) === undefined) && !this.machine.dependenciesSaveFile;
|
156 | if (depCondition && this._toBeCached(includePath) && this._isCachedReader(reader)) {
|
157 | let result;
|
158 | if ((result = this._findFile(includePath)) && !this._isCacheFileOutdate(result)) {
|
159 |
|
160 | includePath = result;
|
161 | this.machine.logger.info(`Read source from local path "${includePath}"`);
|
162 | reader = this.machine.readers.file;
|
163 | } else {
|
164 | needCache = true;
|
165 | }
|
166 | }
|
167 |
|
168 | const options = { dependencies: dependencies, context: context };
|
169 |
|
170 | if (originalReader === this.machine.readers.file) {
|
171 |
|
172 |
|
173 | options.resultPathParsed = includePathParsed;
|
174 | }
|
175 |
|
176 | let content = reader.read(includePath, options);
|
177 |
|
178 |
|
179 | if (content.length > 0 && content[content.length - 1] != '\n') {
|
180 | content += '\n';
|
181 | }
|
182 |
|
183 | if (needCache && this.useCache) {
|
184 | this.machine.logger.debug(`Caching file "${includePath}"`);
|
185 | this._cacheFile(includePath, content);
|
186 | }
|
187 | return {
|
188 | 'content' : content,
|
189 | 'includePathParsed' : includePathParsed
|
190 | };
|
191 | }
|
192 |
|
193 | clearCache() {
|
194 | fs.removeSync(this.cacheDir);
|
195 | }
|
196 |
|
197 | |
198 |
|
199 |
|
200 |
|
201 | get useCache() {
|
202 | return this._useCache || false;
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 | set useCache(value) {
|
209 | this._useCache = value;
|
210 | }
|
211 |
|
212 | set cacheDir(value) {
|
213 | this._cacheDir = value.replace(/\//g, path.sep);
|
214 | }
|
215 |
|
216 | get cacheDir() {
|
217 | return this._cacheDir;
|
218 | }
|
219 |
|
220 | set machine(value) {
|
221 | this._machine = value;
|
222 | }
|
223 |
|
224 | get machine() {
|
225 | return this._machine;
|
226 | }
|
227 |
|
228 | get excludeList() {
|
229 | return this._excludeList;
|
230 | }
|
231 |
|
232 | |
233 |
|
234 |
|
235 |
|
236 | set excludeList(fileName) {
|
237 | if (fileName == '') {
|
238 | fileName = DEFAULT_EXCLUDE_FILE_NAME;
|
239 | }
|
240 |
|
241 | const newPath = fileName;
|
242 |
|
243 | if (!fs.existsSync(newPath)) {
|
244 | if (fileName == DEFAULT_EXCLUDE_FILE_NAME) {
|
245 |
|
246 | this._excludeList = [];
|
247 | return;
|
248 | } else {
|
249 | throw new Error(`${newPath} file does not exist`);
|
250 | }
|
251 | }
|
252 |
|
253 | const content = fs.readFileSync(newPath, 'utf8');
|
254 | const filenames = content.split(/\n|\r\n/);
|
255 |
|
256 | const patterns = filenames.map((value) => value.trimLeft())
|
257 | .filter((value) => (value != '' && value[0] != '#'))
|
258 | .map((value) => minimatch.makeRe(value));
|
259 | this._excludeList = patterns;
|
260 | }
|
261 | }
|
262 |
|
263 | module.exports = FileCache;
|