@controlplane/cli
Version:
Control Plane Corporation CLI
147 lines • 5.84 kB
JavaScript
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
;