UNPKG

5.36 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright © 2019 Atomist, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.save = exports.CachingProjectLoader = void 0;
19const shutdown_1 = require("@atomist/automation-client/lib/internal/util/shutdown");
20const logger_1 = require("@atomist/automation-client/lib/util/logger");
21const fs = require("fs-extra");
22const sha = require("sha-regex");
23const util_1 = require("util");
24const cloningProjectLoader_1 = require("./cloningProjectLoader");
25const cacheKey_1 = require("./support/cacheKey");
26const LruCache_1 = require("./support/LruCache");
27/**
28 * Caching implementation of ProjectLoader
29 */
30class CachingProjectLoader {
31 constructor(delegate = cloningProjectLoader_1.CloningProjectLoader, maxEntries = 20) {
32 this.delegate = delegate;
33 this.deleteOnExit = [];
34 this.cache = new LruCache_1.LruCache(maxEntries, p => this.cleanUp(p.baseDir, "eviction"));
35 shutdown_1.registerShutdownHook(async () => {
36 if (this.deleteOnExit.length > 0) {
37 logger_1.logger.debug("Deleting cached projects");
38 }
39 await Promise.all(this.deleteOnExit.map(p => this.cleanUp(p, "shutdown")));
40 return 0;
41 }, 10000, `deleting cached projects`);
42 }
43 async doWithProject(params, action) {
44 // read-only == false means the consumer is going to make changes; don't cache such projects
45 if (!params.readOnly) {
46 logger_1.logger.debug("Forcing fresh clone for non readonly use of '%j'", params.id);
47 return this.saveAndRunAction(this.delegate, params, action);
48 }
49 // Caching projects by branch references is wrong as the branch might change; give out new versions
50 if (!sha({ exact: true }).test(params.id.sha)) {
51 logger_1.logger.debug("Forcing fresh clone for branch use of '%j'", params.id);
52 return this.saveAndRunAction(this.delegate, params, action);
53 }
54 logger_1.logger.debug("Attempting to reuse clone for readonly use of '%j'", params.id);
55 const key = cacheKey_1.cacheKey(params);
56 let project = this.cache.get(key);
57 if (!!project) {
58 // Validate it, as the directory may have been cleaned up
59 try {
60 await util_1.promisify(fs.access)(project.baseDir);
61 }
62 catch (_a) {
63 this.cache.evict(key);
64 project = undefined;
65 }
66 }
67 if (!project) {
68 project = await save(this.delegate, params);
69 logger_1.logger.debug("Caching project '%j' at '%s'", project.id, project.baseDir);
70 this.cache.put(key, project);
71 }
72 logger_1.logger.debug("About to invoke action. Cache stats: %j", this.cache.stats);
73 return action(project);
74 }
75 /**
76 * Save project and run provided WithLoadedProject action on it.
77 * @param delegate
78 * @param params
79 * @param action
80 */
81 async saveAndRunAction(delegate, params, action) {
82 const p = await save(delegate, params);
83 if (params.context && params.context.lifecycle) {
84 params.context.lifecycle.registerDisposable(async () => this.cleanUp(p.baseDir, "disposal"));
85 }
86 else {
87 // schedule a cleanup timer but don't block the Node.js event loop for this
88 setTimeout(async () => this.cleanUp(p.baseDir, "timeout"), 10000).unref();
89 // also store a reference to this project to be deleted when we exit
90 this.deleteOnExit.push(p.baseDir);
91 }
92 return action(p);
93 }
94 /**
95 * Eviction callback to clean up file system resources.
96 * @param dir
97 * @param reason
98 */
99 async cleanUp(dir, reason) {
100 if (dir && await fs.pathExists(dir)) {
101 if (reason === "timeout") {
102 logger_1.logger.debug(`Deleting project '%s' because a timeout passed`, dir);
103 }
104 else {
105 logger_1.logger.debug(`Deleting project '%s' because %s was triggered`, dir, reason);
106 }
107 try {
108 await fs.remove(dir);
109 const ix = this.deleteOnExit.indexOf(dir);
110 if (ix >= 0) {
111 this.deleteOnExit.slice(ix, 1);
112 }
113 }
114 catch (err) {
115 logger_1.logger.warn(err);
116 }
117 }
118 }
119}
120exports.CachingProjectLoader = CachingProjectLoader;
121/**
122 * Delegate to the underlying ProjectLoader to load the project.
123 * @param pl
124 * @param params
125 */
126function save(pl, params) {
127 let p;
128 return pl.doWithProject(params, async (loaded) => {
129 p = loaded;
130 }).then(() => p);
131}
132exports.save = save;
133//# sourceMappingURL=CachingProjectLoader.js.map
\No newline at end of file