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