"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ATTRIBUTES = void 0;
const node_path_1 = require("node:path");
const ssh2_1 = require("ssh2");
const FileSystem_1 = require("../FileSystem");
// https://github.com/mscdex/ssh2/blob/master/SFTP.md
// https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-4.3
// see also https://github.com/theophilusx/ssh2-sftp-client
exports.ATTRIBUTES = [
    {
        name: "name",
        type: FileSystem_1.FileAttributeType.String,
        title: "Name"
    },
    {
        name: "size",
        type: FileSystem_1.FileAttributeType.Number,
        title: "Size"
    },
    {
        name: "modified",
        type: FileSystem_1.FileAttributeType.Date,
        title: "Last Modified"
    },
    {
        name: "rights",
        type: FileSystem_1.FileAttributeType.String,
        title: "Rights"
    }
];
class SFtp extends FileSystem_1.FileSystem {
    constructor(host, user, password, port = 22, onError, onClose = () => { }) {
        super();
        this.host = host;
        this.user = user;
        this.password = password;
        this.port = port;
        this.onError = onError;
        this.onClose = onClose;
        this.connection = new ssh2_1.Client();
    }
    async open() {
        if (this.connected === undefined) {
            let connecting = true;
            this.connected = new Promise((resolve, reject) => {
                this.connection
                    .on('close', () => {
                    if (this.connected) {
                        if (connecting) {
                            reject(new Error('Remote side unexpectedly closed network connection'));
                        }
                        else {
                            this.onClose();
                        }
                    }
                    this.connected = undefined;
                    connecting = false;
                })
                    .on('ready', () => {
                    connecting = false;
                    this.connection.sftp((e, sftp) => {
                        if (e) {
                            reject(new Error(this.decodeError(e)));
                            return;
                        }
                        this.connection.on('error', this.onError);
                        this.extensions = sftp._extensions;
                        resolve(sftp);
                    });
                })
                    .on('error', err => reject(err));
            });
            this.connection.connect({
                host: this.host,
                username: this.user,
                password: this.password,
                port: this.port,
                // debug: s => console.log('DEBUG', s)
            });
        }
        return this.connected;
    }
    close() {
        this.connection.end();
    }
    opened() {
        return this.connected != undefined;
    }
    async pwd() {
        const sftp = await this.open();
        return new Promise((resolve, reject) => sftp.realpath('.', (e, current) => e ?
            reject(new Error(this.decodeError(e))) :
            resolve(current)));
    }
    async ls(dir) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.readdir(dir, async (e, list) => {
                if (e) {
                    reject(new Error(`LS: Can't get contents of ${dir} ` + this.decodeError(e)));
                    return;
                }
                const files = [];
                for (let f of list) {
                    if (f.attrs.isSymbolicLink()) {
                        let target = await this.readlink((0, node_path_1.join)(dir, f.filename));
                        if (!(0, node_path_1.isAbsolute)(target)) {
                            target = (0, node_path_1.normalize)((0, node_path_1.join)(dir, target));
                        }
                        const targetStat = await this.stat(target);
                        files.push({
                            path: (0, node_path_1.join)(dir, f.filename),
                            name: f.filename,
                            dir: f.attrs.isDirectory(),
                            size: targetStat.size,
                            modified: new Date(targetStat.mtime * 1000),
                            owner: targetStat.uid,
                            group: targetStat.gid,
                            rights: targetStat.mode,
                            target
                        });
                    }
                    else {
                        files.push({
                            path: (0, node_path_1.join)(dir, f.filename),
                            name: f.filename,
                            dir: f.attrs.isDirectory(),
                            size: f.attrs.size,
                            modified: new Date(f.attrs.mtime * 1000),
                            owner: f.attrs.uid,
                            group: f.attrs.gid,
                            rights: f.attrs.mode
                        });
                    }
                }
                resolve(files);
            });
        });
    }
    async readlink(path) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.readlink(path, (e, target) => e ? reject(new Error(this.decodeError(e))) : resolve(target));
        });
    }
    async stat(path) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.stat(path, (e, stats) => e ? reject(new Error(this.decodeError(e))) : resolve(stats));
        });
    }
    async get(fromRemote, toLocal) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.fastGet(fromRemote, toLocal, (e) => e ? reject(new Error(this.decodeError(e))) : resolve());
        });
    }
    async put(fromLocal, toRemote) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.fastPut(fromLocal, toRemote, e => e ? reject(new Error(this.decodeError(e) + ` ${toRemote}`)) : resolve());
        });
    }
    async rm(path, recursive) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            if (recursive) {
                sftp.rmdir(path, e => e ? reject(new Error(this.decodeError(e))) : resolve());
            }
            else {
                sftp.unlink(path, e => e ? reject(new Error(this.decodeError(e))) : resolve());
            }
        });
    }
    async mkdir(path) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => sftp.mkdir(path, e => e ?
            reject(new Error(`MKDIR: Can't create directory ${path} ` + this.decodeError(e))) :
            resolve()));
    }
    async rename(from, to) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.rename(from, to, e => {
                if (e) {
                    reject(new Error(this.decodeError(e)));
                    return;
                }
                resolve();
            });
        });
    }
    async mv(from, to) {
        await this.exec(`rm -Rf '${to}'`);
        await this.exec(`mkdir -p '${(0, node_path_1.dirname)(to)}'`);
        return this.exec(`mv -f '${from}' '${to}'`);
    }
    async cp(from, to, recursive) {
        await this.exec(`rm -Rf '${to}'`);
        if (recursive) {
            return this.exec(`cp -fR '${from}' '${to}'`);
        }
        await this.exec(`mkdir -p '${(0, node_path_1.dirname)(to)}'`);
        return this.exec(`cp -f '${from}' '${to}'`);
    }
    async write(path, s) {
        const sftp = await this.open();
        return new Promise((resolve, reject) => {
            sftp.open(path, 'w', (e, h) => {
                if (e) {
                    reject(e);
                    return;
                }
                const data = Buffer.from(s);
                sftp.write(h, data, 0, data.length, 0, (e) => e ? reject(new Error(this.decodeError(e))) : resolve());
            });
        });
    }
    async exec(cmd) {
        await this.open();
        return new Promise((resolve, reject) => this.connection.exec(cmd, (e, stream) => {
            if (e) {
                reject(new Error(cmd + ': ' + this.decodeError(e)));
                return;
            }
            stream.on('exit', (code) => {
                if (code) {
                    reject(new Error(`${cmd}: the process's return code is ${code}`));
                }
                resolve();
            }).on('data', (data) => {
                console.log(`${cmd}: ${data}`);
            }).stderr.on('data', data => {
                reject(new Error(`${cmd}: ${data}`));
            });
        }));
    }
    decodeError(e) {
        const STATUS_CODE = {
            OK: 0,
            EOF: 1,
            NO_SUCH_FILE: 2,
            PERMISSION_DENIED: 3,
            FAILURE: 4,
            BAD_MESSAGE: 5,
            NO_CONNECTION: 6,
            CONNECTION_LOST: 7,
            OP_UNSUPPORTED: 8
        };
        const STATUS_CODE_STR = {
            [STATUS_CODE.OK]: 'No error',
            [STATUS_CODE.EOF]: 'End of file',
            [STATUS_CODE.NO_SUCH_FILE]: 'No such file or directory',
            [STATUS_CODE.PERMISSION_DENIED]: 'Permission denied',
            [STATUS_CODE.FAILURE]: 'Failure',
            [STATUS_CODE.BAD_MESSAGE]: 'Bad message',
            [STATUS_CODE.NO_CONNECTION]: 'No connection',
            [STATUS_CODE.CONNECTION_LOST]: 'Connection lost',
            [STATUS_CODE.OP_UNSUPPORTED]: 'Operation unsupported',
        };
        let msg = e.message || (('code' in e) ?
            (e['code'] in STATUS_CODE_STR ? STATUS_CODE_STR[e['code']] : `Code: ${e['code']}`) :
            'Unknown error');
        return msg;
    }
}
exports.default = SFtp;
