UNPKG

filefive

Version:

SFTP/FTP/Amazon S3 client and dual-panel file manager for macOS and Linux

222 lines (221 loc) 8.63 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 () { 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const node_fs_1 = require("node:fs"); const node_os_1 = require("node:os"); const node_path_1 = require("node:path"); const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap")); const types_1 = require("./types"); const URI_1 = require("./utils/URI"); const uniqid_1 = __importDefault(require("./utils/uniqid")); const log_1 = __importStar(require("./log")); const Local_1 = __importDefault(require("./fs/Local")); const SFtp_1 = __importStar(require("./fs/SFtp")); const Ftp_1 = __importStar(require("./fs/Ftp")); const S3_1 = __importStar(require("./fs/S3")); const options_1 = __importDefault(require("./options")); class default_1 { static initialize() { this.shared.set(types_1.LocalFileSystemID, new Local_1.default); } static async open(protocol, user, host, port, password, privatekey) { const id = (0, URI_1.connectionID)(protocol, user, host, port); const attrs = { sftp: SFtp_1.ATTRIBUTES, ftp: Ftp_1.ATTRIBUTES, s3: S3_1.ATTRIBUTES }[protocol]; if (this.shared.inc(id)) { return attrs; } this.protocols.set(id, protocol); if (privatekey) { if (privatekey.startsWith('~')) { privatekey = (0, node_path_1.join)((0, node_os_1.homedir)(), privatekey.substring(1)); } this.credentials.set(id, ['key', (0, node_fs_1.readFileSync)(privatekey)]); } else { this.credentials.set(id, ['password', password]); } const conn = await this.create(id); await conn.open(); this.shared.set(id, conn); return attrs; } static get(id) { return this.shared.get(id); } static close(id) { this.shared.dec(id)?.close(); } static async list(id, path) { const files = await this.get(id)?.ls(path) || []; return files.map(f => ({ ...f, URI: (0, URI_1.createURI)(id, f.path) })); } static async transmit(id) { if (id == types_1.LocalFileSystemID) { return [new Local_1.default, () => { }]; } const conn = await this.hold(id); if (conn) { return [conn[0], () => this.release(id, conn[1])]; } return new Promise((resolve) => { const onRelease = (poolId) => { const { fs } = this.pools.get(id).get(poolId); resolve([fs, () => this.release(id, poolId)]); }; id in this.pending ? this.pending[id].push(onRelease) : (this.pending[id] = [onRelease]); }); } static async hold(id) { !this.pools.has(id) && this.pools.set(id, new Map()); const pool = this.pools.get(id); const conn = this.getIdle(id); if (conn) { return conn; } if (pool.size < this.getLimit(id)) { return new Promise((resolve) => { const connect = async () => { const conn = this.getIdle(id); if (conn) { resolve(conn); return; } const poolId = (0, uniqid_1.default)(); try { const onClose = () => this.pools.get(id)?.delete(poolId); const fs = await this.create(id, onClose); await fs.open(); this.pools.get(id).set(poolId, { fs, idle: false }); resolve([fs, poolId]); } catch (e) { const conn = this.getIdle(id); if (conn) { resolve(conn); return; } } resolve(null); }; this.queue.push(connect); if (this.queue.length == 1) { this.connectNext(); } }); } return null; } static getIdle(id) { const conn = Array.from(this.pools.get(id)?.entries() || []).find(([, { idle }]) => idle !== false); if (conn) { conn[1].idle !== false && clearTimeout(conn[1].idle); conn[1].idle = false; return [conn[1].fs, conn[0]]; } return null; } static async connectNext() { this.queue .splice(0, Math.max(this.maxStartups - this.numOfStartups, 0)) .map(connect => async () => { await connect(); this.numOfStartups--; this.connectNext(); }) .forEach(f => { this.numOfStartups++; f(); }); } static release(id, poolId) { const conn = this.pools.get(id)?.get(poolId); if (conn) { if (this.pending[id]?.length) { this.pending[id].shift()(poolId); } else { conn.idle = setTimeout(() => { conn.fs.close(); this.pools.get(id)?.delete(poolId); }, 2000); } } } static getLimit(id) { return this.limits.has(id) ? this.limits.get(id) : 1024; // for vsftpd max_per_ip in /etc/vsftpd.conf } static async create(id, onClose = () => { }) { if (options_1.default.log) { return new log_1.LogFS(id, await this.createFS(id, onClose)); } return this.createFS(id, onClose); } static async createFS(id, onClose) { const { scheme, user, host, port } = (0, URI_1.parseURI)(id); if (!this.credentials.has(id)) { onClose?.(); return; } const [authType, credential] = this.credentials.get(id); const protocol = this.protocols.get(id); if (!protocol) { throw new Error(`Protocol ${protocol} not found`); } switch (protocol) { case 'sftp': { return new SFtp_1.default(host, user, authType == 'password' ? credential : '', authType == 'key' ? credential : null, port, error => log_1.default.error('SFTP error:', error), onClose); } case 'ftp': { return new Ftp_1.default(host, user, credential, port, error => log_1.default.error('FTP error:', error), onClose); } case 's3': { return new S3_1.default(`${scheme}:\\${host}`, user, credential, 'us-east-1', // TODO port, error => log_1.default.error('S3 error:', error), onClose); } default: throw new Error(`Unsupported protocol ${protocol}`); } } } default_1.numOfStartups = 0; default_1.maxStartups = 7; // for SFTP see MaxStartups in /etc/ssh/sshd_config default_1.shared = new ReferenceCountMap_1.default; default_1.pools = new Map(); default_1.queue = []; default_1.pending = {}; default_1.limits = new Map(); default_1.protocols = new Map(); default_1.credentials = new Map(); exports.default = default_1;