diff options
Diffstat (limited to 'server/models/redundancy/video-redundancy.ts')
-rw-r--r-- | server/models/redundancy/video-redundancy.ts | 189 |
1 files changed, 141 insertions, 48 deletions
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 48ec77206..fb07287a8 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -14,11 +14,10 @@ import { | |||
14 | UpdatedAt | 14 | UpdatedAt |
15 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
16 | import { ActorModel } from '../activitypub/actor' | 16 | import { ActorModel } from '../activitypub/actor' |
17 | import { throwIfNotValid } from '../utils' | 17 | import { getVideoSort, throwIfNotValid } from '../utils' |
18 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 18 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
19 | import { CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers' | 19 | import { CONFIG, CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers' |
20 | import { VideoFileModel } from '../video/video-file' | 20 | import { VideoFileModel } from '../video/video-file' |
21 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
22 | import { getServerActor } from '../../helpers/utils' | 21 | import { getServerActor } from '../../helpers/utils' |
23 | import { VideoModel } from '../video/video' | 22 | import { VideoModel } from '../video/video' |
24 | import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' | 23 | import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' |
@@ -28,6 +27,7 @@ import { VideoChannelModel } from '../video/video-channel' | |||
28 | import { ServerModel } from '../server/server' | 27 | import { ServerModel } from '../server/server' |
29 | import { sample } from 'lodash' | 28 | import { sample } from 'lodash' |
30 | import { isTestInstance } from '../../helpers/core-utils' | 29 | import { isTestInstance } from '../../helpers/core-utils' |
30 | import * as Bluebird from 'bluebird' | ||
31 | 31 | ||
32 | export enum ScopeNames { | 32 | export enum ScopeNames { |
33 | WITH_VIDEO = 'WITH_VIDEO' | 33 | WITH_VIDEO = 'WITH_VIDEO' |
@@ -145,65 +145,90 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
145 | return VideoRedundancyModel.findOne(query) | 145 | return VideoRedundancyModel.findOne(query) |
146 | } | 146 | } |
147 | 147 | ||
148 | static async getVideoSample (p: Bluebird<VideoModel[]>) { | ||
149 | const rows = await p | ||
150 | const ids = rows.map(r => r.id) | ||
151 | const id = sample(ids) | ||
152 | |||
153 | return VideoModel.loadWithFile(id, undefined, !isTestInstance()) | ||
154 | } | ||
155 | |||
148 | static async findMostViewToDuplicate (randomizedFactor: number) { | 156 | static async findMostViewToDuplicate (randomizedFactor: number) { |
149 | // On VideoModel! | 157 | // On VideoModel! |
150 | const query = { | 158 | const query = { |
159 | attributes: [ 'id', 'views' ], | ||
151 | logging: !isTestInstance(), | 160 | logging: !isTestInstance(), |
152 | limit: randomizedFactor, | 161 | limit: randomizedFactor, |
153 | order: [ [ 'views', 'DESC' ] ], | 162 | order: getVideoSort('-views'), |
154 | include: [ | 163 | include: [ |
155 | { | 164 | await VideoRedundancyModel.buildVideoFileForDuplication(), |
156 | model: VideoFileModel.unscoped(), | 165 | VideoRedundancyModel.buildServerRedundancyInclude() |
157 | required: true, | ||
158 | where: { | ||
159 | id: { | ||
160 | [ Sequelize.Op.notIn ]: await VideoRedundancyModel.buildExcludeIn() | ||
161 | } | ||
162 | } | ||
163 | }, | ||
164 | { | ||
165 | attributes: [], | ||
166 | model: VideoChannelModel.unscoped(), | ||
167 | required: true, | ||
168 | include: [ | ||
169 | { | ||
170 | attributes: [], | ||
171 | model: ActorModel.unscoped(), | ||
172 | required: true, | ||
173 | include: [ | ||
174 | { | ||
175 | attributes: [], | ||
176 | model: ServerModel.unscoped(), | ||
177 | required: true, | ||
178 | where: { | ||
179 | redundancyAllowed: true | ||
180 | } | ||
181 | } | ||
182 | ] | ||
183 | } | ||
184 | ] | ||
185 | } | ||
186 | ] | 166 | ] |
187 | } | 167 | } |
188 | 168 | ||
189 | const rows = await VideoModel.unscoped().findAll(query) | 169 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) |
170 | } | ||
190 | 171 | ||
191 | return sample(rows) | 172 | static async findTrendingToDuplicate (randomizedFactor: number) { |
173 | // On VideoModel! | ||
174 | const query = { | ||
175 | attributes: [ 'id', 'views' ], | ||
176 | subQuery: false, | ||
177 | logging: !isTestInstance(), | ||
178 | group: 'VideoModel.id', | ||
179 | limit: randomizedFactor, | ||
180 | order: getVideoSort('-trending'), | ||
181 | include: [ | ||
182 | await VideoRedundancyModel.buildVideoFileForDuplication(), | ||
183 | VideoRedundancyModel.buildServerRedundancyInclude(), | ||
184 | |||
185 | VideoModel.buildTrendingQuery(CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS) | ||
186 | ] | ||
187 | } | ||
188 | |||
189 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) | ||
192 | } | 190 | } |
193 | 191 | ||
194 | static async getVideoFiles (strategy: VideoRedundancyStrategy) { | 192 | static async findRecentlyAddedToDuplicate (randomizedFactor: number, minViews: number) { |
193 | // On VideoModel! | ||
194 | const query = { | ||
195 | attributes: [ 'id', 'publishedAt' ], | ||
196 | logging: !isTestInstance(), | ||
197 | limit: randomizedFactor, | ||
198 | order: getVideoSort('-publishedAt'), | ||
199 | where: { | ||
200 | views: { | ||
201 | [ Sequelize.Op.gte ]: minViews | ||
202 | } | ||
203 | }, | ||
204 | include: [ | ||
205 | await VideoRedundancyModel.buildVideoFileForDuplication(), | ||
206 | VideoRedundancyModel.buildServerRedundancyInclude() | ||
207 | ] | ||
208 | } | ||
209 | |||
210 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) | ||
211 | } | ||
212 | |||
213 | static async getTotalDuplicated (strategy: VideoRedundancyStrategy) { | ||
195 | const actor = await getServerActor() | 214 | const actor = await getServerActor() |
196 | 215 | ||
197 | const queryVideoFiles = { | 216 | const options = { |
198 | logging: !isTestInstance(), | 217 | logging: !isTestInstance(), |
199 | where: { | 218 | include: [ |
200 | actorId: actor.id, | 219 | { |
201 | strategy | 220 | attributes: [], |
202 | } | 221 | model: VideoRedundancyModel, |
222 | required: true, | ||
223 | where: { | ||
224 | actorId: actor.id, | ||
225 | strategy | ||
226 | } | ||
227 | } | ||
228 | ] | ||
203 | } | 229 | } |
204 | 230 | ||
205 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO) | 231 | return VideoFileModel.sum('size', options) |
206 | .findAll(queryVideoFiles) | ||
207 | } | 232 | } |
208 | 233 | ||
209 | static listAllExpired () { | 234 | static listAllExpired () { |
@@ -211,7 +236,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
211 | logging: !isTestInstance(), | 236 | logging: !isTestInstance(), |
212 | where: { | 237 | where: { |
213 | expiresOn: { | 238 | expiresOn: { |
214 | [Sequelize.Op.lt]: new Date() | 239 | [ Sequelize.Op.lt ]: new Date() |
215 | } | 240 | } |
216 | } | 241 | } |
217 | } | 242 | } |
@@ -220,6 +245,37 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
220 | .findAll(query) | 245 | .findAll(query) |
221 | } | 246 | } |
222 | 247 | ||
248 | static async getStats (strategy: VideoRedundancyStrategy) { | ||
249 | const actor = await getServerActor() | ||
250 | |||
251 | const query = { | ||
252 | raw: true, | ||
253 | attributes: [ | ||
254 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ], | ||
255 | [ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', 'videoId')), 'totalVideos' ], | ||
256 | [ Sequelize.fn('COUNT', 'videoFileId'), 'totalVideoFiles' ] | ||
257 | ], | ||
258 | where: { | ||
259 | strategy, | ||
260 | actorId: actor.id | ||
261 | }, | ||
262 | include: [ | ||
263 | { | ||
264 | attributes: [], | ||
265 | model: VideoFileModel, | ||
266 | required: true | ||
267 | } | ||
268 | ] | ||
269 | } | ||
270 | |||
271 | return VideoRedundancyModel.find(query as any) // FIXME: typings | ||
272 | .then((r: any) => ({ | ||
273 | totalUsed: parseInt(r.totalUsed.toString(), 10), | ||
274 | totalVideos: r.totalVideos, | ||
275 | totalVideoFiles: r.totalVideoFiles | ||
276 | })) | ||
277 | } | ||
278 | |||
223 | toActivityPubObject (): CacheFileObject { | 279 | toActivityPubObject (): CacheFileObject { |
224 | return { | 280 | return { |
225 | id: this.url, | 281 | id: this.url, |
@@ -237,13 +293,50 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
237 | } | 293 | } |
238 | } | 294 | } |
239 | 295 | ||
240 | private static async buildExcludeIn () { | 296 | // Don't include video files we already duplicated |
297 | private static async buildVideoFileForDuplication () { | ||
241 | const actor = await getServerActor() | 298 | const actor = await getServerActor() |
242 | 299 | ||
243 | return Sequelize.literal( | 300 | const notIn = Sequelize.literal( |
244 | '(' + | 301 | '(' + |
245 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "expiresOn" >= NOW()` + | 302 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "expiresOn" >= NOW()` + |
246 | ')' | 303 | ')' |
247 | ) | 304 | ) |
305 | |||
306 | return { | ||
307 | attributes: [], | ||
308 | model: VideoFileModel.unscoped(), | ||
309 | required: true, | ||
310 | where: { | ||
311 | id: { | ||
312 | [ Sequelize.Op.notIn ]: notIn | ||
313 | } | ||
314 | } | ||
315 | } | ||
316 | } | ||
317 | |||
318 | private static buildServerRedundancyInclude () { | ||
319 | return { | ||
320 | attributes: [], | ||
321 | model: VideoChannelModel.unscoped(), | ||
322 | required: true, | ||
323 | include: [ | ||
324 | { | ||
325 | attributes: [], | ||
326 | model: ActorModel.unscoped(), | ||
327 | required: true, | ||
328 | include: [ | ||
329 | { | ||
330 | attributes: [], | ||
331 | model: ServerModel.unscoped(), | ||
332 | required: true, | ||
333 | where: { | ||
334 | redundancyAllowed: true | ||
335 | } | ||
336 | } | ||
337 | ] | ||
338 | } | ||
339 | ] | ||
340 | } | ||
248 | } | 341 | } |
249 | } | 342 | } |