UNPKG

filefive

Version:

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

237 lines (236 loc) 8.97 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 = void 0; const node_path_1 = require("node:path"); const types_1 = require("./types"); const Local_1 = require("./Local"); const URI_1 = require("./utils/URI"); const ramda_1 = require("ramda"); const rxjs_1 = require("rxjs"); const Connection_1 = __importDefault(require("./Connection")); const log_1 = __importDefault(require("./log")); class Queue { constructor(type, connId, src, dest, watcher, onState, onConflict, onError, onComplete) { this.type = type; this.connId = connId; this.watcher = watcher; this.onState = onState; this.onConflict = onConflict; this.onError = onError; this.onComplete = onComplete; this.queue = []; this.queue$ = new rxjs_1.Subject(); this.totalCnt = 0; this.doneCnt = 0; this.totalSize = 0; this.doneSize = 0; this.pending = []; this.touched = new Set(); if (type == types_1.QueueType.Download) { this.download(src, (0, URI_1.parseURI)(dest)['path']); } else if (type == types_1.QueueType.Upload) { this.upload(src.map(uri => (0, URI_1.parseURI)(uri)['path']), dest); } else { this.remove(src.map(uri => (0, URI_1.parseURI)(uri)['path'])); } } 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, to: f.dest.path, action }))); drained && this.queue$.next(this.queue.shift()); if (this.pending.length) { const { src, dest } = this.pending[0]; this.onConflict(src, dest); } } close() { if (this.processing?.closed === false) { this.processing?.unsubscribe(); this.onComplete(); if (this.type == types_1.QueueType.Upload || this.type == types_1.QueueType.Remove) { this.touched.forEach(path => this.watcher.refresh(this.connId + path)); } } } async download(src, dest) { await this.enqueue(src.map((0, ramda_1.pipe)(URI_1.parseURI, (0, ramda_1.prop)('path'))), dest, (0, ramda_1.memoizeWith)(ramda_1.identity, (path) => Connection_1.default.list(this.connId, path))); const transmit = async (fs, from, to) => { await (0, Local_1.touch)(to); log_1.default.log(`start downloading ${from.path} -> ${to}`); try { await fs.get(from.path, to); } catch (error) { this.onError(error); } log_1.default.log(`end downloading ${from.path}`); this.sendState(from.size); }; this.processing = this.queue$.subscribe(async ({ from, to, action }) => { let a = action ?? this.action; const existing = (0, Local_1.stat)(to); if (existing) { if (a) { if (a.type == types_1.QueueActionType.Skip) { this.sendState(from.size); return this.next(); } } else { this.putOnHold(from, existing); return this.next(); } } const [fs, close] = await Connection_1.default.transmit(this.connId); existing ? this.applyAction(a, from, existing, transmit.bind(this, fs)).then(close) : transmit(fs, from, to).then(close); this.next(); }); this.next(); } async upload(src, dest) { const conn = Connection_1.default.get(this.connId); const to = (0, URI_1.parseURI)(dest)['path']; await this.enqueue(src, to, path => Promise.resolve((0, Local_1.list)(path))); this.touched.add(to); const transmit = async (fs, from, to) => { if (!((0, Local_1.stat)(from.path))) { return; } log_1.default.log(`start uploading ${from.path} -> ${to}`); try { const dir = (0, node_path_1.dirname)(to); if (!this.touched.has(dir)) { await fs.mkdir(dir); } await fs.put(from.path, to); this.touched.add(dir); } catch (error) { this.onError(error); } log_1.default.log(`end uploading ${from.path}`); this.sendState(from.size); }; const ls = (0, ramda_1.memoizeWith)(ramda_1.identity, async (path) => { try { return await conn.ls(path); } catch (e) { } return []; }); const exists = async (path) => (await ls((0, node_path_1.dirname)(path))).find((0, ramda_1.whereEq)({ path })); this.processing = this.queue$.subscribe(async ({ from, to, action }) => { let a = action ?? this.action; const existing = await exists(to); if (existing) { if (a) { if (a.type == types_1.QueueActionType.Skip) { this.sendState(from.size); return this.next(); } } else { this.putOnHold(from, existing); return this.next(); } } const [fs, close] = await Connection_1.default.transmit(this.connId); existing ? this.applyAction(a, from, existing, transmit.bind(this, fs)).then(close) : transmit(fs, from, to).then(close); this.next(); }); this.next(); } async remove(paths) { this.totalCnt = paths.length; const touched = new Map(); paths.forEach(async (path) => { const dir = (0, node_path_1.dirname)(path); const [conn, close] = await Connection_1.default.transmit(this.connId); if (!touched.has(dir)) { try { touched.set(dir, await conn.ls(dir)); } catch (e) { touched.set(dir, null); } } const name = (0, node_path_1.basename)(path); const file = touched.get(dir)?.find(f => f.name == name); if (file) { try { await conn.rm(path, file.dir); } catch (error) { this.onError({ type: types_1.FailureType.RemoteError, id: this.connId, error }); } } close(); this.sendState(0); if (this.doneCnt == this.totalCnt) { this.onComplete(); touched.forEach((files, dir) => { this.watcher.refresh((0, URI_1.createURI)(this.connId, dir)); }); } }); } next() { if (this.queue.length) { this.queue$.next(this.queue.shift()); } else if (!this.pending.length) { this.close(); } } async enqueue(paths, dest, ls) { const add = async (path, to) => { const from = (await ls((0, node_path_1.dirname)(path))).find((0, ramda_1.whereEq)({ path })); from && (from.dir ? (await ls(path)).forEach(async (f) => await add(f.path, (0, node_path_1.join)(to, (0, node_path_1.basename)(path)))) : this.queue.push({ from, to: (0, node_path_1.join)(to, (0, node_path_1.basename)(path)) })); }; for (const path of paths) { await add(path, dest); } this.queue.forEach(({ from: { size } }) => { this.totalCnt++; this.totalSize += size; }); } applyAction(action, from, to, transmit) { switch (action.type) { case types_1.QueueActionType.Replace: return transmit(from, to.path); } } putOnHold(src, dest) { this.pending.push({ src, dest }); if (this.pending.length == 1) { this.onConflict(src, dest); } } 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 }); } } exports.default = Queue; exports.queues = new Map();