UNPKG

@ethersphere/swarm-cli

Version:
660 lines (659 loc) 25.4 kB
"use strict"; 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __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"); const history_1 = require("../service/history"); 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 (await 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 (await this.bee.isGateway()) { 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!'); const swarmHash = this.result.getOrThrow().toHex(); this.console.log((0, text_1.createKeyValue)('Swarm hash', swarmHash)); 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 (this.commandConfig.config.historyEnabled && !usedFromOtherCommand) { const history = new history_1.History(this.commandConfig, this.console); history.addItem({ timestamp: Date.now(), reference: swarmHash, stamp: this.stamp, path: this.path, uploadType: this.uploadType(), }); } if (!usedFromOtherCommand) { this.console.quiet(this.result.getOrThrow().toHex()); if (!(await this.bee.isGateway()) && !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)(this.path ? `Uploading ${this.path}...` : 'Uploading data from stdin...'); 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(this.path ? `Data syncing timeout for ${this.path} (${syncProgress} / ${tag.split})` : `Data syncing timeout (${syncProgress} / ${tag.split})`); (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; } async hasUnsupportedGatewayOptions() { if (!(await this.bee.isGateway())) { 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}`); } } uploadType() { if (this.stdin) { return 'stdin'; } const stats = FS.lstatSync(this.path); if (stats.isDirectory()) { return 'folder'; } else { return 'file'; } } } exports.Upload = Upload; __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: 1000, }), __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: 60, }), __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);