"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.serializeConnection = serializeConnection;
exports.inferOperation = inferOperation;
const lodash_1 = __importDefault(require("lodash"));
const connections_1 = require("./connections");
const misc_1 = require("./misc");
const throttler_1 = require("./throttler");
const auth_1 = require("./auth");
const SendList_1 = require("./SendList");
const persistence_1 = require("./persistence");
const cross_1 = require("./cross");
exports.default = {
    async disconnect({ ip, port, allButLocalhost }) {
        (0, misc_1.apiAssertTypes)({
            string_undefined: { ip },
            number_undefined: { port },
            boolean_undefined: { allButLocalhost },
        });
        const match = allButLocalhost ? ((x) => !(0, misc_1.isLocalHost)(x.ip))
            : lodash_1.default.matches({ ip, port });
        const found = (0, connections_1.getConnections)().filter(c => match(getConnAddress(c)));
        for (const c of found)
            (0, connections_1.disconnect)(c.socket, "manual disconnection");
        return { result: found.length };
    },
    get_connections({}, ctx) {
        const list = new SendList_1.SendListReadable({
            diff: true,
            addAtStart: (0, connections_1.getConnections)().map(serializeConnection),
        });
        list.props({ you: ctx.ip });
        return list.events(ctx, {
            connection(conn) {
                list.add(serializeConnection(conn));
            },
            connectionClosed(conn) {
                list.remove(getConnAddress(conn));
            },
            connectionNewIp(conn, oldIp, newIp) {
                list.update(getConnAddress(conn, oldIp), { ip: newIp });
            },
            connectionUpdated(conn, change) {
                if (conn.socket.closed || lodash_1.default.isEmpty(change))
                    return;
                if (change.ctx) {
                    Object.assign(change, fromCtx(change.ctx));
                    change.ctx = undefined;
                }
                list.update(getConnAddress(conn), change);
            },
        });
    },
    async *get_connection_stats() {
        while (1) {
            const connections = (0, connections_1.getConnections)();
            yield {
                outSpeedKb: throttler_1.totalOutSpeedKb,
                inSpeedKb: throttler_1.totalInSpeedKb,
                sent_got: [throttler_1.totalSent.get(), throttler_1.totalGot.get(), totalGotSentResetTime.get()],
                connections: connections.length,
                ips: (0, cross_1.countUniqueBy)(connections, conn => conn.ip),
            };
            await (0, misc_1.wait)(1000);
        }
    },
    async clear_persistent({ k }) {
        (0, misc_1.apiAssertTypes)({ string_array: { k } });
        totalGotSentResetTime.set(new Date);
        for (const x of (0, misc_1.wantArray)(k))
            void persistence_1.storedMap.del(x);
    },
};
function serializeConnection(conn) {
    const { socket, started, secure } = conn;
    return {
        ...getConnAddress(conn),
        v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
        // connection fields are request-scoped once transfer tracking starts; socket counters cover earlier snapshots
        got: conn.got || socket.bytesRead,
        sent: conn.sent || socket.bytesWritten,
        outSpeedKb: conn.outSpeedKb,
        inSpeedKb: conn.inSpeedKb,
        country: conn.country,
        started,
        secure: (secure || undefined), // undefined will save some space once json-ed
        ...fromCtx(conn.ctx),
    };
}
function fromCtx(ctx) {
    if (!ctx)
        return;
    return {
        user: (0, auth_1.getCurrentUsername)(ctx),
        agent: (0, misc_1.shortenAgent)(ctx.get('user-agent')),
        ...inferOperation(ctx)
    };
}
function inferOperation(ctx) {
    const s = ctx.state; // short alias
    return {
        archive: s.archive,
        ...s.browsing ? { op: 'browsing', path: (0, misc_1.safeDecodeURIComponent)(s.browsing) }
            : s.uploadPath ? { op: 'upload', path: (0, misc_1.safeDecodeURIComponent)(s.uploadPath) }
                : {
                    op: !s.considerAsGui && (ctx.state.archive || ctx.state.vfsNode) ? 'download' : undefined,
                    path: (0, misc_1.safeDecodeURIComponent)(ctx.originalUrl),
                },
        opProgress: lodash_1.default.isNumber(s.opProgress) ? lodash_1.default.round(s.opProgress, 3) : undefined,
        opTotal: s.opTotal,
        opOffset: s.opOffset,
    };
}
function getConnAddress(conn, overrideIp) {
    return {
        ip: overrideIp ?? conn.ip,
        port: conn.socket.remotePort,
    };
}
const totalGotSentResetTime = persistence_1.storedMap.singleSync('totalGotSentResetTime', new Date(0));
totalGotSentResetTime.ready().then(() => // because default value is not stored, and we need to init this value
 totalGotSentResetTime.set(was => was.getTime() ? was : new Date));
