"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VideoViewerCounters = void 0;
const tslib_1 = require("tslib");
const core_utils_1 = require("../../../helpers/core-utils");
const logger_1 = require("../../../helpers/logger");
const constants_1 = require("../../../initializers/constants");
const send_view_1 = require("../../activitypub/send/send-view");
const peertube_socket_1 = require("../../peertube-socket");
const application_1 = require("../../../models/application/application");
const video_1 = require("../../../models/video/video");
const extra_utils_1 = require("../../../../shared/extra-utils");
const lTags = (0, logger_1.loggerTagsFactory)('views');
class VideoViewerCounters {
    constructor() {
        this.viewersPerVideo = new Map();
        this.idToViewer = new Map();
        this.salt = (0, extra_utils_1.buildUUID)();
        this.processingViewerCounters = false;
        setInterval(() => this.cleanViewerCounters(), constants_1.VIEW_LIFETIME.VIEWER_COUNTER);
    }
    addLocalViewer(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, ip } = options;
            logger_1.logger.debug('Adding local viewer to video viewers counter %s.', video.uuid, Object.assign({}, lTags(video.uuid)));
            const viewerId = this.generateViewerId(ip, video.uuid);
            const viewer = this.idToViewer.get(viewerId);
            if (viewer) {
                viewer.expires = this.buildViewerExpireTime();
                yield this.federateViewerIfNeeded(video, viewer);
                return false;
            }
            const newViewer = yield this.addViewerToVideo({ viewerId, video, viewerScope: 'local' });
            yield this.federateViewerIfNeeded(video, newViewer);
            return true;
        });
    }
    addRemoteViewer(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, viewerExpires, viewerId } = options;
            logger_1.logger.debug('Adding remote viewer to video %s.', video.uuid, Object.assign({}, lTags(video.uuid)));
            yield this.addViewerToVideo({ video, viewerExpires, viewerId, viewerScope: 'remote' });
            return true;
        });
    }
    getTotalViewers(options) {
        let total = 0;
        for (const viewers of this.viewersPerVideo.values()) {
            total += viewers.filter(v => v.viewerScope === options.viewerScope && v.videoScope === options.videoScope).length;
        }
        return total;
    }
    getViewers(video) {
        const viewers = this.viewersPerVideo.get(video.id);
        if (!viewers)
            return 0;
        return viewers.length;
    }
    buildViewerExpireTime() {
        return new Date().getTime() + constants_1.VIEW_LIFETIME.VIEWER_COUNTER;
    }
    addViewerToVideo(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, viewerExpires, viewerId, viewerScope } = options;
            let watchers = this.viewersPerVideo.get(video.id);
            if (!watchers) {
                watchers = [];
                this.viewersPerVideo.set(video.id, watchers);
            }
            const expires = viewerExpires
                ? viewerExpires.getTime()
                : this.buildViewerExpireTime();
            const videoScope = video.remote
                ? 'remote'
                : 'local';
            const viewer = { id: viewerId, expires, videoScope, viewerScope };
            watchers.push(viewer);
            this.idToViewer.set(viewerId, viewer);
            yield this.notifyClients(video.id, watchers.length);
            return viewer;
        });
    }
    cleanViewerCounters() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.processingViewerCounters)
                return;
            this.processingViewerCounters = true;
            if (!(0, core_utils_1.isTestOrDevInstance)())
                logger_1.logger.info('Cleaning video viewers.', lTags());
            try {
                for (const videoId of this.viewersPerVideo.keys()) {
                    const notBefore = new Date().getTime();
                    const viewers = this.viewersPerVideo.get(videoId);
                    const newViewers = [];
                    for (const viewer of viewers) {
                        if (viewer.expires > notBefore) {
                            newViewers.push(viewer);
                        }
                        else {
                            this.idToViewer.delete(viewer.id);
                        }
                    }
                    if (newViewers.length === 0)
                        this.viewersPerVideo.delete(videoId);
                    else
                        this.viewersPerVideo.set(videoId, newViewers);
                    yield this.notifyClients(videoId, newViewers.length);
                }
            }
            catch (err) {
                logger_1.logger.error('Error in video clean viewers scheduler.', Object.assign({ err }, lTags()));
            }
            this.processingViewerCounters = false;
        });
    }
    notifyClients(videoId, viewersLength) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const video = yield video_1.VideoModel.loadImmutableAttributes(videoId);
            if (!video)
                return;
            peertube_socket_1.PeerTubeSocket.Instance.sendVideoViewsUpdate(video, viewersLength);
            logger_1.logger.debug('Video viewers update for %s is %d.', video.url, viewersLength, lTags());
        });
    }
    generateViewerId(ip, videoUUID) {
        return (0, extra_utils_1.sha256)(this.salt + '-' + ip + '-' + videoUUID);
    }
    federateViewerIfNeeded(video, viewer) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const now = new Date().getTime();
            const federationLimit = now - (constants_1.VIEW_LIFETIME.VIEWER_COUNTER * 0.75);
            if (viewer.lastFederation && viewer.lastFederation > federationLimit)
                return;
            yield (0, send_view_1.sendView)({ byActor: yield (0, application_1.getServerActor)(), video, type: 'viewer', viewerIdentifier: viewer.id });
            viewer.lastFederation = now;
        });
    }
}
exports.VideoViewerCounters = VideoViewerCounters;
//# sourceMappingURL=video-viewer-counters.js.map