filefive
Version:
SFTP/FTP/Amazon S3 client and dual-panel file manager for macOS and Linux
235 lines (234 loc) • 8.8 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 = 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();