"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StatusService = void 0;
const child_process_1 = require("child_process");
const os_1 = require("os");
const path_1 = require("path");
const util_1 = require("util");
const axios_1 = require("@nestjs/axios");
const common_1 = require("@nestjs/common");
const fs_extra_1 = require("fs-extra");
const NodeCache = require("node-cache");
const rxjs_1 = require("rxjs");
const semver_1 = require("semver");
const systeminformation_1 = require("systeminformation");
const config_service_1 = require("../../core/config/config.service");
const homebridge_ipc_service_1 = require("../../core/homebridge-ipc/homebridge-ipc.service");
const logger_service_1 = require("../../core/logger/logger.service");
const plugins_service_1 = require("../plugins/plugins.service");
const server_service_1 = require("../server/server.service");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
let StatusService = class StatusService {
    constructor(httpService, logger, configService, pluginsService, serverService, homebridgeIpcService) {
        this.httpService = httpService;
        this.logger = logger;
        this.configService = configService;
        this.pluginsService = pluginsService;
        this.serverService = serverService;
        this.homebridgeIpcService = homebridgeIpcService;
        this.statusCache = new NodeCache({ stdTTL: 3600 });
        this.homebridgeStatus = "down";
        this.homebridgeStatusChange = new rxjs_1.Subject();
        this.cpuLoadHistory = [];
        this.memoryUsageHistory = [];
        this.rpiGetThrottledMapping = {
            0: 'Under-voltage detected',
            1: 'Arm frequency capped',
            2: 'Currently throttled',
            3: 'Soft temperature limit active',
            16: 'Under-voltage has occurred',
            17: 'Arm frequency capping has occurred',
            18: 'Throttled has occurred',
            19: 'Soft temperature limit has occurred',
        };
        if ((0, os_1.platform)() === 'freebsd') {
            this.getCpuLoadPoint = this.getCpuLoadPointAlt;
            this.getCpuTemp = this.getCpuTempAlt;
        }
        if (this.configService.ui.disableServerMetricsMonitoring !== true) {
            setInterval(async () => {
                this.getCpuLoadPoint();
                this.getMemoryUsagePoint();
            }, 10000);
        }
        else {
            this.logger.warn('Server metrics monitoring disabled.');
        }
        if (this.configService.serviceMode) {
            this.homebridgeIpcService.on('serverStatusUpdate', (data) => {
                this.homebridgeStatus = data.status === "ok" ? "up" : data.status;
                if (data?.setupUri) {
                    this.serverService.setupCode = data.setupUri;
                }
                this.homebridgeStatusChange.next(this.homebridgeStatus);
            });
        }
    }
    async getCpuLoadPoint() {
        const load = (await (0, systeminformation_1.currentLoad)()).currentLoad;
        this.cpuLoadHistory = this.cpuLoadHistory.slice(-60);
        this.cpuLoadHistory.push(load);
    }
    async getMemoryUsagePoint() {
        const memory = await (0, systeminformation_1.mem)();
        this.memoryInfo = memory;
        const memoryFreePercent = ((memory.total - memory.available) / memory.total) * 100;
        this.memoryUsageHistory = this.memoryUsageHistory.slice(-60);
        this.memoryUsageHistory.push(memoryFreePercent);
    }
    async getCpuLoadPointAlt() {
        const load = ((0, os_1.loadavg)()[0] * 100 / (0, os_1.cpus)().length);
        this.cpuLoadHistory = this.cpuLoadHistory.slice(-60);
        this.cpuLoadHistory.push(load);
    }
    async getCpuTemp() {
        const cpuTempData = await (0, systeminformation_1.cpuTemperature)();
        if (cpuTempData.main === -1 && this.configService.ui.temp) {
            return this.getCpuTempLegacy();
        }
        return cpuTempData;
    }
    async getCpuTempLegacy() {
        try {
            const tempData = await (0, fs_extra_1.readFile)(this.configService.ui.temp, 'utf-8');
            const cpuTemp = parseInt(tempData, 10) / 1000;
            return {
                main: cpuTemp,
                cores: [],
                max: cpuTemp,
            };
        }
        catch (e) {
            this.logger.error(`Failed to read temp from ${this.configService.ui.temp} - ${e.message}`);
            return this.getCpuTempAlt();
        }
    }
    async getCpuTempAlt() {
        return {
            main: -1,
            cores: [],
            max: -1,
        };
    }
    async getCurrentNetworkUsage(netInterfaces) {
        if (!netInterfaces || !netInterfaces.length) {
            netInterfaces = [await (0, systeminformation_1.networkInterfaceDefault)()];
        }
        const net = await (0, systeminformation_1.networkStats)(netInterfaces.join(','));
        const txRxSec = (net[0].tx_sec + net[0].rx_sec) / 1024 / 1024;
        return { net: net[0], point: txRxSec };
    }
    async getDashboardLayout() {
        if (!this.dashboardLayout) {
            try {
                const layout = await (0, fs_extra_1.readJson)((0, path_1.resolve)(this.configService.storagePath, '.uix-dashboard.json'));
                this.dashboardLayout = layout;
                return layout;
            }
            catch (e) {
                return [];
            }
        }
        else {
            return this.dashboardLayout;
        }
    }
    async setDashboardLayout(layout) {
        (0, fs_extra_1.writeJsonSync)((0, path_1.resolve)(this.configService.storagePath, '.uix-dashboard.json'), layout);
        this.dashboardLayout = layout;
        return { status: 'ok' };
    }
    async getServerCpuInfo() {
        if (!this.memoryUsageHistory.length) {
            await this.getCpuLoadPoint();
        }
        return {
            cpuTemperature: await this.getCpuTemp(),
            currentLoad: this.cpuLoadHistory.slice(-1)[0],
            cpuLoadHistory: this.cpuLoadHistory,
        };
    }
    async getServerMemoryInfo() {
        if (!this.memoryUsageHistory.length) {
            await this.getMemoryUsagePoint();
        }
        return {
            mem: this.memoryInfo,
            memoryUsageHistory: this.memoryUsageHistory,
        };
    }
    async getServerUptimeInfo() {
        return {
            time: (0, systeminformation_1.time)(),
            processUptime: process.uptime(),
        };
    }
    async getHomebridgePairingPin() {
        return {
            pin: this.configService.homebridgeConfig.bridge.pin,
            setupUri: await this.serverService.getSetupCode(),
        };
    }
    async getHomebridgeStatus() {
        return {
            status: this.homebridgeStatus,
            consolePort: this.configService.ui.port,
            port: this.configService.homebridgeConfig.bridge.port,
            pin: this.configService.homebridgeConfig.bridge.pin,
            setupUri: this.serverService.setupCode,
            packageVersion: this.configService.package.version,
        };
    }
    async watchStats(client) {
        let homebridgeStatusChangeSub;
        let homebridgeStatusInterval;
        client.emit('homebridge-status', await this.getHomebridgeStats());
        if (this.configService.serviceMode) {
            homebridgeStatusChangeSub = this.homebridgeStatusChange.subscribe(async () => {
                client.emit('homebridge-status', await this.getHomebridgeStats());
            });
        }
        else {
            homebridgeStatusInterval = setInterval(async () => {
                client.emit('homebridge-status', await this.getHomebridgeStats());
            }, 10000);
        }
        const onEnd = () => {
            client.removeAllListeners('end');
            client.removeAllListeners('disconnect');
            if (homebridgeStatusInterval) {
                clearInterval(homebridgeStatusInterval);
            }
            if (homebridgeStatusChangeSub) {
                homebridgeStatusChangeSub.unsubscribe();
            }
        };
        client.on('end', onEnd.bind(this));
        client.on('disconnect', onEnd.bind(this));
    }
    async getHomebridgeStats() {
        return {
            consolePort: this.configService.ui.port,
            port: this.configService.homebridgeConfig.bridge.port,
            pin: this.configService.homebridgeConfig.bridge.pin,
            setupUri: await this.serverService.getSetupCode(),
            packageVersion: this.configService.package.version,
            status: await this.checkHomebridgeStatus(),
        };
    }
    async checkHomebridgeStatus() {
        if (this.configService.serviceMode) {
            return this.homebridgeStatus;
        }
        try {
            await this.httpService.get(`http://localhost:${this.configService.homebridgeConfig.bridge.port}`, {
                validateStatus: () => true,
            }).toPromise();
            this.homebridgeStatus = "up";
        }
        catch (e) {
            this.homebridgeStatus = "down";
        }
        return this.homebridgeStatus;
    }
    async getDefaultInterface() {
        const cachedResult = this.statusCache.get('defaultInterface');
        if (cachedResult) {
            return cachedResult;
        }
        const defaultInterfaceName = await (0, systeminformation_1.networkInterfaceDefault)();
        const defaultInterface = defaultInterfaceName ? (await (0, systeminformation_1.networkInterfaces)()).find((x) => x.iface === defaultInterfaceName) : undefined;
        if (defaultInterface) {
            this.statusCache.set('defaultInterface', defaultInterface);
        }
        return defaultInterface;
    }
    async getOsInfo() {
        const cachedResult = this.statusCache.get('osInfo');
        if (cachedResult) {
            return cachedResult;
        }
        const osInformation = await (0, systeminformation_1.osInfo)();
        this.statusCache.set('osInfo', osInformation, 86400);
        return osInformation;
    }
    getGlibcVersion() {
        if ((0, os_1.platform)() !== 'linux') {
            return '';
        }
        const cachedResult = this.statusCache.get('glibcVersion');
        if (cachedResult) {
            return cachedResult;
        }
        try {
            const glibcVersion = (0, child_process_1.execSync)('getconf GNU_LIBC_VERSION 2>/dev/null').toString().split('glibc')[1].trim();
            this.statusCache.set('glibcVersion', glibcVersion, 86400);
            return glibcVersion;
        }
        catch (e) {
            this.logger.debug('Could not check glibc version:', e.message);
            return '';
        }
    }
    async getHomebridgeServerInfo() {
        return {
            serviceUser: (0, os_1.userInfo)().username,
            homebridgeConfigJsonPath: this.configService.configPath,
            homebridgeStoragePath: this.configService.storagePath,
            homebridgeInsecureMode: this.configService.homebridgeInsecureMode,
            homebridgeCustomPluginPath: this.configService.customPluginPath,
            homebridgePluginPath: (0, path_1.resolve)(process.env.UIX_BASE_PATH, '..'),
            homebridgeRunningInDocker: this.configService.runningInDocker,
            homebridgeRunningInSynologyPackage: this.configService.runningInSynologyPackage,
            homebridgeRunningInPackageMode: this.configService.runningInPackageMode,
            homebridgeServiceMode: this.configService.serviceMode,
            nodeVersion: process.version,
            os: await this.getOsInfo(),
            glibcVersion: this.getGlibcVersion(),
            time: (0, systeminformation_1.time)(),
            network: await this.getDefaultInterface() || {},
        };
    }
    async getHomebridgeVersion() {
        return this.pluginsService.getHomebridgePackage();
    }
    async getNodeJsVersionInfo() {
        const cachedResult = this.statusCache.get('nodeJsVersion');
        if (cachedResult) {
            return cachedResult;
        }
        try {
            const versionList = (await this.httpService.get('https://nodejs.org/dist/index.json').toPromise()).data;
            const latest18 = versionList.filter((x) => x.version.startsWith('v18'))[0];
            const latest20 = versionList.filter((x) => x.version.startsWith('v20'))[0];
            let updateAvailable = false;
            let latestVersion = process.version;
            let showNodeUnsupportedWarning = false;
            let showGlibcUnsupportedWarning = false;
            switch (process.version.split('.')[0]) {
                case 'v18': {
                    if ((0, os_1.platform)() === 'linux') {
                        const glibcVersion = this.getGlibcVersion();
                        if (glibcVersion) {
                            if (parseFloat(glibcVersion) >= 2.31) {
                                updateAvailable = true;
                                latestVersion = latest20.version;
                            }
                            else {
                                if ((0, semver_1.gt)(latest18.version, process.version)) {
                                    updateAvailable = true;
                                    latestVersion = latest18.version;
                                }
                                if (parseFloat(glibcVersion) < 2.31) {
                                    showGlibcUnsupportedWarning = true;
                                }
                            }
                        }
                    }
                    else {
                        updateAvailable = true;
                        latestVersion = latest20.version;
                    }
                    break;
                }
                case 'v20': {
                    if ((0, semver_1.gt)(latest20.version, process.version)) {
                        updateAvailable = true;
                        latestVersion = latest20.version;
                    }
                    break;
                }
                default: {
                    showNodeUnsupportedWarning = true;
                }
            }
            const versionInformation = {
                currentVersion: process.version,
                latestVersion,
                updateAvailable,
                showNodeUnsupportedWarning,
                showGlibcUnsupportedWarning,
                installPath: (0, path_1.dirname)(process.execPath),
            };
            this.statusCache.set('nodeJsVersion', versionInformation, 86400);
            return versionInformation;
        }
        catch (e) {
            this.logger.log('Failed to check for Node.js version updates - check your internet connection.');
            const versionInformation = {
                currentVersion: process.version,
                latestVersion: process.version,
                updateAvailable: false,
                showNodeUnsupportedWarning: false,
                showGlibcUnsupportedWarning: false,
            };
            this.statusCache.set('nodeJsVersion', versionInformation, 3600);
            return versionInformation;
        }
    }
    async getRaspberryPiThrottledStatus() {
        if (!this.configService.runningOnRaspberryPi) {
            throw new common_1.BadRequestException('This command is only available on Raspberry Pi');
        }
        const output = {};
        for (const bit of Object.keys(this.rpiGetThrottledMapping)) {
            output[this.rpiGetThrottledMapping[bit]] = false;
        }
        try {
            const { stdout } = await execAsync('vcgencmd get_throttled');
            const throttledHex = parseInt(stdout.trim().replace('throttled=', ''));
            if (!isNaN(throttledHex)) {
                for (const bit of Object.keys(this.rpiGetThrottledMapping)) {
                    output[this.rpiGetThrottledMapping[bit]] = !!((throttledHex >> parseInt(bit, 10)) & 1);
                }
            }
        }
        catch (e) {
            this.logger.debug('Could not check vcgencmd get_throttled:', e.message);
        }
        return output;
    }
};
exports.StatusService = StatusService;
exports.StatusService = StatusService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [axios_1.HttpService,
        logger_service_1.Logger,
        config_service_1.ConfigService,
        plugins_service_1.PluginsService,
        server_service_1.ServerService,
        homebridge_ipc_service_1.HomebridgeIpcService])
], StatusService);
//# sourceMappingURL=status.service.js.map