UNPKG

13.2 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.Bundle = void 0;
9
10var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
12var _stream = require("stream");
13
14var _path = require("path");
15
16var _util = require("util");
17
18var _fsExtra = _interopRequireDefault(require("fs-extra"));
19
20var _archiveFiles = require("@shockpkg/archive-files");
21
22var _util2 = require("./util");
23
24var _queue = require("./queue");
25
26const pipelineP = (0, _util.promisify)(_stream.pipeline);
27const userExec = 0b001000000;
28/**
29 * Options for adding resources.
30 */
31
32/**
33 * Bundle constructor.
34 *
35 * @param path Output path for the main executable.
36 */
37class Bundle extends Object {
38 /**
39 * File and directory names to exclude when adding a directory.
40 */
41
42 /**
43 * Bundle main executable path.
44 */
45
46 /**
47 * Projector instance.
48 */
49
50 /**
51 * Open flag.
52 */
53
54 /**
55 * Close callbacks priority queue.
56 */
57 constructor(path) {
58 super();
59 (0, _defineProperty2.default)(this, "excludes", [/^\./, /^ehthumbs\.db$/, /^Thumbs\.db$/]);
60 (0, _defineProperty2.default)(this, "path", void 0);
61 (0, _defineProperty2.default)(this, "projector", void 0);
62 (0, _defineProperty2.default)(this, "_isOpen", false);
63 (0, _defineProperty2.default)(this, "_closeQueue", new _queue.Queue());
64 this.path = path;
65 }
66 /**
67 * Check if output open.
68 *
69 * @returns Returns true if open, else false.
70 */
71
72
73 get isOpen() {
74 return this._isOpen;
75 }
76 /**
77 * Check if name is excluded file.
78 *
79 * @param name File name.
80 * @returns Returns true if excluded, else false.
81 */
82
83
84 isExcludedFile(name) {
85 for (const exclude of this.excludes) {
86 if (exclude.test(name)) {
87 return true;
88 }
89 }
90
91 return false;
92 }
93 /**
94 * Open output with file.
95 *
96 * @param player Player path.
97 * @param movieFile Movie file.
98 */
99
100
101 async openFile(player, movieFile) {
102 const movieData = movieFile ? await _fsExtra.default.readFile(movieFile) : null;
103 await this.openData(player, movieData);
104 }
105 /**
106 * Open output with data.
107 *
108 * @param player Player path.
109 * @param movieData Movie data.
110 */
111
112
113 async openData(player, movieData) {
114 if (this._isOpen) {
115 throw new Error('Already open');
116 }
117
118 await this._checkOutput();
119
120 this._closeQueue.clear();
121
122 await this._openData(player, movieData);
123 this._isOpen = true;
124 }
125 /**
126 * Close output.
127 */
128
129
130 async close() {
131 this._assertIsOpen();
132
133 try {
134 await this._close();
135 } finally {
136 this._closeQueue.clear();
137 }
138
139 this._isOpen = false;
140 }
141 /**
142 * Write out projector with player and file.
143 * Has a callback to write out the resources.
144 *
145 * @param player Player path.
146 * @param movieFile Movie file.
147 * @param func Async function.
148 * @returns Return value of the async function.
149 */
150
151
152 async withFile(player, movieFile, func = null) {
153 const movieData = movieFile ? await _fsExtra.default.readFile(movieFile) : null;
154 return this.withData(player, movieData, func);
155 }
156 /**
157 * Write out projector with player and data.
158 * Has a callback to write out the resources.
159 *
160 * @param player Player path.
161 * @param movieData Movie data.
162 * @param func Async function.
163 * @returns Return value of the async function.
164 */
165
166
167 async withData(player, movieData, func = null) {
168 await this.openData(player, movieData);
169
170 try {
171 return func ? await func.call(this, this) : null;
172 } finally {
173 await this.close();
174 }
175 }
176 /**
177 * Get path for resource.
178 *
179 * @param destination Resource destination.
180 * @returns Destination path.
181 */
182
183
184 resourcePath(destination) {
185 return (0, _path.join)((0, _path.dirname)(this.projector.path), destination);
186 }
187 /**
188 * Check if path for resource exists.
189 *
190 * @param destination Resource destination.
191 * @returns True if destination exists, else false.
192 */
193
194
195 async resourceExists(destination) {
196 return !!(0, _archiveFiles.fsLstatExists)(this.resourcePath(destination));
197 }
198 /**
199 * Copy resource, detecting source type automatically.
200 *
201 * @param destination Resource destination.
202 * @param source Source directory.
203 * @param options Resource options.
204 */
205
206
207 async copyResource(destination, source, options = null) {
208 this._assertIsOpen();
209
210 const stat = await _fsExtra.default.lstat(source);
211
212 switch (true) {
213 case stat.isSymbolicLink():
214 {
215 await this.copyResourceSymlink(destination, source, options);
216 break;
217 }
218
219 case stat.isFile():
220 {
221 await this.copyResourceFile(destination, source, options);
222 break;
223 }
224
225 case stat.isDirectory():
226 {
227 await this.copyResourceDirectory(destination, source, options);
228 break;
229 }
230
231 default:
232 {
233 throw new Error(`Unsupported resource type: ${source}`);
234 }
235 }
236 }
237 /**
238 * Copy directory as resource, recursive copy.
239 *
240 * @param destination Resource destination.
241 * @param source Source directory.
242 * @param options Resource options.
243 */
244
245
246 async copyResourceDirectory(destination, source, options = null) {
247 this._assertIsOpen(); // Create directory.
248
249
250 await this.createResourceDirectory(destination, options ? await this._expandResourceOptionsCopy(options, async () => _fsExtra.default.stat(source)) : options); // If not recursive do not walk contents.
251
252 if (options && options.noRecurse) {
253 return;
254 } // Any directories we add should not be recursive.
255
256
257 const opts = { ...(options || {}),
258 noRecurse: true
259 };
260 await (0, _archiveFiles.fsWalk)(source, async (path, stat) => {
261 // If this name is excluded, skip without descending.
262 if (this.isExcludedFile((0, _path.basename)(path))) {
263 return false;
264 }
265
266 await this.copyResource((0, _path.join)(destination, path), (0, _path.join)(source, path), opts);
267 return true;
268 });
269 }
270 /**
271 * Copy file as resource.
272 *
273 * @param destination Resource destination.
274 * @param source Source file.
275 * @param options Resource options.
276 */
277
278
279 async copyResourceFile(destination, source, options = null) {
280 this._assertIsOpen();
281
282 await this.streamResourceFile(destination, _fsExtra.default.createReadStream(source), options ? await this._expandResourceOptionsCopy(options, async () => _fsExtra.default.stat(source)) : options);
283 }
284 /**
285 * Copy symlink as resource.
286 *
287 * @param destination Resource destination.
288 * @param source Source symlink.
289 * @param options Resource options.
290 */
291
292
293 async copyResourceSymlink(destination, source, options = null) {
294 this._assertIsOpen();
295
296 await this.createResourceSymlink(destination, await _fsExtra.default.readlink(source), options ? await this._expandResourceOptionsCopy(options, async () => _fsExtra.default.lstat(source)) : options);
297 }
298 /**
299 * Create a resource directory.
300 *
301 * @param destination Resource destination.
302 * @param options Resource options.
303 */
304
305
306 async createResourceDirectory(destination, options = null) {
307 this._assertIsOpen();
308
309 const dest = await this._assertNotResourceExists(destination, !!(options && options.merge));
310 await _fsExtra.default.ensureDir(dest); // If either is set, queue up change times when closing.
311
312 if (options && (options.atime || options.mtime)) {
313 // Get absolute path, use length for the priority.
314 // Also copy the options object which the owner could change.
315 const abs = (0, _path.resolve)(dest);
316
317 this._closeQueue.push(this._setResourceAttributes.bind(this, abs, { ...options
318 }), abs.length);
319 }
320 }
321 /**
322 * Create a resource file.
323 *
324 * @param destination Resource destination.
325 * @param data Resource data.
326 * @param options Resource options.
327 */
328
329
330 async createResourceFile(destination, data, options = null) {
331 this._assertIsOpen();
332
333 await this.streamResourceFile(destination, new _stream.Readable({
334 read() {
335 this.push(data);
336 this.push(null);
337 }
338
339 }), options);
340 }
341 /**
342 * Create a resource symlink.
343 *
344 * @param destination Resource destination.
345 * @param target Symlink target.
346 * @param options Resource options.
347 */
348
349
350 async createResourceSymlink(destination, target, options = null) {
351 this._assertIsOpen();
352
353 const dest = await this._assertNotResourceExists(destination);
354 await _fsExtra.default.ensureDir((0, _path.dirname)(dest));
355 await _fsExtra.default.symlink(target, dest);
356
357 if (options) {
358 await this._setResourceAttributes(dest, options);
359 }
360 }
361 /**
362 * Stream readable source to resource file.
363 *
364 * @param destination Resource destination.
365 * @param data Resource stream.
366 * @param options Resource options.
367 */
368
369
370 async streamResourceFile(destination, data, options = null) {
371 this._assertIsOpen();
372
373 const dest = await this._assertNotResourceExists(destination);
374 await _fsExtra.default.ensureDir((0, _path.dirname)(dest));
375 await pipelineP(data, _fsExtra.default.createWriteStream(dest));
376
377 if (options) {
378 await this._setResourceAttributes(dest, options);
379 }
380 }
381 /**
382 * Check that output path is valid, else throws.
383 */
384
385
386 async _checkOutput() {
387 for (const p of [this.path, this.resourcePath('')]) {
388 // eslint-disable-next-line no-await-in-loop
389 if (await _fsExtra.default.pathExists(p)) {
390 throw new Error(`Output path already exists: ${p}`);
391 }
392 }
393 }
394 /**
395 * Expand resource options copy properties with stat object from source.
396 *
397 * @param options Options object.
398 * @param stat Stat function.
399 * @returns Options copy with any values populated.
400 */
401
402
403 async _expandResourceOptionsCopy(options, stat) {
404 const r = { ...options
405 };
406 const st = (0, _util2.once)(stat);
407
408 if (!r.atime && r.atimeCopy) {
409 r.atime = (await st()).atime;
410 }
411
412 if (!r.mtime && r.mtimeCopy) {
413 r.mtime = (await st()).mtime;
414 }
415
416 if (typeof r.executable !== 'boolean' && r.executableCopy) {
417 r.executable = this._getResourceModeExecutable((await st()).mode);
418 }
419
420 return r;
421 }
422 /**
423 * Set resource attributes from options object.
424 *
425 * @param path File path.
426 * @param options Options object.
427 */
428
429
430 async _setResourceAttributes(path, options) {
431 const {
432 atime,
433 mtime,
434 executable
435 } = options;
436 const st = await _fsExtra.default.lstat(path); // Maybe set executable if not a directory and supported.
437
438 if (typeof executable === 'boolean' && !st.isDirectory()) {
439 if (!st.isSymbolicLink()) {
440 await _fsExtra.default.chmod(path, this._setResourceModeExecutable(st.mode, executable));
441 } else if (_archiveFiles.fsLchmodSupported) {
442 await (0, _archiveFiles.fsLchmod)(path, this._setResourceModeExecutable( // Workaround for a legacy Node issue.
443 // eslint-disable-next-line no-bitwise
444 st.mode & 0b111111111, executable));
445 }
446 } // Maybe change times if either is set and supported.
447
448
449 if (atime || mtime) {
450 if (!st.isSymbolicLink()) {
451 await _fsExtra.default.utimes(path, atime || st.atime, mtime || st.mtime);
452 } else if (_archiveFiles.fsLutimesSupported) {
453 await (0, _archiveFiles.fsLutimes)(path, atime || st.atime, mtime || st.mtime);
454 }
455 }
456 }
457 /**
458 * Get file mode executable.
459 *
460 * @param mode Current mode.
461 * @returns Is executable.
462 */
463
464
465 _getResourceModeExecutable(mode) {
466 // eslint-disable-next-line no-bitwise
467 return !!(mode & userExec);
468 }
469 /**
470 * Set file mode executable.
471 *
472 * @param mode Current mode.
473 * @param executable Is executable.
474 * @returns File mode.
475 */
476
477
478 _setResourceModeExecutable(mode, executable) {
479 // eslint-disable-next-line no-bitwise
480 return (executable ? mode | userExec : mode & ~userExec) >>> 0;
481 }
482 /**
483 * Open output with data.
484 *
485 * @param player Player path.
486 * @param movieData Movie data.
487 */
488
489
490 async _openData(player, movieData) {
491 await this.projector.withData(player, movieData);
492 }
493 /**
494 * Close output.
495 */
496
497
498 async _close() {
499 await this._writeLauncher();
500 await this._closeQueue.run();
501 }
502 /**
503 * Assert bundle is open.
504 */
505
506
507 _assertIsOpen() {
508 if (!this._isOpen) {
509 throw new Error('Not open');
510 }
511 }
512 /**
513 * Assert resource does not exist, returning destination path.
514 *
515 * @param destination Resource destination.
516 * @param ignoreDirectory Ignore directories.
517 * @returns Destination path.
518 */
519
520
521 async _assertNotResourceExists(destination, ignoreDirectory = false) {
522 const dest = this.resourcePath(destination);
523 const st = await (0, _archiveFiles.fsLstatExists)(dest);
524
525 if (st && (!ignoreDirectory || !st.isDirectory())) {
526 throw new Error(`Resource path exists: ${dest}`);
527 }
528
529 return dest;
530 }
531 /**
532 * Main application file extension.
533 *
534 * @returns File extension.
535 */
536
537
538}
539
540exports.Bundle = Bundle;
541//# sourceMappingURL=bundle.js.map