]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/video-views.ts
Better 413 error handling in cli script
[github/Chocobozzz/PeerTube.git] / server / lib / video-views.ts
CommitLineData
3455c265 1import { isTestInstance } from '@server/helpers/core-utils'
51353d9a
C
2import { logger, loggerTagsFactory } from '@server/helpers/logger'
3import { VIEW_LIFETIME } from '@server/initializers/constants'
4import { VideoModel } from '@server/models/video/video'
5import { MVideo } from '@server/types/models'
6import { PeerTubeSocket } from './peertube-socket'
7import { Redis } from './redis'
8
9const lTags = loggerTagsFactory('views')
10
11export class VideoViews {
12
13 // Values are Date().getTime()
14 private readonly viewersPerVideo = new Map<number, number[]>()
15
16 private static instance: VideoViews
17
18 private constructor () {
19 }
20
21 init () {
22 setInterval(() => this.cleanViewers(), VIEW_LIFETIME.VIEWER)
23 }
24
25 async processView (options: {
26 video: MVideo
27 ip: string | null
28 viewerExpires?: Date
29 }) {
30 const { video, ip, viewerExpires } = options
31
32 logger.debug('Processing view for %s and ip %s.', video.url, ip, lTags())
33
34 let success = await this.addView(video, ip)
35
36 if (video.isLive) {
37 const successViewer = await this.addViewer(video, ip, viewerExpires)
38 success ||= successViewer
39 }
40
41 return success
42 }
43
44 getViewers (video: MVideo) {
45 const viewers = this.viewersPerVideo.get(video.id)
46 if (!viewers) return 0
47
48 return viewers.length
49 }
50
51 buildViewerExpireTime () {
52 return new Date().getTime() + VIEW_LIFETIME.VIEWER
53 }
54
55 private async addView (video: MVideo, ip: string | null) {
56 const promises: Promise<any>[] = []
57
58 if (ip !== null) {
59 const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid)
60 if (viewExists) return false
61
62 promises.push(Redis.Instance.setIPVideoView(ip, video.uuid))
63 }
64
65 if (video.isOwned()) {
66 promises.push(Redis.Instance.addLocalVideoView(video.id))
67 }
68
69 promises.push(Redis.Instance.addVideoViewStats(video.id))
70
71 await Promise.all(promises)
72
73 return true
74 }
75
76 private async addViewer (video: MVideo, ip: string | null, viewerExpires?: Date) {
77 if (ip !== null) {
78 const viewExists = await Redis.Instance.doesVideoIPViewerExist(ip, video.uuid)
79 if (viewExists) return false
80
81 await Redis.Instance.setIPVideoViewer(ip, video.uuid)
82 }
83
84 let watchers = this.viewersPerVideo.get(video.id)
85
86 if (!watchers) {
87 watchers = []
88 this.viewersPerVideo.set(video.id, watchers)
89 }
90
91 const expiration = viewerExpires
92 ? viewerExpires.getTime()
93 : this.buildViewerExpireTime()
94
95 watchers.push(expiration)
96 await this.notifyClients(video.id, watchers.length)
97
98 return true
99 }
100
101 private async cleanViewers () {
3455c265 102 if (!isTestInstance()) logger.info('Cleaning video viewers.', lTags())
51353d9a
C
103
104 for (const videoId of this.viewersPerVideo.keys()) {
105 const notBefore = new Date().getTime()
106
107 const viewers = this.viewersPerVideo.get(videoId)
108
109 // Only keep not expired viewers
110 const newViewers = viewers.filter(w => w > notBefore)
111
112 if (newViewers.length === 0) this.viewersPerVideo.delete(videoId)
113 else this.viewersPerVideo.set(videoId, newViewers)
114
115 await this.notifyClients(videoId, newViewers.length)
116 }
117 }
118
119 private async notifyClients (videoId: string | number, viewersLength: number) {
120 const video = await VideoModel.loadImmutableAttributes(videoId)
121 if (!video) return
122
123 PeerTubeSocket.Instance.sendVideoViewsUpdate(video, viewersLength)
124
125 logger.debug('Live video views update for %s is %d.', video.url, viewersLength, lTags())
126 }
127
128 static get Instance () {
129 return this.instance || (this.instance = new this())
130 }
131}