UNPKG

@ohd-tools/utils

Version:
187 lines (186 loc) 7.65 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 __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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SFTPTail = void 0; /* eslint-disable @typescript-eslint/no-explicit-any */ const node_crypto_1 = __importDefault(require("node:crypto")); const node_events_1 = __importDefault(require("node:events")); const node_fs_1 = __importDefault(require("node:fs")); const promises_1 = require("node:fs/promises"); const node_path_1 = __importDefault(require("node:path")); class SFTPTail extends node_events_1.default { client; options; filePath; fetchLoopActive; lastByteReceived; fetchLoopPromise; log; tmpFilePath; constructor(options) { super(); // Set default options. this.options = { sftp: {}, fetchInterval: 0, tailLastBytes: 10 * 1000, log: false, ...options, }; // Setup logger. if (typeof this.options.log === 'function') { this.log = this.options.log; this.options.sftp.debug = this.options.log; } else if (this.options.log) { this.log = console.log; this.options.sftp.debug = console.log; } else { this.log = () => { }; this.options.sftp.debug = () => { }; } // Setup internal properties. this.filePath = null; this.lastByteReceived = null; this.fetchLoopActive = false; this.fetchLoopPromise = null; } async setup() { const { default: Client } = await Promise.resolve().then(() => __importStar(require('ssh2-sftp-client'))); // Setup basic-ftp client. this.client = new Client(); } async watch(filePath) { this.filePath = filePath; // Setup temp file. this.tmpFilePath = node_path_1.default.join(process.cwd(), node_crypto_1.default .createHash('md5') .update(`${this.options.sftp.host}:${this.options.sftp.port}:${this.filePath}`) .digest('hex') + '.tmp'); // Connect. await this.connect(); // Start fetch loop. this.log('Starting fetch loop...'); this.fetchLoopActive = true; this.fetchLoopPromise = this.fetchLoop(); } async unwatch() { this.log('Stopping fetch loop...'); this.fetchLoopActive = false; await this.fetchLoopPromise; } async fetchLoop() { while (this.fetchLoopActive) { try { // Store the start time of the loop. const fetchStartTime = Date.now(); // Reconnect the FTP client in case it has disconnected. await this.connect(); // Get the size of the file on the FTP server. this.log('Fetching size of file...'); const fileSize = (await this.client.stat(this.filePath)).size; this.log(`File size is ${fileSize}.`); // If the file size has not changed then skip this loop iteration. if (fileSize === this.lastByteReceived) { this.log('File has not changed.'); await this.sleep(this.options.fetchInterval); } // If the file has not been tailed before or it has been decreased in size download the last // few bytes. if (this.lastByteReceived === null || this.lastByteReceived > fileSize) { this.log('File has not been tailed before or has decreased in size.'); this.lastByteReceived = Math.max(0, fileSize - this.options.tailLastBytes); } // Download the data to a temp file overwritting any previous data. this.log(`Downloading file with offset of ${this.lastByteReceived}...`); await this.client.get(this.filePath, this.tmpFilePath, { readStreamOptions: { start: this.lastByteReceived, }, }); // Update the last byte marker - this is so we can get data since this position on the next // FTP download. const downloadSize = node_fs_1.default.statSync(this.tmpFilePath).size; this.lastByteReceived += downloadSize; this.log(`Downloaded file of size ${downloadSize}.`); // Get contents of download. const data = await (0, promises_1.readFile)(this.tmpFilePath, 'utf8'); // Only continue if something was fetched. if (data.length === 0) { this.log('No data was fetched.'); await this.sleep(this.options.fetchInterval); continue; } data // Remove trailing new lines. .replace(/\r\n$/, '') // Split the data on the lines. .split('\r\n') // Emit each line. .forEach((line) => this.emit('line', line)); // Log the loop runtime. const fetchEndTime = Date.now(); const fetchTime = fetchEndTime - fetchStartTime; this.log(`Fetch loop took ${fetchTime}ms.`); await this.sleep(this.options.fetchInterval); } catch (err) { this.emit('error', err); this.log(`Error in fetch loop: ${err.stack}`); } } if (node_fs_1.default.existsSync(this.tmpFilePath)) { node_fs_1.default.unlinkSync(this.tmpFilePath); this.log('Deleted temp file.'); } await this.disconnect(); } async connect() { if (this.client.sftp) return; this.log('Connecting to SFTP server...'); await this.client.connect(this.options.sftp); this.emit('connected'); this.log('Connected to SFTP server.'); } async disconnect() { if (!this.client.sftp) return; this.log('Disconnecting from SFTP server...'); await this.client.end(); this.emit('disconnect'); this.log('Disconnected from SFTP server.'); } async sleep(ms) { this.log(`Sleeping for ${ms} seconds...`); await new Promise((resolve) => setTimeout(resolve, ms)); } } exports.SFTPTail = SFTPTail;