hackpro-sdk
Version:
286 lines • 12 kB
JavaScript
"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 bluebird_1 = require("bluebird");
const events_1 = require("events");
const lodash_1 = require("lodash");
const stream_1 = require("stream");
const constants_1 = require("../constants");
const errors_1 = require("../errors");
const utils_1 = require("../utils");
const source_destination_1 = require("./source-destination");
function isntNull(x) {
return x !== null;
}
class MultiDestinationError extends Error {
constructor(error, destination) {
super();
this.error = error;
this.destination = destination;
}
}
exports.MultiDestinationError = MultiDestinationError;
class MultiDestinationVerifier extends source_destination_1.Verifier {
constructor(source, checksumOrBlocks, size) {
super();
this.verifiers = new Set();
for (const dest of source.activeDestinations) {
const verifier = dest.createVerifier(checksumOrBlocks, size);
verifier.on('error', (error) => {
this.oneVerifierFinished(verifier);
source.destinationError(dest, error, this);
});
verifier.on('finish', () => {
this.oneVerifierFinished(verifier);
});
this.verifiers.add(verifier);
}
}
oneVerifierFinished(verifier) {
if (!this.verifiers.has(verifier)) {
return;
}
if (this.verifiers.size === 1) {
clearInterval(this.timer);
this.emitProgress();
this.emit('finish');
}
this.verifiers.delete(verifier);
}
emitProgress() {
// TODO: avoid Array.from
const verifier = lodash_1.minBy(Array.from(this.verifiers), (v) => {
return v.progress.bytes;
});
if (verifier !== undefined) {
this.emit('progress', verifier.progress);
}
}
run() {
return __awaiter(this, void 0, void 0, function* () {
if (this.verifiers.size === 0) {
this.emit('finish');
return;
}
this.timer = setInterval(this.emitProgress.bind(this), constants_1.PROGRESS_EMISSION_INTERVAL);
for (const verifier of this.verifiers) {
verifier.run();
}
});
}
}
exports.MultiDestinationVerifier = MultiDestinationVerifier;
class MultiDestination extends source_destination_1.SourceDestination {
constructor(destinations) {
super();
// MultiDestination does not emit 'error' events, only 'fail' events wrapping the original error in a MultiDestinationError
this.destinations = new Set();
this.erroredDestinations = new Set();
if (destinations.length === 0) {
throw new Error('At least one destination is required');
}
destinations.map((destination) => {
this.destinations.add(destination);
});
}
destinationError(destination, error, stream) {
// If a stream is provided, emit the 'fail' event on it, instead of this instance.
if (!(error instanceof errors_1.VerificationError)) {
// Verification errors aren't fatal
this.erroredDestinations.add(destination);
}
error = new MultiDestinationError(error, destination);
// Don't emit 'error' events as it would unpipe the source from the stream
if (stream !== undefined) {
stream.emit('fail', error);
}
else {
this.emit('fail', error);
}
}
get activeDestinations() {
return utils_1.difference(this.destinations, this.erroredDestinations);
}
can(methodName) {
return __awaiter(this, void 0, void 0, function* () {
return lodash_1.every(yield bluebird_1.map(this.activeDestinations, (destination) => __awaiter(this, void 0, void 0, function* () {
return yield destination[methodName]();
})));
});
}
canRead() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.can('canRead');
});
}
canWrite() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.can('canWrite');
});
}
canCreateReadStream() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.can('canCreateReadStream');
});
}
canCreateSparseReadStream() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.can('canCreateSparseReadStream');
});
}
canCreateWriteStream() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.can('canCreateWriteStream');
});
}
canCreateSparseWriteStream() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.can('canCreateSparseWriteStream');
});
}
read(buffer, bufferOffset, length, sourceOffset) {
return __awaiter(this, void 0, void 0, function* () {
// Reads from the first destination (supposing all destinations contain the same data)
return yield Array.from(this.activeDestinations)[0].read(buffer, bufferOffset, length, sourceOffset);
});
}
write(buffer, bufferOffset, length, fileOffset) {
return __awaiter(this, void 0, void 0, function* () {
const results = yield bluebird_1.map(this.activeDestinations, (destination) => __awaiter(this, void 0, void 0, function* () {
return yield destination.write(buffer, bufferOffset, length, fileOffset);
}));
// Returns the first WriteResult (they should be all the same)
return results[0];
// TODO: handle errors so one destination can fail
});
}
createReadStream(...args) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: raise an error or a warning here
return yield Array.from(this.activeDestinations)[0].createReadStream(...args);
});
}
createSparseReadStream(...args) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: raise an error or a warning here
return yield Array.from(this.activeDestinations)[0].createSparseReadStream(...args);
});
}
createStream(methodName) {
return __awaiter(this, void 0, void 0, function* () {
const passthrough = new stream_1.PassThrough({
objectMode: methodName === 'createSparseWriteStream',
});
// all streams listen to end events, +1 because we'll listen too
const listeners = this.activeDestinations.size + 1;
if (listeners > events_1.EventEmitter.defaultMaxListeners) {
passthrough.setMaxListeners(listeners);
}
const progresses = new Map();
let interval;
function oneStreamFinished(stream) {
if (!progresses.has(stream)) {
return;
}
if (progresses.size === 1) {
clearInterval(interval);
emitProgress(); // Just to be sure we emitted the last state
passthrough.emit('done');
}
progresses.delete(stream);
}
function emitProgress() {
// TODO: avoid Array.from
const leastAdvancedProgress = lodash_1.minBy(Array.from(progresses.values()).filter(isntNull), 'position');
if (leastAdvancedProgress !== undefined) {
passthrough.emit('progress', leastAdvancedProgress);
}
}
yield bluebird_1.map(this.activeDestinations, (destination) => __awaiter(this, void 0, void 0, function* () {
const stream = yield destination[methodName]();
progresses.set(stream, null);
stream.on('progress', (progressEvent) => {
progresses.set(stream, progressEvent);
if (interval === undefined) {
interval = setInterval(emitProgress, constants_1.PROGRESS_EMISSION_INTERVAL);
}
});
stream.on('error', (error) => {
this.destinationError(destination, error, passthrough);
oneStreamFinished(stream);
});
stream.on('finish', oneStreamFinished.bind(null, stream));
passthrough.pipe(stream);
}));
passthrough.on('pipe', () => {
// Handle the special case where we have zero destination streams
if (this.activeDestinations.size === 0) {
passthrough.emit('done');
}
});
return passthrough;
});
}
createWriteStream() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.createStream('createWriteStream');
});
}
createSparseWriteStream() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.createStream('createSparseWriteStream');
});
}
createVerifier(checksumOrBlocks, size) {
return new MultiDestinationVerifier(this, checksumOrBlocks, size);
}
_open() {
return __awaiter(this, void 0, void 0, function* () {
yield bluebird_1.map(this.destinations, (destination) => __awaiter(this, void 0, void 0, function* () {
try {
yield destination.open();
}
catch (error) {
this.destinationError(destination, error);
}
}));
});
}
_close() {
return __awaiter(this, void 0, void 0, function* () {
yield bluebird_1.map(this.destinations, (destination) => __awaiter(this, void 0, void 0, function* () {
try {
yield destination.close();
}
catch (error) {
this.destinationError(destination, error);
}
}));
});
}
}
exports.MultiDestination = MultiDestination;
//# sourceMappingURL=multi-destination.js.map