diff options
author | Chocobozzz <me@florianbigard.com> | 2018-06-15 16:52:15 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-06-15 18:20:56 +0200 |
commit | bbe0f0645ca958d33a3f409b15166609733b663f (patch) | |
tree | edcd5d702c73cda74a2177c4bdc08c616334337d /server | |
parent | 2baea0c77cc765f7cbca9c9a2f4272268892a35c (diff) | |
download | PeerTube-bbe0f0645ca958d33a3f409b15166609733b663f.tar.gz PeerTube-bbe0f0645ca958d33a3f409b15166609733b663f.tar.zst PeerTube-bbe0f0645ca958d33a3f409b15166609733b663f.zip |
Add ability to schedule video publication
Diffstat (limited to 'server')
-rw-r--r-- | server/initializers/constants.ts | 6 | ||||
-rw-r--r-- | server/lib/schedulers/update-videos-scheduler.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/videos.ts | 2 | ||||
-rw-r--r-- | server/models/video/schedule-video-update.ts | 23 | ||||
-rw-r--r-- | server/models/video/video.ts | 33 | ||||
-rw-r--r-- | server/tests/api/check-params/videos.ts | 29 | ||||
-rw-r--r-- | server/tests/api/videos/video-schedule-update.ts | 26 |
7 files changed, 97 insertions, 26 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 164378505..53902071c 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -8,8 +8,6 @@ import { VideoPrivacy } from '../../shared/models/videos' | |||
8 | import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' | 8 | import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' |
9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
10 | import { invert } from 'lodash' | 10 | import { invert } from 'lodash' |
11 | import { RemoveOldJobsScheduler } from '../lib/schedulers/remove-old-jobs-scheduler' | ||
12 | import { UpdateVideosScheduler } from '../lib/schedulers/update-videos-scheduler' | ||
13 | 11 | ||
14 | // Use a variable to reload the configuration if we need | 12 | // Use a variable to reload the configuration if we need |
15 | let config: IConfig = require('config') | 13 | let config: IConfig = require('config') |
@@ -98,8 +96,8 @@ const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days | |||
98 | // 1 hour | 96 | // 1 hour |
99 | let SCHEDULER_INTERVALS_MS = { | 97 | let SCHEDULER_INTERVALS_MS = { |
100 | badActorFollow: 60000 * 60, // 1 hour | 98 | badActorFollow: 60000 * 60, // 1 hour |
101 | removeOldJobs: 60000 * 60, // 1 jour | 99 | removeOldJobs: 60000 * 60, // 1 hour |
102 | updateVideos: 60000 * 1, // 1 minute | 100 | updateVideos: 60000 // 1 minute |
103 | } | 101 | } |
104 | 102 | ||
105 | // --------------------------------------------------------------------------- | 103 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index d123c3ceb..a964648fd 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts | |||
@@ -33,7 +33,9 @@ export class UpdateVideosScheduler extends AbstractScheduler { | |||
33 | } | 33 | } |
34 | } | 34 | } |
35 | 35 | ||
36 | private updateVideos () { | 36 | private async updateVideos () { |
37 | if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined | ||
38 | |||
37 | return sequelizeTypescript.transaction(async t => { | 39 | return sequelizeTypescript.transaction(async t => { |
38 | const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t) | 40 | const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t) |
39 | 41 | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 9fe5a253b..da17b4a68 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -223,7 +223,7 @@ const videosUpdateValidator = [ | |||
223 | 223 | ||
224 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { | 224 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { |
225 | return res.status(409) | 225 | return res.status(409) |
226 | .json({ error: 'Cannot set "private" a video that was not private anymore.' }) | 226 | .json({ error: 'Cannot set "private" a video that was not private.' }) |
227 | .end() | 227 | .end() |
228 | } | 228 | } |
229 | 229 | ||
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index d4e37beb5..3cf5f6c99 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts | |||
@@ -25,7 +25,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
25 | @AllowNull(true) | 25 | @AllowNull(true) |
26 | @Default(null) | 26 | @Default(null) |
27 | @Column | 27 | @Column |
28 | privacy: VideoPrivacy | 28 | privacy: VideoPrivacy.PUBLIC | VideoPrivacy.UNLISTED |
29 | 29 | ||
30 | @CreatedAt | 30 | @CreatedAt |
31 | createdAt: Date | 31 | createdAt: Date |
@@ -45,6 +45,21 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
45 | }) | 45 | }) |
46 | Video: VideoModel | 46 | Video: VideoModel |
47 | 47 | ||
48 | static areVideosToUpdate () { | ||
49 | const query = { | ||
50 | logging: false, | ||
51 | attributes: [ 'id' ], | ||
52 | where: { | ||
53 | updateAt: { | ||
54 | [Sequelize.Op.lte]: new Date() | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | return ScheduleVideoUpdateModel.findOne(query) | ||
60 | .then(res => !!res) | ||
61 | } | ||
62 | |||
48 | static listVideosToUpdate (t: Transaction) { | 63 | static listVideosToUpdate (t: Transaction) { |
49 | const query = { | 64 | const query = { |
50 | where: { | 65 | where: { |
@@ -68,4 +83,10 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
68 | return ScheduleVideoUpdateModel.findAll(query) | 83 | return ScheduleVideoUpdateModel.findAll(query) |
69 | } | 84 | } |
70 | 85 | ||
86 | toFormattedJSON () { | ||
87 | return { | ||
88 | updateAt: this.updateAt, | ||
89 | privacy: this.privacy || undefined | ||
90 | } | ||
91 | } | ||
71 | } | 92 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 440f4d171..0041e4d38 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -97,7 +97,8 @@ export enum ScopeNames { | |||
97 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 97 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
98 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', | 98 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', |
99 | WITH_TAGS = 'WITH_TAGS', | 99 | WITH_TAGS = 'WITH_TAGS', |
100 | WITH_FILES = 'WITH_FILES' | 100 | WITH_FILES = 'WITH_FILES', |
101 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE' | ||
101 | } | 102 | } |
102 | 103 | ||
103 | @Scopes({ | 104 | @Scopes({ |
@@ -286,6 +287,14 @@ export enum ScopeNames { | |||
286 | required: true | 287 | required: true |
287 | } | 288 | } |
288 | ] | 289 | ] |
290 | }, | ||
291 | [ScopeNames.WITH_SCHEDULED_UPDATE]: { | ||
292 | include: [ | ||
293 | { | ||
294 | model: () => ScheduleVideoUpdateModel.unscoped(), | ||
295 | required: false | ||
296 | } | ||
297 | ] | ||
289 | } | 298 | } |
290 | }) | 299 | }) |
291 | @Table({ | 300 | @Table({ |
@@ -843,7 +852,7 @@ export class VideoModel extends Model<VideoModel> { | |||
843 | } | 852 | } |
844 | 853 | ||
845 | return VideoModel | 854 | return VideoModel |
846 | .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS ]) | 855 | .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ]) |
847 | .findById(id, options) | 856 | .findById(id, options) |
848 | } | 857 | } |
849 | 858 | ||
@@ -869,7 +878,7 @@ export class VideoModel extends Model<VideoModel> { | |||
869 | } | 878 | } |
870 | 879 | ||
871 | return VideoModel | 880 | return VideoModel |
872 | .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS ]) | 881 | .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ]) |
873 | .findOne(options) | 882 | .findOne(options) |
874 | } | 883 | } |
875 | 884 | ||
@@ -1022,9 +1031,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1022 | 1031 | ||
1023 | toFormattedJSON (options?: { | 1032 | toFormattedJSON (options?: { |
1024 | additionalAttributes: { | 1033 | additionalAttributes: { |
1025 | state: boolean, | 1034 | state?: boolean, |
1026 | waitTranscoding: boolean, | 1035 | waitTranscoding?: boolean, |
1027 | scheduledUpdate: boolean | 1036 | scheduledUpdate?: boolean |
1028 | } | 1037 | } |
1029 | }): Video { | 1038 | }): Video { |
1030 | const formattedAccount = this.VideoChannel.Account.toFormattedJSON() | 1039 | const formattedAccount = this.VideoChannel.Account.toFormattedJSON() |
@@ -1084,18 +1093,18 @@ export class VideoModel extends Model<VideoModel> { | |||
1084 | } | 1093 | } |
1085 | 1094 | ||
1086 | if (options) { | 1095 | if (options) { |
1087 | if (options.additionalAttributes.state) { | 1096 | if (options.additionalAttributes.state === true) { |
1088 | videoObject.state = { | 1097 | videoObject.state = { |
1089 | id: this.state, | 1098 | id: this.state, |
1090 | label: VideoModel.getStateLabel(this.state) | 1099 | label: VideoModel.getStateLabel(this.state) |
1091 | } | 1100 | } |
1092 | } | 1101 | } |
1093 | 1102 | ||
1094 | if (options.additionalAttributes.waitTranscoding) { | 1103 | if (options.additionalAttributes.waitTranscoding === true) { |
1095 | videoObject.waitTranscoding = this.waitTranscoding | 1104 | videoObject.waitTranscoding = this.waitTranscoding |
1096 | } | 1105 | } |
1097 | 1106 | ||
1098 | if (options.additionalAttributes.scheduledUpdate && this.ScheduleVideoUpdate) { | 1107 | if (options.additionalAttributes.scheduledUpdate === true && this.ScheduleVideoUpdate) { |
1099 | videoObject.scheduledUpdate = { | 1108 | videoObject.scheduledUpdate = { |
1100 | updateAt: this.ScheduleVideoUpdate.updateAt, | 1109 | updateAt: this.ScheduleVideoUpdate.updateAt, |
1101 | privacy: this.ScheduleVideoUpdate.privacy || undefined | 1110 | privacy: this.ScheduleVideoUpdate.privacy || undefined |
@@ -1107,7 +1116,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1107 | } | 1116 | } |
1108 | 1117 | ||
1109 | toFormattedDetailsJSON (): VideoDetails { | 1118 | toFormattedDetailsJSON (): VideoDetails { |
1110 | const formattedJson = this.toFormattedJSON() | 1119 | const formattedJson = this.toFormattedJSON({ |
1120 | additionalAttributes: { | ||
1121 | scheduledUpdate: true | ||
1122 | } | ||
1123 | }) | ||
1111 | 1124 | ||
1112 | const detailsJson = { | 1125 | const detailsJson = { |
1113 | support: this.support, | 1126 | support: this.support, |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 04bed3b44..abbea6ba3 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -291,6 +291,23 @@ describe('Test videos API validator', function () { | |||
291 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 291 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
292 | }) | 292 | }) |
293 | 293 | ||
294 | it('Should fail with a bad schedule update (miss updateAt)', async function () { | ||
295 | const fields = immutableAssign(baseCorrectParams, { 'scheduleUpdate[privacy]': VideoPrivacy.PUBLIC }) | ||
296 | const attaches = baseCorrectAttaches | ||
297 | |||
298 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
299 | }) | ||
300 | |||
301 | it('Should fail with a bad schedule update (wrong updateAt)', async function () { | ||
302 | const fields = immutableAssign(baseCorrectParams, { | ||
303 | 'scheduleUpdate[privacy]': VideoPrivacy.PUBLIC, | ||
304 | 'scheduleUpdate[updateAt]': 'toto' | ||
305 | }) | ||
306 | const attaches = baseCorrectAttaches | ||
307 | |||
308 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
309 | }) | ||
310 | |||
294 | it('Should fail without an input file', async function () { | 311 | it('Should fail without an input file', async function () { |
295 | const fields = baseCorrectParams | 312 | const fields = baseCorrectParams |
296 | const attaches = {} | 313 | const attaches = {} |
@@ -494,6 +511,18 @@ describe('Test videos API validator', function () { | |||
494 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) | 511 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) |
495 | }) | 512 | }) |
496 | 513 | ||
514 | it('Should fail with a bad schedule update (miss updateAt)', async function () { | ||
515 | const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } }) | ||
516 | |||
517 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) | ||
518 | }) | ||
519 | |||
520 | it('Should fail with a bad schedule update (wrong updateAt)', async function () { | ||
521 | const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { updateAt: 'toto', privacy: VideoPrivacy.PUBLIC } }) | ||
522 | |||
523 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) | ||
524 | }) | ||
525 | |||
497 | it('Should fail with an incorrect thumbnail file', async function () { | 526 | it('Should fail with an incorrect thumbnail file', async function () { |
498 | const fields = baseCorrectParams | 527 | const fields = baseCorrectParams |
499 | const attaches = { | 528 | const attaches = { |
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts index 8b87ea855..a260fa4da 100644 --- a/server/tests/api/videos/video-schedule-update.ts +++ b/server/tests/api/videos/video-schedule-update.ts | |||
@@ -5,11 +5,14 @@ import 'mocha' | |||
5 | import { VideoPrivacy } from '../../../../shared/models/videos' | 5 | import { VideoPrivacy } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | doubleFollow, | 7 | doubleFollow, |
8 | flushAndRunMultipleServers, getMyVideos, | 8 | flushAndRunMultipleServers, |
9 | getMyVideos, | ||
9 | getVideosList, | 10 | getVideosList, |
11 | getVideoWithToken, | ||
10 | killallServers, | 12 | killallServers, |
11 | ServerInfo, | 13 | ServerInfo, |
12 | setAccessTokensToServers, updateVideo, | 14 | setAccessTokensToServers, |
15 | updateVideo, | ||
13 | uploadVideo, | 16 | uploadVideo, |
14 | wait | 17 | wait |
15 | } from '../../utils' | 18 | } from '../../utils' |
@@ -69,17 +72,22 @@ describe('Test video update scheduler', function () { | |||
69 | const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) | 72 | const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) |
70 | expect(res.body.total).to.equal(1) | 73 | expect(res.body.total).to.equal(1) |
71 | 74 | ||
72 | const video = res.body.data[0] | 75 | const videoFromList = res.body.data[0] |
73 | expect(video.name).to.equal('video 1') | 76 | const res2 = await getVideoWithToken(servers[0].url, servers[0].accessToken, videoFromList.uuid) |
74 | expect(video.privacy.id).to.equal(VideoPrivacy.PRIVATE) | 77 | const videoFromGet = res2.body |
75 | expect(new Date(video.scheduledUpdate.updateAt)).to.be.above(new Date()) | 78 | |
76 | expect(video.scheduledUpdate.privacy).to.equal(VideoPrivacy.PUBLIC) | 79 | for (const video of [ videoFromList, videoFromGet ]) { |
80 | expect(video.name).to.equal('video 1') | ||
81 | expect(video.privacy.id).to.equal(VideoPrivacy.PRIVATE) | ||
82 | expect(new Date(video.scheduledUpdate.updateAt)).to.be.above(new Date()) | ||
83 | expect(video.scheduledUpdate.privacy).to.equal(VideoPrivacy.PUBLIC) | ||
84 | } | ||
77 | }) | 85 | }) |
78 | 86 | ||
79 | it('Should wait some seconds and have the video in public privacy', async function () { | 87 | it('Should wait some seconds and have the video in public privacy', async function () { |
80 | this.timeout(20000) | 88 | this.timeout(20000) |
81 | 89 | ||
82 | await wait(10000) | 90 | await wait(15000) |
83 | await waitJobs(servers) | 91 | await waitJobs(servers) |
84 | 92 | ||
85 | for (const server of servers) { | 93 | for (const server of servers) { |
@@ -144,7 +152,7 @@ describe('Test video update scheduler', function () { | |||
144 | it('Should wait some seconds and have the updated video in public privacy', async function () { | 152 | it('Should wait some seconds and have the updated video in public privacy', async function () { |
145 | this.timeout(20000) | 153 | this.timeout(20000) |
146 | 154 | ||
147 | await wait(10000) | 155 | await wait(15000) |
148 | await waitJobs(servers) | 156 | await waitJobs(servers) |
149 | 157 | ||
150 | for (const server of servers) { | 158 | for (const server of servers) { |