UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

147 lines 5.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TarFileTransfer = void 0; const path = require("path"); const fs = require("fs"); const tar = require("tar-stream"); const progress_bar_1 = require("./progress-bar"); const ws_writable_1 = require("./ws-writable"); const constants_1 = require("./constants"); class TarFileTransfer { constructor(srcPath, destPath, ws) { // Private Properties this.openStreams = 0; this.progressBar = new progress_bar_1.ProgressBar(); // - Readonly Properties this.srcPath = path.normalize(srcPath); this.destPath = path.normalize(destPath); this.ws = ws; // Pre-calculate total size of the source path this.totalSize = this.calculateTotalSize(this.srcPath) + 1024; } // Public Methods // isComplete() { return this.progressBar.isComplete(); } stopProgress() { this.progressBar.stop(); } async transfer() { // Start the progress bar immediately with the computed total size this.progressBar.start(this.totalSize); // Initialize tar-stream pack const pack = tar.pack(); // Create our custom writable that handles backpressure on the WebSocket const wsWritable = new ws_writable_1.WebSocketWritable(this.ws); // Pipe the data from the tar to the replica over the WebSocket connection pack.pipe(wsWritable); // Subscribe to the error event to handle errors pack.on('error', (err) => { if (err) { throw err; } }); // Get the base destination name const destFile = path.basename(this.destPath); // Pack the src file or directory async await this.packFileOrDirectory(this.srcPath, destFile, pack); // After opening all the necessary streams, periodically check if they were closed this.checkFinalization(pack); } updateProgress(bytes) { this.progressBar.update(bytes); } // Private Methods // calculateTotalSize(srcPath) { // Get the file statistics for the given source path const stat = fs.statSync(srcPath); // 512-byte header for the current entry let size = 512; if (stat.isDirectory()) { // For directories, add header const entries = fs.readdirSync(srcPath); // Iterate over entries and add to the size recursively for (const entry of entries) { size += this.calculateTotalSize(path.join(srcPath, entry)); } } else if (stat.isSymbolicLink()) { // For symlinks, just the header } else { // For regular files, add file size plus padding to the next 512 bytes size += stat.size; if (stat.size % 512 !== 0) { size += 512 - (stat.size % 512); } } return size; } checkFinalization(pack) { if (this.openStreams == 0) { // Finalize the tar archive; finalize() will end the pack stream pack.finalize(); return; } setTimeout(() => this.checkFinalization(pack), 100); } async packFileOrDirectory(srcPath, destFile, pack) { // Get the file statistics for the given file path const stat = fs.statSync(srcPath); // Check if the current path is a directory if (stat.isDirectory()) { // Add an entry for the directory to the tar archive pack.entry({ name: destFile + '/', type: 'directory' }); // Read all files and directories inside the current directory const subFileNames = fs.readdirSync(srcPath); for (const srcSubFileName of subFileNames) { // Recursively add each file or directory to the tar archive this.packFileOrDirectory(path.join(srcPath, srcSubFileName), path.join(destFile, srcSubFileName), pack); } return; } else if (stat.isSymbolicLink()) { // Add an entry for the symbolic link to the tar archive const linkTarget = fs.readlinkSync(srcPath); await new Promise((resolve, reject) => { pack.entry({ name: destFile, type: 'symlink', linkname: linkTarget }, (err) => { if (err) { reject(err); return; } resolve(); }); }); return; } // For regular files, wait for the open streams to reach the limit while (this.openStreams >= constants_1.MAX_CONCURRENT_STREAMS) { await new Promise((res) => setTimeout(res, 10)); } // Increase the amount of open streams this.openStreams++; // Create a new pack entry const headers = { name: destFile, size: stat.size }; await new Promise((resolve, reject) => { const entry = pack.entry(headers, (err) => { if (err) { reject(err); return; } resolve(); }); // Create a read stream out of the file const fileStream = fs.createReadStream(srcPath); // Pipe the file stream to the tar entry fileStream.pipe(entry); // Decrease the amount of open streams after streaming the file fileStream.on('close', () => { this.openStreams--; }); // Reject on file stream error fileStream.on('error', (err) => reject(err)); }); } } exports.TarFileTransfer = TarFileTransfer; //# sourceMappingURL=tar-file-transfer.js.map