UNPKG

hackpro-sdk

Version:
240 lines 11.2 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 block_read_stream_1 = require("./block-read-stream"); const block_transform_stream_1 = require("./block-transform-stream"); const constants_1 = require("./constants"); const compressed_source_1 = require("./source-destination/compressed-source"); const multi_destination_1 = require("./source-destination/multi-destination"); const source_destination_1 = require("./source-destination/source-destination"); function getEta(current, total, speed) { return speed === 0 ? undefined : (total - current) / speed; } // This function is the most common use case of the SDK. // Added it here to avoid duplicating it in other projects. function pipeSourceToDestinations(source, destinations, onFail, onProgress, verify = false) { return __awaiter(this, void 0, void 0, function* () { const destination = new multi_destination_1.MultiDestination(destinations); const failures = new Map(); let bytesWritten = 0; const state = { active: destination.destinations.size, flashing: destination.destinations.size, verifying: 0, failed: 0, successful: 0, type: 'flashing', }; destination.on('fail', _onFail); yield Promise.all([source.open(), destination.open()]); const [sourceMetadata, sparseSource, sparseDestination] = yield Promise.all([ source.getMetadata(), source.canCreateSparseReadStream(), destination.canCreateSparseWriteStream(), ]); const sparse = sparseSource && sparseDestination; state.sparse = sparse; state.size = sourceMetadata.size; state.compressedSize = sourceMetadata.compressedSize; state.blockmappedSize = sourceMetadata.blockmappedSize; function updateState(step) { if (step !== undefined) { state.type = step; } state.failed = failures.size; state.active = destination.destinations.size - state.failed; if (state.type === 'flashing') { state.flashing = state.active; state.verifying = 0; } else if (state.type === 'verifying') { state.flashing = 0; state.verifying = state.active; } else if (state.type === 'finished') { state.successful = state.active; } } function _onFail(error) { failures.set(error.destination, error.error); updateState(); onFail(error.destination, error.error); } function _onRootStreamProgress(progress) { state.rootStreamPosition = progress.position; state.rootStreamSpeed = progress.speed; } function _onProgress(progress) { if (sourceMetadata.isSizeEstimated === false && sourceMetadata.size !== undefined) { state.size = sourceMetadata.size; } const totalSpeed = progress.speed * state.active; let size; let percentage; let eta; if (sparse) { size = state.blockmappedSize; bytesWritten = progress.bytes; } else { size = state.size; bytesWritten = progress.position; } if (size !== undefined && bytesWritten !== undefined && bytesWritten <= size) { percentage = (bytesWritten / size) * 100; eta = getEta(bytesWritten, size, progress.speed); } else if (state.rootStreamSpeed !== undefined && state.rootStreamPosition !== undefined && state.compressedSize !== undefined) { percentage = (state.rootStreamPosition / state.compressedSize) * 100; eta = getEta(state.rootStreamPosition, state.compressedSize, state.rootStreamSpeed); } const result = Object.assign({}, progress, state, { totalSpeed, percentage, eta }); onProgress(result); } if (sparse) { yield pipeSparseSourceToDestination(source, destination, verify, updateState, _onFail, _onProgress, _onRootStreamProgress); } else { yield pipeRegularSourceToDestination(source, sourceMetadata, destination, verify, updateState, _onFail, _onProgress, _onRootStreamProgress); } updateState('finished'); yield Promise.all([source.close(), destination.close()]); return { failures, bytesWritten }; }); } exports.pipeSourceToDestinations = pipeSourceToDestinations; function pipeRegularSourceToDestination(source, sourceMetadata, destination, verify, updateState, onFail, onProgress, _onRootStreamProgress) { return __awaiter(this, void 0, void 0, function* () { let lastPosition = 0; const emitSourceProgress = sourceMetadata.size === undefined || sourceMetadata.isSizeEstimated; const [sourceStream, destinationStream] = yield Promise.all([ source.createReadStream(emitSourceProgress), destination.createWriteStream(), ]); compressed_source_1.getRootStream(sourceStream).on('progress', (progress) => { _onRootStreamProgress(progress); }); const checksum = yield new Promise((resolve, reject) => { let result; let done = false; let hasher; function maybeDone(maybeChecksum) { if (maybeChecksum !== undefined) { result = maybeChecksum; } else { done = true; } if (done && (!verify || destination.activeDestinations.size === 0 || result !== undefined)) { if (hasher !== undefined) { sourceStream.unpipe(hasher); hasher.end(); } resolve(result); } } sourceStream.once('error', reject); destinationStream.on('fail', onFail); // This is emitted by MultiDestination when one of its destinations fails destinationStream.once('error', reject); if (verify) { hasher = source_destination_1.createHasher(); hasher.once('checksum', maybeDone); sourceStream.pipe(hasher); } destinationStream.once('done', maybeDone); destinationStream.on('progress', (progress) => { lastPosition = progress.position; onProgress(progress); }); if (!(sourceStream instanceof block_read_stream_1.BlockReadStream) && destination.destinations.size > 1) { // Chunk the input stream in a transform if it's not a block read stream, avoiding // chunking it in each destination stream. sourceStream .pipe(new block_transform_stream_1.BlockTransformStream(constants_1.CHUNK_SIZE)) .pipe(destinationStream); } else { sourceStream.pipe(destinationStream); } }); if (sourceMetadata.size === undefined || sourceMetadata.isSizeEstimated === true) { sourceMetadata.size = lastPosition; sourceMetadata.isSizeEstimated = false; } if (verify && checksum) { updateState('verifying'); const verifier = destination.createVerifier(checksum, lastPosition); yield runVerifier(verifier, onFail, onProgress); } }); } function pipeSparseSourceToDestination(source, destination, verify, updateState, onFail, onProgress, _onRootStreamProgress) { return __awaiter(this, void 0, void 0, function* () { // TODO: if verify is true, we must ensure that source and destination streams hash algorithms are the same const [sourceStream, destinationStream] = yield Promise.all([ source.createSparseReadStream(verify), destination.createSparseWriteStream(), ]); compressed_source_1.getRootStream(sourceStream).on('progress', (progress) => { _onRootStreamProgress(progress); }); yield new Promise((resolve, reject) => { sourceStream.once('error', reject); destinationStream.once('error', reject); destinationStream.once('done', resolve); destinationStream.on('fail', onFail); // This is emitted by MultiDestination when one of its destinations fails destinationStream.on('progress', onProgress); sourceStream.pipe(destinationStream); }); if (verify) { updateState('verifying'); const verifier = destination.createVerifier(sourceStream.blocks); yield runVerifier(verifier, onFail, onProgress); } }); } function runVerifier(verifier, onFail, onProgress) { return __awaiter(this, void 0, void 0, function* () { yield new Promise((resolve, reject) => { verifier.once('error', reject); verifier.once('finish', resolve); verifier.on('fail', onFail); verifier.on('progress', onProgress); verifier.run(); }); }); } //# sourceMappingURL=multi-write.js.map