"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.totalInSpeedKb = exports.totalOutSpeedKb = exports.totalGot = exports.totalSent = exports.throttler = void 0;
exports.roundSpeed = roundSpeed;
const stream_1 = require("stream");
const ThrottledStream_1 = require("./ThrottledStream");
const config_1 = require("./config");
const misc_1 = require("./misc");
const connections_1 = require("./connections");
const lodash_1 = __importDefault(require("lodash"));
const events_1 = __importDefault(require("./events"));
const persistence_1 = require("./persistence");
const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
(0, config_1.defineConfig)('max_kbps', Infinity).sub(v => mainThrottleGroup.updateLimit(v));
const ip2group = {};
const SymThrStr = Symbol('stream');
const SymTimeout = Symbol('timeout');
const maxKbpsPerIp = (0, config_1.defineConfig)('max_kbps_per_ip', Infinity);
maxKbpsPerIp.sub(v => {
    for (const [ip, { group }] of Object.entries(ip2group))
        if (ip) // empty-string = unlimited group
            group.updateLimit(v);
});
const throttler = async (ctx, next) => {
    await next();
    let { body } = ctx;
    const downloadTotal = ctx.response.length;
    if (typeof body === 'string' || body && body instanceof Buffer)
        ctx.body = body = stream_1.Readable.from(body);
    if (!body || !(body instanceof stream_1.Readable))
        return;
    // we wrap the stream also for unlimited connections to get speed and other features
    const noLimit = ctx.state.account?.ignore_limits || (0, misc_1.isLocalHost)(ctx);
    const ipGroup = ip2group[noLimit ? '' : ctx.ip] ||= {
        count: 0,
        group: new ThrottledStream_1.ThrottleGroup(noLimit ? Infinity : maxKbpsPerIp.get(), noLimit ? undefined : mainThrottleGroup),
    };
    const conn = (0, connections_1.getConnection)(ctx);
    if (!conn)
        throw 'assert throttler connection';
    const ts = conn[SymThrStr] = new ThrottledStream_1.ThrottledStream(ipGroup.group, conn[SymThrStr]);
    const offset = ts.getBytesSent();
    let closed = false;
    const DELAY = 1000;
    const update = lodash_1.default.debounce(() => {
        const ts = conn[SymThrStr];
        const outSpeed = roundSpeed(ts.getSpeed());
        const { state } = ctx;
        (0, connections_1.updateConnection)(conn, { outSpeedKb: outSpeed, sent: conn.socket.bytesWritten }, { opProgress: state.opTotal && ((state.opOffset || 0) + (ts.getBytesSent() - offset) / state.opTotal) });
        /* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
        * so who will take care to updateConnection? This artificial next-call will ensure just that */
        clearTimeout(conn[SymTimeout]);
        if (outSpeed || !closed)
            conn[SymTimeout] = setTimeout(update, DELAY);
    }, DELAY, { leading: true, maxWait: DELAY });
    await exports.totalSent.ready();
    ts.on('sent', n => {
        exports.totalSent.set(x => x + n);
        update();
    });
    ++ipGroup.count;
    ts.on('close', () => {
        update.flush();
        closed = true;
        if (--ipGroup.count)
            return; // any left?
        delete ip2group[ctx.ip];
    });
    ctx.state.originalStream = body;
    ctx.body = body.pipe(ts);
    if (downloadTotal !== undefined) // undefined will break SSE
        ctx.response.length = downloadTotal; // preserve this info
    ts.once('close', () => // in case of compressed response, we offer calculation of real size
     ctx.state.length = ts.getBytesSent() - offset);
};
exports.throttler = throttler;
function roundSpeed(n) {
    return lodash_1.default.round(n, 1) || lodash_1.default.round(n, 3); // further precision if necessary
}
exports.totalSent = persistence_1.storedMap.singleSync('totalSent', 0);
exports.totalGot = persistence_1.storedMap.singleSync('totalGot', 0);
exports.totalOutSpeedKb = 0;
exports.totalInSpeedKb = 0;
let lastSent;
let lastGot;
let last = Date.now();
setInterval(() => {
    const now = Date.now();
    const past = now - last;
    last = now;
    {
        const v = exports.totalSent.get();
        exports.totalOutSpeedKb = roundSpeed((v - (lastSent ?? v)) / past); // lastSent is bytes, past is milliseconds, so the result is KB/s
        lastSent = v;
    }
    {
        const v = exports.totalGot.get();
        exports.totalInSpeedKb = roundSpeed((v - (lastGot ?? v)) / past);
        lastGot = v;
    }
}, 1000);
events_1.default.on('connection', (c) => {
    const count = (data) => exports.totalGot.set(x => x + data.length);
    c.socket.on('data', count);
    c.socket.on('secure', s => s.on('data', count)); // secure sockets won't forward 'data' events to the plain ones
});
