@ethersphere/swarm-cli
Version:
CLI tool for Bee
624 lines (623 loc) • 24.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Upload = void 0;
const bee_js_1 = require("@ethersphere/bee-js");
const cafe_utility_1 = require("cafe-utility");
const cli_progress_1 = require("cli-progress");
const FS = __importStar(require("fs"));
const furious_commander_1 = require("furious-commander");
const path_1 = require("path");
const process_1 = require("process");
const curl_1 = require("../curl");
const stamp_1 = require("../service/stamp");
const utils_1 = require("../utils");
const error_1 = require("../utils/error");
const mime_1 = require("../utils/mime");
const option_1 = require("../utils/option");
const spinner_1 = require("../utils/spinner");
const text_1 = require("../utils/text");
const root_command_1 = require("./root-command");
const command_log_1 = require("./root-command/command-log");
class Upload extends root_command_1.RootCommand {
constructor() {
super(...arguments);
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: 'upload'
});
Object.defineProperty(this, "alias", {
enumerable: true,
configurable: true,
writable: true,
value: 'up'
});
Object.defineProperty(this, "description", {
enumerable: true,
configurable: true,
writable: true,
value: 'Upload file to Swarm'
});
Object.defineProperty(this, "path", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "stdin", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "stamp", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "pin", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "encrypt", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "deferred", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "act", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "optHistoryAddress", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "sync", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "dropName", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "syncPollingTime", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "syncPollingTrials", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "indexDocument", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "errorDocument", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "fileName", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "contentType", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "redundancy", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "stdinData", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "historyAddress", {
enumerable: true,
configurable: true,
writable: true,
value: cafe_utility_1.Optional.empty()
});
}
// eslint-disable-next-line complexity
async run(usedFromOtherCommand = false) {
super.init();
if (this.hasUnsupportedGatewayOptions()) {
(0, process_1.exit)(1);
}
await this.maybePrintSyncWarning();
if (!this.stdin && !FS.existsSync(this.path)) {
throw new error_1.CommandLineError(`Given filepath '${this.path}' doesn't exist`);
}
if (this.stdin) {
if (!this.stamp) {
throw new error_1.CommandLineError('Stamp must be passed when reading data from stdin');
}
this.stdinData = await (0, utils_1.readStdin)(this.console);
}
if (!this.stamp) {
if ((0, utils_1.isGateway)(this.beeApiUrl)) {
this.stamp = '0'.repeat(64);
}
else {
this.stamp = await (0, stamp_1.pickStamp)(this.bee, this.console);
}
}
await this.maybePrintRedundancyStats();
const tag = this.sync ? await this.bee.createTag() : undefined;
const uploadingFolder = !this.stdin && FS.statSync(this.path).isDirectory();
if (uploadingFolder && !this.indexDocument && (0, utils_1.fileExists)((0, path_1.join)(this.path, 'index.html'))) {
this.console.info('Setting --index-document to index.html');
this.indexDocument = 'index.html';
}
const url = await this.uploadAnyWithSpinner(tag, uploadingFolder);
this.console.dim('Data has been sent to the Bee node successfully!');
this.console.log((0, text_1.createKeyValue)('Swarm hash', this.result.getOrThrow().toHex()));
if (this.act) {
this.console.log((0, text_1.createKeyValue)('Swarm history address', this.historyAddress.getOrThrow().toHex()));
}
this.console.dim('Waiting for file chunks to be synced on Swarm network...');
if (this.sync && tag) {
await this.waitForFileSynced(tag);
}
this.console.dim('Uploading was successful!');
this.console.log((0, text_1.createKeyValue)('URL', url));
if (!usedFromOtherCommand) {
this.console.quiet(this.result.getOrThrow().toHex());
if (!(0, utils_1.isGateway)(this.beeApiUrl) && !this.quiet) {
(0, stamp_1.printStamp)(await this.bee.getPostageBatch(this.stamp), this.console, { shortenBatchId: true });
}
}
}
async uploadAnyWithSpinner(tag, isFolder) {
const spinner = (0, spinner_1.createSpinner)('Uploading data...');
if (this.verbosity !== command_log_1.VerbosityLevel.Quiet && !this.curl) {
spinner.start();
}
try {
const url = await this.uploadAny(tag, isFolder);
return url;
}
finally {
if (spinner.isSpinning) {
spinner.stop();
}
}
}
uploadAny(tag, isFolder) {
if (this.stdin) {
return this.uploadStdin(tag);
}
else {
if (isFolder) {
return this.uploadFolder(tag);
}
else {
return this.uploadSingleFile(tag);
}
}
}
async uploadStdin(tag) {
if (this.fileName) {
const contentType = this.contentType || (0, mime_1.getMime)(this.fileName) || undefined;
const { reference, historyAddress } = await this.bee.uploadFile(this.stamp, this.stdinData, this.fileName, {
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
contentType,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
});
this.result = cafe_utility_1.Optional.of(reference);
if (this.act) {
this.historyAddress = historyAddress;
}
return `${this.bee.url}/bzz/${reference.toHex()}/`;
}
else {
const { reference, historyAddress } = await this.bee.uploadData(this.stamp, this.stdinData, {
tag: tag?.uid,
deferred: this.deferred,
encrypt: this.encrypt,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
});
this.result = cafe_utility_1.Optional.of(reference);
if (this.act) {
this.historyAddress = historyAddress;
}
return `${this.bee.url}/bytes/${reference.toHex()}`;
}
}
async uploadFolder(tag) {
(0, curl_1.setCurlStore)({
path: this.path,
folder: true,
type: 'buffer',
});
const { reference, historyAddress } = await this.bee.uploadFilesFromDirectory(this.stamp, this.path, {
indexDocument: this.indexDocument,
errorDocument: this.errorDocument,
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
});
this.result = cafe_utility_1.Optional.of(reference);
if (this.act) {
this.historyAddress = historyAddress;
}
return `${this.bee.url}/bzz/${reference.toHex()}/`;
}
async uploadSingleFile(tag) {
const contentType = this.contentType || (0, mime_1.getMime)(this.path) || undefined;
(0, curl_1.setCurlStore)({
path: this.path,
folder: false,
type: 'stream',
});
const readable = FS.createReadStream(this.path);
const parsedPath = (0, path_1.parse)(this.path);
const { reference, historyAddress } = await this.bee.uploadFile(this.stamp, readable, this.determineFileName(parsedPath.base), {
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
contentType,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
});
this.result = cafe_utility_1.Optional.of(reference);
if (this.act) {
this.historyAddress = historyAddress;
}
return `${this.bee.url}/bzz/${reference.toHex()}/`;
}
/**
* Waits until the data syncing is successful on Swarm network
*
* @param tag had to be attached to the uploaded file
*
* @returns whether the file sync was successful or not.
*/
async waitForFileSynced(tag) {
const tagUid = tag.uid;
const pollingTime = this.syncPollingTime;
const pollingTrials = this.syncPollingTrials;
let synced = false;
let syncProgress = 0;
const progressBar = new cli_progress_1.SingleBar({ clearOnComplete: true }, cli_progress_1.Presets.rect);
if (this.verbosity !== command_log_1.VerbosityLevel.Quiet && !this.curl) {
progressBar.start(tag.split, 0);
}
for (let i = 0; i < pollingTrials; i++) {
tag = await this.bee.retrieveTag(tagUid);
const newSyncProgress = tag.seen + tag.synced;
if (newSyncProgress > syncProgress) {
i = 0;
}
syncProgress = newSyncProgress;
if (syncProgress >= tag.split) {
synced = true;
break;
}
if (this.curl) {
this.console.log(`${syncProgress} / ${tag.split}`);
}
else {
progressBar.setTotal(tag.split);
progressBar.update(syncProgress);
}
await cafe_utility_1.System.sleepMillis(pollingTime);
}
progressBar.stop();
if (synced) {
this.console.dim('Data has been synced on Swarm network');
}
else {
this.console.error('Data syncing timeout.');
(0, process_1.exit)(1);
}
}
async maybePrintRedundancyStats() {
if (!this.redundancy || this.quiet) {
return;
}
const currentSetting = bee_js_1.Utils.getRedundancyStat(this.redundancy);
const originalSize = await this.getUploadSize();
const originalChunks = Math.ceil(originalSize / 4e3);
const sizeMultiplier = bee_js_1.Utils.approximateOverheadForRedundancyLevel(originalChunks, currentSetting.value, this.encrypt);
const newSize = originalChunks * 4e3 * (1 + sizeMultiplier);
const extraSize = newSize - originalSize;
this.console.log((0, text_1.createKeyValue)('Redundancy setting', currentSetting.label));
this.console.log(`This setting will provide ${Math.round(currentSetting.errorTolerance * 100)}% error tolerance.`);
this.console.log(`An additional ${cafe_utility_1.Numbers.convertBytes(extraSize)} of data will be uploaded approximately.`);
this.console.log(`${cafe_utility_1.Numbers.convertBytes(originalSize)} → ${cafe_utility_1.Numbers.convertBytes(newSize)} (+${cafe_utility_1.Numbers.convertBytes(extraSize)})`);
if (!this.yes && !this.quiet) {
const confirmation = await this.console.confirm('Do you want to proceed?');
if (!confirmation) {
(0, process_1.exit)(0);
}
}
}
async getUploadSize() {
let size = -1;
if (this.stdin) {
size = this.stdinData.length;
}
else {
const stats = FS.lstatSync(this.path);
size = stats.isDirectory() ? await bee_js_1.Utils.getFolderSize(this.path) : stats.size;
}
this.console.verbose(`Upload size is approximately ${cafe_utility_1.Numbers.convertBytes(size)}`);
return size;
}
hasUnsupportedGatewayOptions() {
if (!(0, utils_1.isGateway)(this.beeApiUrl)) {
return false;
}
if (this.act) {
this.console.error('You are trying to upload to the gateway which does not support ACT.');
this.console.error('Please try again without the --act option.');
return true;
}
if (this.pin) {
this.console.error('You are trying to upload to the gateway which does not support pinning.');
this.console.error('Please try again without the --pin option.');
return true;
}
if (this.sync) {
this.console.error('You are trying to upload to the gateway which does not support syncing.');
this.console.error('Please try again without the --sync option.');
return true;
}
if (this.encrypt) {
this.console.error('You are trying to upload to the gateway which does not support encryption.');
this.console.error('Please try again without the --encrypt option.');
return true;
}
return false;
}
async maybePrintSyncWarning() {
if (this.quiet || !this.sync) {
return;
}
const connectedPeers = await this.getConnectedPeers();
if (connectedPeers === null) {
this.console.log((0, text_1.warningSymbol)());
this.console.log((0, text_1.warningText)('Could not fetch connected peers info.'));
this.console.log((0, text_1.warningText)('Are you uploading to a gateway node?'));
this.console.log((0, text_1.warningText)('Synchronization may time out.'));
}
else if (connectedPeers === 0) {
this.console.log((0, text_1.warningSymbol)());
this.console.log((0, text_1.warningText)('Your Bee node has no connected peers.'));
this.console.log((0, text_1.warningText)('Synchronization may time out.'));
}
}
async getConnectedPeers() {
try {
const { connected } = await this.bee.getTopology();
return connected;
}
catch {
return null;
}
}
determineFileName(defaultName) {
if (this.dropName) {
return undefined;
}
if (this.fileName) {
return this.fileName;
}
return defaultName;
}
determineRedundancyLevel() {
if (!this.redundancy) {
return undefined;
}
switch (this.redundancy.toUpperCase()) {
case 'MEDIUM':
return bee_js_1.RedundancyLevel.MEDIUM;
case 'STRONG':
return bee_js_1.RedundancyLevel.STRONG;
case 'INSANE':
return bee_js_1.RedundancyLevel.INSANE;
case 'PARANOID':
return bee_js_1.RedundancyLevel.PARANOID;
default:
throw new error_1.CommandLineError(`Invalid redundancy level: ${this.redundancy}`);
}
}
}
__decorate([
(0, furious_commander_1.Argument)({
key: 'path',
description: 'Path to the file or folder',
required: true,
autocompletePath: true,
conflicts: 'stdin',
}),
__metadata("design:type", String)
], Upload.prototype, "path", void 0);
__decorate([
(0, furious_commander_1.Option)({ key: 'stdin', type: 'boolean', description: 'Take data from standard input', conflicts: 'path' }),
__metadata("design:type", Boolean)
], Upload.prototype, "stdin", void 0);
__decorate([
(0, furious_commander_1.Option)(option_1.stampProperties),
__metadata("design:type", String)
], Upload.prototype, "stamp", void 0);
__decorate([
(0, furious_commander_1.Option)({ key: 'pin', type: 'boolean', description: 'Persist the uploaded data on the node' }),
__metadata("design:type", Boolean)
], Upload.prototype, "pin", void 0);
__decorate([
(0, furious_commander_1.Option)({ key: 'encrypt', type: 'boolean', description: 'Encrypt uploaded data' }),
__metadata("design:type", Boolean)
], Upload.prototype, "encrypt", void 0);
__decorate([
(0, furious_commander_1.Option)({ key: 'deferred', type: 'boolean', description: 'Do not wait for network sync', default: true }),
__metadata("design:type", Boolean)
], Upload.prototype, "deferred", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'act',
type: 'boolean',
description: 'Upload with ACT',
default: false,
required: { when: 'act-history-address' },
}),
__metadata("design:type", Boolean)
], Upload.prototype, "act", void 0);
__decorate([
(0, furious_commander_1.Option)({ key: 'act-history-address', type: 'string', description: 'ACT history address' }),
__metadata("design:type", String)
], Upload.prototype, "optHistoryAddress", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'sync',
type: 'boolean',
description: 'Wait for chunk synchronization over the network',
}),
__metadata("design:type", Boolean)
], Upload.prototype, "sync", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'drop-name',
type: 'boolean',
description: 'Erase file name when uploading a single file',
conflicts: 'name',
}),
__metadata("design:type", Boolean)
], Upload.prototype, "dropName", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'sync-polling-time',
description: 'Waiting time in ms between sync pollings',
type: 'number',
default: 500,
}),
__metadata("design:type", Number)
], Upload.prototype, "syncPollingTime", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'sync-polling-trials',
description: 'After the given trials the sync polling will stop',
type: 'number',
default: 15,
}),
__metadata("design:type", Number)
], Upload.prototype, "syncPollingTrials", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'index-document',
description: 'Default retrieval file on bzz request without provided filepath',
}),
__metadata("design:type", String)
], Upload.prototype, "indexDocument", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'error-document',
description: 'Default error file on bzz request without with wrong filepath',
}),
__metadata("design:type", String)
], Upload.prototype, "errorDocument", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'name',
description: 'Set the name of the uploaded file',
conflicts: 'drop-name',
}),
__metadata("design:type", String)
], Upload.prototype, "fileName", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'content-type',
description: 'Content type when uploading a single file',
}),
__metadata("design:type", String)
], Upload.prototype, "contentType", void 0);
__decorate([
(0, furious_commander_1.Option)({
key: 'redundancy',
description: 'Redundancy of the upload (MEDIUM, STRONG, INSANE, PARANOID)',
}),
__metadata("design:type", String)
], Upload.prototype, "redundancy", void 0);
exports.Upload = Upload;