codekingpro/portable-devtools
114k
1"use strict";
2// A path exclusive reservation system
3// reserve([list, of, paths], fn)
4// When the fn is first in line for all its paths, it
5// is called with a cb that clears the reservation.
6//
7// Used by async unpack to avoid clobbering paths in use,
8// while still allowing maximal safe parallelization.
9Object.defineProperty(exports, "__esModule", { value: true });
10exports.PathReservations = void 0;
11const node_path_1 = require("node:path");
12const normalize_unicode_js_1 = require("./normalize-unicode.js");
13const strip_trailing_slashes_js_1 = require("./strip-trailing-slashes.js");
14const platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
15const isWindows = platform === 'win32';
16// return a set of parent dirs for a given path
17// '/a/b/c/d' -> ['/', '/a', '/a/b', '/a/b/c', '/a/b/c/d']
18const getDirs = (path) => {
19 const dirs = path
20 .split('/')
21 .slice(0, -1)
22 .reduce((set, path) => {
23 const s = set[set.length - 1];
24 if (s !== undefined) {
25 path = (0, node_path_1.join)(s, path);
26 }
27 set.push(path || '/');
28 return set;
29 }, []);
30 return dirs;
31};
32class PathReservations {
33 // path => [function or Set]
34 // A Set object means a directory reservation
35 // A fn is a direct reservation on that path
36 #queues = new Map();
37 // fn => {paths:[path,...], dirs:[path, ...]}
38 #reservations = new Map();
39 // functions currently running
40 #running = new Set();
41 reserve(paths, fn) {
42 paths =
43 isWindows ?
44 ['win32 parallelization disabled']
45 : paths.map(p => {
46 // don't need normPath, because we skip this entirely for windows
47 return (0, strip_trailing_slashes_js_1.stripTrailingSlashes)((0, node_path_1.join)((0, normalize_unicode_js_1.normalizeUnicode)(p)));
48 });
49 const dirs = new Set(paths.map(path => getDirs(path)).reduce((a, b) => a.concat(b)));
50 this.#reservations.set(fn, { dirs, paths });
51 for (const p of paths) {
52 const q = this.#queues.get(p);
53 if (!q) {
54 this.#queues.set(p, [fn]);
55 }
56 else {
57 q.push(fn);
58 }
59 }
60 for (const dir of dirs) {
61 const q = this.#queues.get(dir);
62 if (!q) {
63 this.#queues.set(dir, [new Set([fn])]);
64 }
65 else {
66 const l = q[q.length - 1];
67 if (l instanceof Set) {
68 l.add(fn);
69 }
70 else {
71 q.push(new Set([fn]));
72 }
73 }
74 }
75 return this.#run(fn);
76 }
77 // return the queues for each path the function cares about
78 // fn => {paths, dirs}
79 #getQueues(fn) {
80 const res = this.#reservations.get(fn);
81 /* c8 ignore start */
82 if (!res) {
83 throw new Error('function does not have any path reservations');
84 }
85 /* c8 ignore stop */
86 return {
87 paths: res.paths.map((path) => this.#queues.get(path)),
88 dirs: [...res.dirs].map(path => this.#queues.get(path)),
89 };
90 }
91 // check if fn is first in line for all its paths, and is
92 // included in the first set for all its dir queues
93 check(fn) {
94 const { paths, dirs } = this.#getQueues(fn);
95 return (paths.every(q => q && q[0] === fn) &&
96 dirs.every(q => q && q[0] instanceof Set && q[0].has(fn)));
97 }
98 // run the function if it's first in line and not already running
99 #run(fn) {
100 if (this.#running.has(fn) || !this.check(fn)) {
101 return false;
102 }
103 this.#running.add(fn);
104 fn(() => this.#clear(fn));
105 return true;
106 }
107 #clear(fn) {
108 if (!this.#running.has(fn)) {
109 return false;
110 }
111 const res = this.#reservations.get(fn);
112 /* c8 ignore start */
113 if (!res) {
114 throw new Error('invalid reservation');
115 }
116 /* c8 ignore stop */
117 const { paths, dirs } = res;
118 const next = new Set();
119 for (const path of paths) {
120 const q = this.#queues.get(path);
121 /* c8 ignore start */
122 if (!q || q?.[0] !== fn) {
123 continue;
124 }
125 /* c8 ignore stop */
126 const q0 = q[1];
127 if (!q0) {
128 this.#queues.delete(path);
129 continue;
130 }
131 q.shift();
132 if (typeof q0 === 'function') {
133 next.add(q0);
134 }
135 else {
136 for (const f of q0) {
137 next.add(f);
138 }
139 }
140 }
141 for (const dir of dirs) {
142 const q = this.#queues.get(dir);
143 const q0 = q?.[0];
144 /* c8 ignore next - type safety only */
145 if (!q || !(q0 instanceof Set))
146 continue;
147 if (q0.size === 1 && q.length === 1) {
148 this.#queues.delete(dir);
149 continue;
150 }
151 else if (q0.size === 1) {
152 q.shift();
153 // next one must be a function,
154 // or else the Set would've been reused
155 const n = q[0];
156 if (typeof n === 'function') {
157 next.add(n);
158 }
159 }
160 else {
161 q0.delete(fn);
162 }
163 }
164 this.#running.delete(fn);
165 next.forEach(fn => this.#run(fn));
166 return true;
167 }
168}
169exports.PathReservations = PathReservations;
170//# sourceMappingURL=path-reservations.js.map