UNPKG

filefive

Version:

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

235 lines (234 loc) 8.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.queues = exports.lsRemote = void 0; const node_path_1 = require("node:path"); const types_1 = require("../types"); const rxjs_1 = require("rxjs"); const ramda_1 = require("ramda"); const Connection_1 = __importDefault(require("../Connection")); const Local_1 = require("../Local"); const filter_1 = require("../utils/filter"); const Local_2 = __importDefault(require("../transformers/Local")); const URI_1 = require("../utils/URI"); const lsRemote = (connId) => { const cache = new Map(); return async (path) => { if (cache.has(path)) { return cache.get(path); } const list = new Promise(async (resove) => { const [conn, close] = await Connection_1.default.transmit(connId); conn.ls(path) .then(resove) .catch(() => resove(null)) .finally(() => close()); }); cache.set(path, list); return list; }; }; exports.lsRemote = lsRemote; class TransmitQueue { constructor(from, to, src, dest, filter, fromRoot, onState, onConflict, onComplete) { this.from = from; this.to = to; this.src = src; this.dest = dest; this.filter = filter; this.fromRoot = fromRoot; this.onState = onState; this.onConflict = onConflict; this.onComplete = onComplete; this.queue$ = new rxjs_1.Subject(); this.queue = []; this.pending = []; this.stopped = false; this.totalCnt = 0; this.doneCnt = 0; this.totalSize = 0; this.doneSize = 0; this.transmits = 0; this.closed = false; } async create() { await this.enqueue(this.src, this.dest); if (this.stopped) { return; } const stat = this.stat(this.to); this.processing = this.queue$.subscribe(async ({ from, dirs, to, action }) => { const a = action ?? this.action; const existing = await stat((0, node_path_1.join)(to, ...dirs, from.name)); if (existing) { if (a) { if (a.type == types_1.QueueActionType.Skip) { this.sendState(from.size); return this.next(); } } else { this.putOnHold(from, dirs, to, existing); return this.next(); } } const [fs, close] = await Connection_1.default.transmit(this.from != types_1.LocalFileSystemID ? this.from : this.to); this.transmits++; (new Promise((resolve) => resolve(existing ? this.applyAction(a, from, dirs, to, existing, this.transmit.bind(this, fs)) : this.transmit(fs, from, dirs, to)))).then(() => { close(); if (--this.transmits == 0 && this.closed) { this.close(); } }); this.next(); }); this.next(); } async enqueue(paths, dest) { const ls = this.ls(this.from); let matchFilter = (file) => true; if (this.filter) { const re = (0, filter_1.filterRegExp)(this.filter); matchFilter = (file) => { if (this.filter.ignored === true && 'git_i' in file.attributes) { return false; } if (this.filter.uncommited === true && !('git_u' in file.attributes || 'git_m' in file.attributes || 'git_a' in file.attributes || 'git_c' in file.attributes)) { return false; } if (!re) { return true; } const found = re.exec(file.name); return this.filter?.invert ?? false ? found === null : found !== null; }; } paths = paths.map(node_path_1.normalize).filter(path => !paths.find(ancestor => path.startsWith(ancestor + node_path_1.sep))); const add = async (path, to, dirs = []) => { if (this.stopped) { return; } const parent = (0, node_path_1.dirname)(path); const from = (await ls(parent))?.find((0, ramda_1.whereEq)({ path })); if (from) { if (from.dir) { return Promise.all((await ls(from.path))?.map(child => add(child.path, to, [...dirs, (0, node_path_1.basename)(path)]))); } else { if (matchFilter(from)) { this.queue.push({ from, to, dirs }); this.totalCnt++; this.totalSize += from.size; } } } }; await Promise.all(paths.map(path => add(path, dest, this.fromRoot ? (0, node_path_1.dirname)(path).substring(this.fromRoot.length + 1).split('/') : []))); } stop() { if (!this.stopped) { this.stopped = true; this.pending = []; this.close(); } } resolve(action, forAll = false) { forAll && (this.action = action); const drained = !this.queue.length; this.queue.push(...this.pending .splice(0, forAll ? this.pending.length : 1) .map(f => ({ from: f.src, dirs: f.dirs, to: f.to, action }))); drained && this.queue$.next(this.queue.shift()); if (this.pending.length) { const { src, dest } = this.pending[0]; this.onConflict(src, dest); } } next() { if (this.queue.length) { this.queue$.next(this.queue.shift()); } else if (!this.pending.length) { this.close(); } } async close() { if (this.processing?.closed === false) { this.processing?.unsubscribe(); } this.closed = true; if (this.transmits == 0) { this.finalize(); this.onComplete(this.stopped); } } sendState(size) { this.doneCnt++; this.doneSize += size; this.onState({ totalCnt: this.totalCnt, doneCnt: this.doneCnt, totalSize: this.totalSize, doneSize: this.doneSize, pending: this.pending.length }); } async applyAction(action, from, dirs, to, existing, transmit) { switch (action.type) { case types_1.QueueActionType.Replace: { await transmit(from, dirs, to); break; } case types_1.QueueActionType.Rename: { from.name = await this.rename(from.name, (0, node_path_1.join)(to, ...dirs)); await transmit(from, dirs, to); break; } } } putOnHold(src, dirs, to, dest) { this.pending.push({ src, dirs, to, dest }); if (this.pending.length == 1) { this.onConflict(src, dest); } } ls(connId) { const transformer = new Local_2.default(); return connId == types_1.LocalFileSystemID ? async (dir) => { return transformer.transform(dir, (await Connection_1.default.get(connId).ls(dir)) .map(f => ({ ...f, URI: (0, URI_1.createURI)(types_1.LocalFileSystemID, f.path) }))); } : (0, exports.lsRemote)(connId); } stat(connId) { if (connId == types_1.LocalFileSystemID) { return (path) => Promise.resolve((0, Local_1.stat)(path)); } const ls = this.ls(connId); return async (path) => (await ls((0, node_path_1.dirname)(path)))?.find((0, ramda_1.whereEq)({ path })); } async rename(name, dir) { let { name: oldName, ext } = (0, node_path_1.parse)(name); const parts = oldName.match(/^(.+) copy ?(\d*)$/); oldName = (parts && parts.length > 2) ? parts[1] : oldName; let counter = (parts && parts.length > 2) ? (parts[2] ? parseInt(parts[2]) + 1 : 1) : 1; let newName = `${oldName} copy${counter > 1 ? ' ' + counter : ''}`; const files = await this.ls(this.to)(dir); while (files?.find((0, ramda_1.whereEq)({ name: newName + ext }))) { newName = `${oldName} copy ${++counter}`; } return newName + ext; } async finalize() { } } exports.default = TransmitQueue; exports.queues = new Map();