filefive
Version:
SFTP/FTP/Amazon S3 client and dual-panel file manager for macOS and Linux
222 lines (221 loc) • 8.63 kB
JavaScript
"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;