import { Client } from 'ssh2'; import fs from 'fs'; import path from 'path'; export default class RemoteFetcher { constructor(config) { this.config = { host: config.host, port: config.port || 22, username: config.username, password: config.password, readyTimeout: 40000, keepaliveInterval: 10000 }; this.conn = new Client(); } connect() { return new Promise((resolve, reject) => { this.conn.on('ready', () => { this.conn.sftp((err, sftp) => { if (err) return reject(err); this.sftp = sftp; resolve(); }); }) .on('error', (err) => reject(err)) .connect(this.config); }); } // Volvemos a fastGet pero con configuración optimizada para velocidad/estabilidad async fetchFile(remotePath, localPath, onProgress) { return new Promise((resolve, reject) => { this.sftp.fastGet(remotePath, localPath, { concurrency: 6, // 6 descargas en paralelo para mayor velocidad chunkSize: 32768, // Bloques de 32KB step: (transferred, chunk, total) => { if (onProgress) onProgress(transferred, total); } }, (err) => { if (err) return reject(err); resolve(); }); }); } async listFiles(remoteDir, extension = '') { return new Promise((resolve, reject) => { this.sftp.readdir(remoteDir, (err, list) => { if (err) return reject(err); const files = list .filter(f => f.attrs.isFile()) .map(f => f.filename); if (extension) { resolve(files.filter(f => f.toLowerCase().endsWith(extension.toLowerCase()))); } else { resolve(files); } }); }); } async listDirectories(remoteDir) { return new Promise((resolve, reject) => { this.sftp.readdir(remoteDir, (err, list) => { if (err) return reject(err); resolve(list.filter(f => f.attrs.isDirectory() && f.filename !== '.' && f.filename !== '..').map(f => f.filename)); }); }); } async getFileStats(remotePath) { return new Promise((resolve, reject) => { this.sftp.stat(remotePath, (err, stats) => { if (err) return reject(err); resolve(stats); }); }); } disconnect() { this.conn.end(); } }