UNPKG

hackpro-sdk

Version:
372 lines 15.4 kB
"use strict"; /* * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const blockmap_1 = require("blockmap"); const bluebird_1 = require("bluebird"); const lodash_1 = require("lodash"); const path_1 = require("path"); const readable_stream_1 = require("readable-stream"); const yauzl_1 = require("yauzl"); const constants_1 = require("../constants"); const zip_1 = require("../zip"); const source_destination_1 = require("./source-destination"); const source_source_1 = require("./source-source"); const errors_1 = require("../errors"); const shared_1 = require("../sparse-stream/shared"); const sparse_filter_stream_1 = require("../sparse-stream/sparse-filter-stream"); const stream_limiter_1 = require("../stream-limiter"); const utils_1 = require("../utils"); function blockmapToBlocks(blockmap) { return blockmap.ranges.map((range) => { const offset = range.start * blockmap.blockSize; const length = (range.end - range.start + 1) * blockmap.blockSize; const checksum = range.checksum; const checksumType = blockmap.checksumType === 'sha1' || blockmap.checksumType === 'sha256' ? blockmap.checksumType : undefined; return { checksum, checksumType, blocks: [{ offset, length }] }; }); } function matchSupportedExtensions(filename) { const extension = path_1.posix.extname(filename); return (extension.length > 1 && source_destination_1.SourceDestination.imageExtensions.includes(extension.slice(1))); } exports.matchSupportedExtensions = matchSupportedExtensions; class StreamZipSource extends source_source_1.SourceSource { constructor(source, match = matchSupportedExtensions) { super(source); this.match = match; } canCreateReadStream() { return __awaiter(this, void 0, void 0, function* () { return true; }); } getEntry() { return __awaiter(this, void 0, void 0, function* () { if (this.entry === undefined) { const entry = yield zip_1.getFileStreamFromZipStream(yield this.source.createReadStream(false), this.match); this.entry = entry; const onData = () => { // We need to reset the entry if any read happens on this stream entry.removeListener('data', onData); this.entry = undefined; }; entry.on('data', onData); entry.pause(); } return this.entry; }); } createReadStream(_emitProgress = false, start = 0, end) { return __awaiter(this, void 0, void 0, function* () { if (start !== 0) { throw new errors_1.NotCapable(); } const stream = yield this.getEntry(); if (end !== undefined) { // TODO: handle errors on stream after transform finsh event const transform = new stream_limiter_1.StreamLimiter(stream, end + 1); return transform; } return stream; }); } _getMetadata() { return __awaiter(this, void 0, void 0, function* () { const entry = yield this.getEntry(); return { size: entry.size, compressedSize: entry.compressedSize, name: path_1.posix.basename(entry.path), }; }); } } exports.StreamZipSource = StreamZipSource; class SourceRandomAccessReader extends yauzl_1.RandomAccessReader { constructor(source) { super(); this.source = source; } _readStreamForRange(start, end) { // _readStreamForRange end is exclusive // this.source.createReadStream end is inclusive // Workaround this method not being async with a passthrough stream const passthrough = new readable_stream_1.PassThrough(); this.source .createReadStream(false, start, end - 1) .then(stream => { stream.on('error', passthrough.emit.bind(passthrough, 'error')); stream.pipe(passthrough); }) .catch(passthrough.emit.bind(passthrough, 'error')); return passthrough; } } class RandomAccessZipSource extends source_source_1.SourceSource { constructor(source, match = matchSupportedExtensions) { super(source); this.match = match; this.entries = []; this.ready = this.init(); } init() { return __awaiter(this, void 0, void 0, function* () { yield this.source.open(); const sourceMetadata = yield this.source.getMetadata(); const reader = new SourceRandomAccessReader(this.source); this.zip = yield bluebird_1.fromCallback((callback) => { if (sourceMetadata.size === undefined) { throw new errors_1.NotCapable(); } yauzl_1.fromRandomAccessReader(reader, sourceMetadata.size, { autoClose: false }, callback); }); this.zip.on('entry', (entry) => { this.entries.push(entry); }); yield new Promise((resolve, reject) => { this.zip.on('end', resolve); this.zip.on('error', reject); }); }); } canCreateReadStream() { return __awaiter(this, void 0, void 0, function* () { return true; }); } canCreateSparseReadStream() { return __awaiter(this, void 0, void 0, function* () { const metadata = yield this.getMetadata(); return metadata.blockMap !== undefined; }); } getEntries() { return __awaiter(this, void 0, void 0, function* () { yield this.ready; return this.entries; }); } getImageEntry() { return __awaiter(this, void 0, void 0, function* () { let entries = (yield this.getEntries()).filter(e => this.match(e.fileName)); if (entries.length === 0) { throw new Error(constants_1.NO_MATCHING_FILE_MSG); } entries = lodash_1.sortBy(entries, 'uncompressedSize'); const entry = entries[entries.length - 1]; if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) { throw new Error(`unsupported compression method: ${entry.compressionMethod}`); } return entry; }); } _open() { return __awaiter(this, void 0, void 0, function* () { yield this.ready; // We only want to run this for the error it may throw if there is no disk image in the zip yield this.getImageEntry(); }); } getEntryByName(name) { return __awaiter(this, void 0, void 0, function* () { const entries = yield this.getEntries(); for (const entry of entries) { if (entry.fileName === name) { return entry; } } }); } getStream(name) { return __awaiter(this, void 0, void 0, function* () { const entry = yield this.getEntryByName(name); if (entry !== undefined) { return yield bluebird_1.fromCallback((callback) => { // yauzl does not support start / end for compressed entries this.zip.openReadStream(entry, callback); }); } }); } getString(name) { return __awaiter(this, void 0, void 0, function* () { const stream = yield this.getStream(name); if (stream !== undefined) { const buffer = yield utils_1.streamToBuffer(stream); return buffer.toString(); } }); } getJson(name) { return __awaiter(this, void 0, void 0, function* () { const data = yield this.getString(name); if (data !== undefined) { return JSON.parse(data); } }); } createReadStream(_emitProgress = false, start = 0, end) { return __awaiter(this, void 0, void 0, function* () { if (start !== 0) { throw new errors_1.NotCapable(); } const entry = yield this.getImageEntry(); const stream = yield this.getStream(entry.fileName); if (stream === undefined) { throw new errors_1.NotCapable(); } if (end !== undefined) { // TODO: handle errors on stream after transform finish event const transform = new stream_limiter_1.StreamLimiter(stream, end + 1); return transform; } return stream; }); } createSparseReadStream(generateChecksums = false) { return __awaiter(this, void 0, void 0, function* () { const metadata = yield this.getMetadata(); if (metadata.blocks === undefined) { throw new errors_1.NotCapable(); } // Verifying and generating checksums makes no sense, so we only verify if generateChecksums is false. const transform = new sparse_filter_stream_1.SparseFilterStream(metadata.blocks, !generateChecksums, generateChecksums); const stream = yield this.createReadStream(false); stream.pipe(transform); return transform; }); } _getMetadata() { return __awaiter(this, void 0, void 0, function* () { const entry = yield this.getImageEntry(); const result = { size: entry.uncompressedSize, compressedSize: entry.compressedSize, }; const prefix = path_1.posix.join(path_1.posix.dirname(entry.fileName), '.meta'); result.logo = yield this.getString(path_1.posix.join(prefix, 'logo.svg')); result.instructions = yield this.getString(path_1.posix.join(prefix, 'instructions.markdown')); const blockMap = yield this.getString(path_1.posix.join(prefix, 'image.bmap')); if (blockMap !== undefined) { result.blockMap = blockmap_1.BlockMap.parse(blockMap); result.blocks = blockmapToBlocks(result.blockMap); result.blockmappedSize = shared_1.blocksLength(result.blocks); } let manifest; try { manifest = yield this.getJson(path_1.posix.join(prefix, 'manifest.json')); } catch (error) { throw new Error('Invalid archive manifest.json'); } let name; if (manifest !== undefined) { name = manifest.name; for (const key of RandomAccessZipSource.manifestFields) { result[key] = manifest[key]; } } result.name = name || path_1.posix.basename(entry.fileName); if (result.logo || result.instructions || result.blockMap || manifest) { result.isEtch = true; } return result; }); } } exports.RandomAccessZipSource = RandomAccessZipSource; RandomAccessZipSource.manifestFields = [ 'bytesToZeroOutFromTheBeginning', 'checksum', 'checksumType', 'recommendedDriveSize', 'releaseNotesUrl', 'supportUrl', 'url', 'version', ]; class ZipSource extends source_source_1.SourceSource { constructor(source, preferStreamSource = false, match = matchSupportedExtensions) { super(source); this.preferStreamSource = preferStreamSource; this.match = match; } prepare() { return __awaiter(this, void 0, void 0, function* () { if (this.implementation === undefined) { if (!this.preferStreamSource && (yield this.source.canRead())) { this.implementation = new RandomAccessZipSource(this.source, this.match); } else { this.implementation = new StreamZipSource(this.source, this.match); } } }); } canCreateReadStream() { return __awaiter(this, void 0, void 0, function* () { yield this.prepare(); return yield this.implementation.canCreateReadStream(); }); } open() { return __awaiter(this, void 0, void 0, function* () { yield this.prepare(); return yield this.implementation.open(); }); } canCreateSparseReadStream() { return __awaiter(this, void 0, void 0, function* () { yield this.prepare(); return yield this.implementation.canCreateSparseReadStream(); }); } createReadStream(emitProgress = false, start = 0, end) { return __awaiter(this, void 0, void 0, function* () { yield this.prepare(); return yield this.implementation.createReadStream(emitProgress, start, end); }); } createSparseReadStream(generateChecksums = false) { return __awaiter(this, void 0, void 0, function* () { yield this.prepare(); return yield this.implementation.createSparseReadStream(generateChecksums); }); } _getMetadata() { return __awaiter(this, void 0, void 0, function* () { yield this.prepare(); return yield this.implementation.getMetadata(); }); } } exports.ZipSource = ZipSource; ZipSource.mimetype = 'application/zip'; source_destination_1.SourceDestination.register(ZipSource); //# sourceMappingURL=zip.js.map