hackpro-sdk
Version:
240 lines • 11.2 kB
JavaScript
;
/*
* 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