diff options
author | Chocobozzz <me@florianbigard.com> | 2018-09-14 09:57:21 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-09-14 09:57:21 +0200 |
commit | b36f41ca09e92ecb30d367d91d1089a23d10d585 (patch) | |
tree | 34b7c90e17f73f37d069a2f08d60dc36fa08372f /server/models | |
parent | 6f0c46be8c9f4690d5e5cb758c4df6164b006f83 (diff) | |
download | PeerTube-b36f41ca09e92ecb30d367d91d1089a23d10d585.tar.gz PeerTube-b36f41ca09e92ecb30d367d91d1089a23d10d585.tar.zst PeerTube-b36f41ca09e92ecb30d367d91d1089a23d10d585.zip |
Add trending videos strategy
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/redundancy/video-redundancy.ts | 115 | ||||
-rw-r--r-- | server/models/video/video.ts | 32 |
2 files changed, 96 insertions, 51 deletions
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 48ec77206..b13ade0f4 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' |
@@ -145,50 +144,51 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
145 | return VideoRedundancyModel.findOne(query) | 144 | return VideoRedundancyModel.findOne(query) |
146 | } | 145 | } |
147 | 146 | ||
147 | static getVideoSample (rows: { id: number }[]) { | ||
148 | const ids = rows.map(r => r.id) | ||
149 | const id = sample(ids) | ||
150 | |||
151 | return VideoModel.loadWithFile(id, undefined, !isTestInstance()) | ||
152 | } | ||
153 | |||
148 | static async findMostViewToDuplicate (randomizedFactor: number) { | 154 | static async findMostViewToDuplicate (randomizedFactor: number) { |
149 | // On VideoModel! | 155 | // On VideoModel! |
150 | const query = { | 156 | const query = { |
157 | attributes: [ 'id', 'views' ], | ||
151 | logging: !isTestInstance(), | 158 | logging: !isTestInstance(), |
152 | limit: randomizedFactor, | 159 | limit: randomizedFactor, |
153 | order: [ [ 'views', 'DESC' ] ], | 160 | order: getVideoSort('-views'), |
154 | include: [ | 161 | include: [ |
155 | { | 162 | await VideoRedundancyModel.buildVideoFileForDuplication(), |
156 | model: VideoFileModel.unscoped(), | 163 | VideoRedundancyModel.buildServerRedundancyInclude() |
157 | required: true, | 164 | ] |
158 | where: { | 165 | } |
159 | id: { | 166 | |
160 | [ Sequelize.Op.notIn ]: await VideoRedundancyModel.buildExcludeIn() | 167 | const rows = await VideoModel.unscoped().findAll(query) |
161 | } | 168 | |
162 | } | 169 | return VideoRedundancyModel.getVideoSample(rows as { id: number }[]) |
163 | }, | 170 | } |
164 | { | 171 | |
165 | attributes: [], | 172 | static async findTrendingToDuplicate (randomizedFactor: number) { |
166 | model: VideoChannelModel.unscoped(), | 173 | // On VideoModel! |
167 | required: true, | 174 | const query = { |
168 | include: [ | 175 | attributes: [ 'id', 'views' ], |
169 | { | 176 | subQuery: false, |
170 | attributes: [], | 177 | logging: !isTestInstance(), |
171 | model: ActorModel.unscoped(), | 178 | group: 'VideoModel.id', |
172 | required: true, | 179 | limit: randomizedFactor, |
173 | include: [ | 180 | order: getVideoSort('-trending'), |
174 | { | 181 | include: [ |
175 | attributes: [], | 182 | await VideoRedundancyModel.buildVideoFileForDuplication(), |
176 | model: ServerModel.unscoped(), | 183 | VideoRedundancyModel.buildServerRedundancyInclude(), |
177 | required: true, | 184 | |
178 | where: { | 185 | VideoModel.buildTrendingQuery(CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS) |
179 | redundancyAllowed: true | ||
180 | } | ||
181 | } | ||
182 | ] | ||
183 | } | ||
184 | ] | ||
185 | } | ||
186 | ] | 186 | ] |
187 | } | 187 | } |
188 | 188 | ||
189 | const rows = await VideoModel.unscoped().findAll(query) | 189 | const rows = await VideoModel.unscoped().findAll(query) |
190 | 190 | ||
191 | return sample(rows) | 191 | return VideoRedundancyModel.getVideoSample(rows as { id: number }[]) |
192 | } | 192 | } |
193 | 193 | ||
194 | static async getVideoFiles (strategy: VideoRedundancyStrategy) { | 194 | static async getVideoFiles (strategy: VideoRedundancyStrategy) { |
@@ -211,7 +211,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
211 | logging: !isTestInstance(), | 211 | logging: !isTestInstance(), |
212 | where: { | 212 | where: { |
213 | expiresOn: { | 213 | expiresOn: { |
214 | [Sequelize.Op.lt]: new Date() | 214 | [ Sequelize.Op.lt ]: new Date() |
215 | } | 215 | } |
216 | } | 216 | } |
217 | } | 217 | } |
@@ -237,13 +237,50 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
237 | } | 237 | } |
238 | } | 238 | } |
239 | 239 | ||
240 | private static async buildExcludeIn () { | 240 | // Don't include video files we already duplicated |
241 | private static async buildVideoFileForDuplication () { | ||
241 | const actor = await getServerActor() | 242 | const actor = await getServerActor() |
242 | 243 | ||
243 | return Sequelize.literal( | 244 | const notIn = Sequelize.literal( |
244 | '(' + | 245 | '(' + |
245 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "expiresOn" >= NOW()` + | 246 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "expiresOn" >= NOW()` + |
246 | ')' | 247 | ')' |
247 | ) | 248 | ) |
249 | |||
250 | return { | ||
251 | attributes: [], | ||
252 | model: VideoFileModel.unscoped(), | ||
253 | required: true, | ||
254 | where: { | ||
255 | id: { | ||
256 | [ Sequelize.Op.notIn ]: notIn | ||
257 | } | ||
258 | } | ||
259 | } | ||
260 | } | ||
261 | |||
262 | private static buildServerRedundancyInclude () { | ||
263 | return { | ||
264 | attributes: [], | ||
265 | model: VideoChannelModel.unscoped(), | ||
266 | required: true, | ||
267 | include: [ | ||
268 | { | ||
269 | attributes: [], | ||
270 | model: ActorModel.unscoped(), | ||
271 | required: true, | ||
272 | include: [ | ||
273 | { | ||
274 | attributes: [], | ||
275 | model: ServerModel.unscoped(), | ||
276 | required: true, | ||
277 | where: { | ||
278 | redundancyAllowed: true | ||
279 | } | ||
280 | } | ||
281 | ] | ||
282 | } | ||
283 | ] | ||
284 | } | ||
248 | } | 285 | } |
249 | } | 286 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 27c631dcd..ef8be7c86 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -387,16 +387,7 @@ type AvailableForListIDsOptions = { | |||
387 | } | 387 | } |
388 | 388 | ||
389 | if (options.trendingDays) { | 389 | if (options.trendingDays) { |
390 | query.include.push({ | 390 | query.include.push(VideoModel.buildTrendingQuery(options.trendingDays)) |
391 | attributes: [], | ||
392 | model: VideoViewModel, | ||
393 | required: false, | ||
394 | where: { | ||
395 | startDate: { | ||
396 | [ Sequelize.Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) | ||
397 | } | ||
398 | } | ||
399 | }) | ||
400 | 391 | ||
401 | query.subQuery = false | 392 | query.subQuery = false |
402 | } | 393 | } |
@@ -1071,9 +1062,12 @@ export class VideoModel extends Model<VideoModel> { | |||
1071 | } | 1062 | } |
1072 | 1063 | ||
1073 | static load (id: number, t?: Sequelize.Transaction) { | 1064 | static load (id: number, t?: Sequelize.Transaction) { |
1074 | const options = t ? { transaction: t } : undefined | 1065 | return VideoModel.findById(id, { transaction: t }) |
1066 | } | ||
1075 | 1067 | ||
1076 | return VideoModel.findById(id, options) | 1068 | static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) { |
1069 | return VideoModel.scope(ScopeNames.WITH_FILES) | ||
1070 | .findById(id, { transaction: t, logging }) | ||
1077 | } | 1071 | } |
1078 | 1072 | ||
1079 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { | 1073 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { |
@@ -1191,6 +1185,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1191 | .then(rows => rows.map(r => r[ field ])) | 1185 | .then(rows => rows.map(r => r[ field ])) |
1192 | } | 1186 | } |
1193 | 1187 | ||
1188 | static buildTrendingQuery (trendingDays: number) { | ||
1189 | return { | ||
1190 | attributes: [], | ||
1191 | subQuery: false, | ||
1192 | model: VideoViewModel, | ||
1193 | required: false, | ||
1194 | where: { | ||
1195 | startDate: { | ||
1196 | [ Sequelize.Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) | ||
1197 | } | ||
1198 | } | ||
1199 | } | ||
1200 | } | ||
1201 | |||
1194 | private static buildActorWhereWithFilter (filter?: VideoFilter) { | 1202 | private static buildActorWhereWithFilter (filter?: VideoFilter) { |
1195 | if (filter && filter === 'local') { | 1203 | if (filter && filter === 'local') { |
1196 | return { | 1204 | return { |