diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/actor.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-create.ts | 17 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-like.ts | 5 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-update.ts | 3 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 125 | ||||
-rw-r--r-- | server/lib/emailer.ts | 7 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/activitypub-refresher.ts | 41 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-file.ts | 4 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-import.ts | 9 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-views.ts | 15 | ||||
-rw-r--r-- | server/lib/job-queue/job-queue.ts | 8 | ||||
-rw-r--r-- | server/lib/redis.ts | 9 | ||||
-rw-r--r-- | server/lib/schedulers/videos-redundancy-scheduler.ts | 4 |
13 files changed, 160 insertions, 91 deletions
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 504263c99..bbe48833d 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -178,9 +178,7 @@ async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { | |||
178 | const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] | 178 | const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] |
179 | 179 | ||
180 | const avatarName = uuidv4() + extension | 180 | const avatarName = uuidv4() + extension |
181 | const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) | 181 | await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE) |
182 | |||
183 | await downloadImage(actorJSON.icon.url, destPath, AVATARS_SIZE) | ||
184 | 182 | ||
185 | return avatarName | 183 | return avatarName |
186 | } | 184 | } |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 9a72cb899..df05ee452 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -12,9 +12,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
12 | import { forwardVideoRelatedActivity } from '../send/utils' | 12 | import { forwardVideoRelatedActivity } from '../send/utils' |
13 | import { Redis } from '../../redis' | 13 | import { Redis } from '../../redis' |
14 | import { createOrUpdateCacheFile } from '../cache-file' | 14 | import { createOrUpdateCacheFile } from '../cache-file' |
15 | import { immutableAssign } from '../../../../shared/utils' | ||
16 | import { getVideoDislikeActivityPubUrl } from '../url' | 15 | import { getVideoDislikeActivityPubUrl } from '../url' |
17 | import { VideoModel } from '../../../models/video/video' | ||
18 | 16 | ||
19 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { | 17 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { |
20 | const activityObject = activity.object | 18 | const activityObject = activity.object |
@@ -71,7 +69,7 @@ async function processCreateDislike (byActor: ActorModel, activity: ActivityCrea | |||
71 | 69 | ||
72 | const [ , created ] = await AccountVideoRateModel.findOrCreate({ | 70 | const [ , created ] = await AccountVideoRateModel.findOrCreate({ |
73 | where: rate, | 71 | where: rate, |
74 | defaults: immutableAssign(rate, { url: getVideoDislikeActivityPubUrl(byActor, video) }), | 72 | defaults: Object.assign({}, rate, { url: getVideoDislikeActivityPubUrl(byActor, video) }), |
75 | transaction: t | 73 | transaction: t |
76 | }) | 74 | }) |
77 | if (created === true) await video.increment('dislikes', { transaction: t }) | 75 | if (created === true) await video.increment('dislikes', { transaction: t }) |
@@ -88,10 +86,19 @@ async function processCreateDislike (byActor: ActorModel, activity: ActivityCrea | |||
88 | async function processCreateView (byActor: ActorModel, activity: ActivityCreate) { | 86 | async function processCreateView (byActor: ActorModel, activity: ActivityCreate) { |
89 | const view = activity.object as ViewObject | 87 | const view = activity.object as ViewObject |
90 | 88 | ||
91 | const video = await VideoModel.loadByUrl(view.object) | 89 | const options = { |
92 | if (!video || video.isOwned() === false) return | 90 | videoObject: view.object, |
91 | fetchType: 'only-video' as 'only-video' | ||
92 | } | ||
93 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) | ||
93 | 94 | ||
94 | await Redis.Instance.addVideoView(video.id) | 95 | await Redis.Instance.addVideoView(video.id) |
96 | |||
97 | if (video.isOwned()) { | ||
98 | // Don't resend the activity to the sender | ||
99 | const exceptions = [ byActor ] | ||
100 | await forwardVideoRelatedActivity(activity, undefined, exceptions, video) | ||
101 | } | ||
95 | } | 102 | } |
96 | 103 | ||
97 | async function processCacheFile (byActor: ActorModel, activity: ActivityCreate) { | 104 | async function processCacheFile (byActor: ActorModel, activity: ActivityCreate) { |
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts index be86665e9..e8e97eece 100644 --- a/server/lib/activitypub/process/process-like.ts +++ b/server/lib/activitypub/process/process-like.ts | |||
@@ -5,8 +5,7 @@ import { AccountVideoRateModel } from '../../../models/account/account-video-rat | |||
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { forwardVideoRelatedActivity } from '../send/utils' | 6 | import { forwardVideoRelatedActivity } from '../send/utils' |
7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
8 | import { immutableAssign } from '../../../../shared/utils' | 8 | import { getVideoLikeActivityPubUrl } from '../url' |
9 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | ||
10 | 9 | ||
11 | async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) { | 10 | async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) { |
12 | return retryTransactionWrapper(processLikeVideo, byActor, activity) | 11 | return retryTransactionWrapper(processLikeVideo, byActor, activity) |
@@ -36,7 +35,7 @@ async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) { | |||
36 | } | 35 | } |
37 | const [ , created ] = await AccountVideoRateModel.findOrCreate({ | 36 | const [ , created ] = await AccountVideoRateModel.findOrCreate({ |
38 | where: rate, | 37 | where: rate, |
39 | defaults: immutableAssign(rate, { url: getVideoLikeActivityPubUrl(byActor, video) }), | 38 | defaults: Object.assign({}, rate, { url: getVideoLikeActivityPubUrl(byActor, video) }), |
40 | transaction: t | 39 | transaction: t |
41 | }) | 40 | }) |
42 | if (created === true) await video.increment('likes', { transaction: t }) | 41 | if (created === true) await video.increment('likes', { transaction: t }) |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index bd4013555..c6b42d846 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -51,7 +51,7 @@ async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) | |||
51 | return undefined | 51 | return undefined |
52 | } | 52 | } |
53 | 53 | ||
54 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id }) | 54 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false }) |
55 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | 55 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) |
56 | 56 | ||
57 | const updateOptions = { | 57 | const updateOptions = { |
@@ -59,7 +59,6 @@ async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) | |||
59 | videoObject, | 59 | videoObject, |
60 | account: actor.Account, | 60 | account: actor.Account, |
61 | channel: channelActor.VideoChannel, | 61 | channel: channelActor.VideoChannel, |
62 | updateViews: true, | ||
63 | overrideTo: activity.to | 62 | overrideTo: activity.to |
64 | } | 63 | } |
65 | return updateVideoFromAP(updateOptions) | 64 | return updateVideoFromAP(updateOptions) |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 4cecf9345..3d17e6846 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -95,9 +95,8 @@ function fetchRemoteVideoStaticFile (video: VideoModel, path: string, reject: Fu | |||
95 | 95 | ||
96 | function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) { | 96 | function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) { |
97 | const thumbnailName = video.getThumbnailName() | 97 | const thumbnailName = video.getThumbnailName() |
98 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) | ||
99 | 98 | ||
100 | return downloadImage(icon.url, thumbnailPath, THUMBNAILS_SIZE) | 99 | return downloadImage(icon.url, CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName, THUMBNAILS_SIZE) |
101 | } | 100 | } |
102 | 101 | ||
103 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { | 102 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { |
@@ -117,7 +116,7 @@ type SyncParam = { | |||
117 | shares: boolean | 116 | shares: boolean |
118 | comments: boolean | 117 | comments: boolean |
119 | thumbnail: boolean | 118 | thumbnail: boolean |
120 | refreshVideo: boolean | 119 | refreshVideo?: boolean |
121 | } | 120 | } |
122 | async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { | 121 | async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { |
123 | logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) | 122 | logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) |
@@ -159,26 +158,29 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
159 | videoObject: VideoTorrentObject | string, | 158 | videoObject: VideoTorrentObject | string, |
160 | syncParam?: SyncParam, | 159 | syncParam?: SyncParam, |
161 | fetchType?: VideoFetchByUrlType, | 160 | fetchType?: VideoFetchByUrlType, |
162 | refreshViews?: boolean | 161 | allowRefresh?: boolean // true by default |
163 | }) { | 162 | }) { |
164 | // Default params | 163 | // Default params |
165 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } | 164 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } |
166 | const fetchType = options.fetchType || 'all' | 165 | const fetchType = options.fetchType || 'all' |
167 | const refreshViews = options.refreshViews || false | 166 | const allowRefresh = options.allowRefresh !== false |
168 | 167 | ||
169 | // Get video url | 168 | // Get video url |
170 | const videoUrl = getAPUrl(options.videoObject) | 169 | const videoUrl = getAPUrl(options.videoObject) |
171 | 170 | ||
172 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) | 171 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) |
173 | if (videoFromDatabase) { | 172 | if (videoFromDatabase) { |
174 | const refreshOptions = { | 173 | |
175 | video: videoFromDatabase, | 174 | if (allowRefresh === true) { |
176 | fetchedType: fetchType, | 175 | const refreshOptions = { |
177 | syncParam, | 176 | video: videoFromDatabase, |
178 | refreshViews | 177 | fetchedType: fetchType, |
178 | syncParam | ||
179 | } | ||
180 | |||
181 | if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) | ||
182 | else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoFromDatabase.url } }) | ||
179 | } | 183 | } |
180 | const p = refreshVideoIfNeeded(refreshOptions) | ||
181 | if (syncParam.refreshVideo === true) videoFromDatabase = await p | ||
182 | 184 | ||
183 | return { video: videoFromDatabase } | 185 | return { video: videoFromDatabase } |
184 | } | 186 | } |
@@ -199,7 +201,6 @@ async function updateVideoFromAP (options: { | |||
199 | videoObject: VideoTorrentObject, | 201 | videoObject: VideoTorrentObject, |
200 | account: AccountModel, | 202 | account: AccountModel, |
201 | channel: VideoChannelModel, | 203 | channel: VideoChannelModel, |
202 | updateViews: boolean, | ||
203 | overrideTo?: string[] | 204 | overrideTo?: string[] |
204 | }) { | 205 | }) { |
205 | logger.debug('Updating remote video "%s".', options.videoObject.uuid) | 206 | logger.debug('Updating remote video "%s".', options.videoObject.uuid) |
@@ -238,8 +239,8 @@ async function updateVideoFromAP (options: { | |||
238 | options.video.set('publishedAt', videoData.publishedAt) | 239 | options.video.set('publishedAt', videoData.publishedAt) |
239 | options.video.set('privacy', videoData.privacy) | 240 | options.video.set('privacy', videoData.privacy) |
240 | options.video.set('channelId', videoData.channelId) | 241 | options.video.set('channelId', videoData.channelId) |
242 | options.video.set('views', videoData.views) | ||
241 | 243 | ||
242 | if (options.updateViews === true) options.video.set('views', videoData.views) | ||
243 | await options.video.save(sequelizeOptions) | 244 | await options.video.save(sequelizeOptions) |
244 | 245 | ||
245 | { | 246 | { |
@@ -297,8 +298,58 @@ async function updateVideoFromAP (options: { | |||
297 | } | 298 | } |
298 | } | 299 | } |
299 | 300 | ||
301 | async function refreshVideoIfNeeded (options: { | ||
302 | video: VideoModel, | ||
303 | fetchedType: VideoFetchByUrlType, | ||
304 | syncParam: SyncParam | ||
305 | }): Promise<VideoModel> { | ||
306 | if (!options.video.isOutdated()) return options.video | ||
307 | |||
308 | // We need more attributes if the argument video was fetched with not enough joints | ||
309 | const video = options.fetchedType === 'all' ? options.video : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) | ||
310 | |||
311 | try { | ||
312 | const { response, videoObject } = await fetchRemoteVideo(video.url) | ||
313 | if (response.statusCode === 404) { | ||
314 | logger.info('Cannot refresh remote video %s: video does not exist anymore. Deleting it.', video.url) | ||
315 | |||
316 | // Video does not exist anymore | ||
317 | await video.destroy() | ||
318 | return undefined | ||
319 | } | ||
320 | |||
321 | if (videoObject === undefined) { | ||
322 | logger.warn('Cannot refresh remote video %s: invalid body.', video.url) | ||
323 | |||
324 | await video.setAsRefreshed() | ||
325 | return video | ||
326 | } | ||
327 | |||
328 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | ||
329 | const account = await AccountModel.load(channelActor.VideoChannel.accountId) | ||
330 | |||
331 | const updateOptions = { | ||
332 | video, | ||
333 | videoObject, | ||
334 | account, | ||
335 | channel: channelActor.VideoChannel | ||
336 | } | ||
337 | await retryTransactionWrapper(updateVideoFromAP, updateOptions) | ||
338 | await syncVideoExternalAttributes(video, videoObject, options.syncParam) | ||
339 | |||
340 | return video | ||
341 | } catch (err) { | ||
342 | logger.warn('Cannot refresh video %s.', options.video.url, { err }) | ||
343 | |||
344 | // Don't refresh in loop | ||
345 | await video.setAsRefreshed() | ||
346 | return video | ||
347 | } | ||
348 | } | ||
349 | |||
300 | export { | 350 | export { |
301 | updateVideoFromAP, | 351 | updateVideoFromAP, |
352 | refreshVideoIfNeeded, | ||
302 | federateVideoIfNeeded, | 353 | federateVideoIfNeeded, |
303 | fetchRemoteVideo, | 354 | fetchRemoteVideo, |
304 | getOrCreateVideoAndAccountAndChannel, | 355 | getOrCreateVideoAndAccountAndChannel, |
@@ -362,52 +413,6 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
362 | return videoCreated | 413 | return videoCreated |
363 | } | 414 | } |
364 | 415 | ||
365 | async function refreshVideoIfNeeded (options: { | ||
366 | video: VideoModel, | ||
367 | fetchedType: VideoFetchByUrlType, | ||
368 | syncParam: SyncParam, | ||
369 | refreshViews: boolean | ||
370 | }): Promise<VideoModel> { | ||
371 | if (!options.video.isOutdated()) return options.video | ||
372 | |||
373 | // We need more attributes if the argument video was fetched with not enough joints | ||
374 | const video = options.fetchedType === 'all' ? options.video : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) | ||
375 | |||
376 | try { | ||
377 | const { response, videoObject } = await fetchRemoteVideo(video.url) | ||
378 | if (response.statusCode === 404) { | ||
379 | logger.info('Cannot refresh remote video %s: video does not exist anymore. Deleting it.', video.url) | ||
380 | |||
381 | // Video does not exist anymore | ||
382 | await video.destroy() | ||
383 | return undefined | ||
384 | } | ||
385 | |||
386 | if (videoObject === undefined) { | ||
387 | logger.warn('Cannot refresh remote video %s: invalid body.', video.url) | ||
388 | return video | ||
389 | } | ||
390 | |||
391 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | ||
392 | const account = await AccountModel.load(channelActor.VideoChannel.accountId) | ||
393 | |||
394 | const updateOptions = { | ||
395 | video, | ||
396 | videoObject, | ||
397 | account, | ||
398 | channel: channelActor.VideoChannel, | ||
399 | updateViews: options.refreshViews | ||
400 | } | ||
401 | await retryTransactionWrapper(updateVideoFromAP, updateOptions) | ||
402 | await syncVideoExternalAttributes(video, videoObject, options.syncParam) | ||
403 | |||
404 | return video | ||
405 | } catch (err) { | ||
406 | logger.warn('Cannot refresh video %s.', options.video.url, { err }) | ||
407 | return video | ||
408 | } | ||
409 | } | ||
410 | |||
411 | async function videoActivityObjectToDBAttributes ( | 416 | async function videoActivityObjectToDBAttributes ( |
412 | videoChannel: VideoChannelModel, | 417 | videoChannel: VideoChannelModel, |
413 | videoObject: VideoTorrentObject, | 418 | videoObject: VideoTorrentObject, |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 9327792fb..074d4ad44 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -14,6 +14,7 @@ class Emailer { | |||
14 | private static instance: Emailer | 14 | private static instance: Emailer |
15 | private initialized = false | 15 | private initialized = false |
16 | private transporter: Transporter | 16 | private transporter: Transporter |
17 | private enabled = false | ||
17 | 18 | ||
18 | private constructor () {} | 19 | private constructor () {} |
19 | 20 | ||
@@ -50,6 +51,8 @@ class Emailer { | |||
50 | tls, | 51 | tls, |
51 | auth | 52 | auth |
52 | }) | 53 | }) |
54 | |||
55 | this.enabled = true | ||
53 | } else { | 56 | } else { |
54 | if (!isTestInstance()) { | 57 | if (!isTestInstance()) { |
55 | logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!') | 58 | logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!') |
@@ -57,6 +60,10 @@ class Emailer { | |||
57 | } | 60 | } |
58 | } | 61 | } |
59 | 62 | ||
63 | isEnabled () { | ||
64 | return this.enabled | ||
65 | } | ||
66 | |||
60 | async checkConnectionOrDie () { | 67 | async checkConnectionOrDie () { |
61 | if (!this.transporter) return | 68 | if (!this.transporter) return |
62 | 69 | ||
diff --git a/server/lib/job-queue/handlers/activitypub-refresher.ts b/server/lib/job-queue/handlers/activitypub-refresher.ts new file mode 100644 index 000000000..671b0f487 --- /dev/null +++ b/server/lib/job-queue/handlers/activitypub-refresher.ts | |||
@@ -0,0 +1,41 @@ | |||
1 | import * as Bull from 'bull' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { fetchVideoByUrl } from '../../../helpers/video' | ||
4 | import { refreshVideoIfNeeded } from '../../activitypub' | ||
5 | |||
6 | export type RefreshPayload = { | ||
7 | videoUrl: string | ||
8 | type: 'video' | ||
9 | } | ||
10 | |||
11 | async function refreshAPObject (job: Bull.Job) { | ||
12 | const payload = job.data as RefreshPayload | ||
13 | |||
14 | logger.info('Processing AP refresher in job %d for video %s.', job.id, payload.videoUrl) | ||
15 | |||
16 | if (payload.type === 'video') return refreshAPVideo(payload.videoUrl) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | refreshAPObject | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | async function refreshAPVideo (videoUrl: string) { | ||
28 | const fetchType = 'all' as 'all' | ||
29 | const syncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true } | ||
30 | |||
31 | const videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) | ||
32 | if (videoFromDatabase) { | ||
33 | const refreshOptions = { | ||
34 | video: videoFromDatabase, | ||
35 | fetchedType: fetchType, | ||
36 | syncParam | ||
37 | } | ||
38 | |||
39 | await refreshVideoIfNeeded(refreshOptions) | ||
40 | } | ||
41 | } | ||
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts index adc0a2a15..ddbf6d1c2 100644 --- a/server/lib/job-queue/handlers/video-file.ts +++ b/server/lib/job-queue/handlers/video-file.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { VideoResolution, VideoState } from '../../../../shared' | 2 | import { VideoResolution, VideoState, Job } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { VideoModel } from '../../../models/video/video' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { JobQueue } from '../job-queue' | 5 | import { JobQueue } from '../job-queue' |
@@ -111,7 +111,7 @@ async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boole | |||
111 | ) | 111 | ) |
112 | 112 | ||
113 | if (resolutionsEnabled.length !== 0) { | 113 | if (resolutionsEnabled.length !== 0) { |
114 | const tasks: Bluebird<any>[] = [] | 114 | const tasks: Bluebird<Bull.Job<any>>[] = [] |
115 | 115 | ||
116 | for (const resolution of resolutionsEnabled) { | 116 | for (const resolution of resolutionsEnabled) { |
117 | const dataInput = { | 117 | const dataInput = { |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 4de901c0c..51a0b5faf 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -7,7 +7,7 @@ import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } fro | |||
7 | import { extname, join } from 'path' | 7 | import { extname, join } from 'path' |
8 | import { VideoFileModel } from '../../../models/video/video-file' | 8 | import { VideoFileModel } from '../../../models/video/video-file' |
9 | import { CONFIG, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_IMPORT_TIMEOUT } from '../../../initializers' | 9 | import { CONFIG, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_IMPORT_TIMEOUT } from '../../../initializers' |
10 | import { doRequestAndSaveToFile, downloadImage } from '../../../helpers/requests' | 10 | import { downloadImage } from '../../../helpers/requests' |
11 | import { VideoState } from '../../../../shared' | 11 | import { VideoState } from '../../../../shared' |
12 | import { JobQueue } from '../index' | 12 | import { JobQueue } from '../index' |
13 | import { federateVideoIfNeeded } from '../../activitypub' | 13 | import { federateVideoIfNeeded } from '../../activitypub' |
@@ -109,6 +109,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
109 | let tempVideoPath: string | 109 | let tempVideoPath: string |
110 | let videoDestFile: string | 110 | let videoDestFile: string |
111 | let videoFile: VideoFileModel | 111 | let videoFile: VideoFileModel |
112 | |||
112 | try { | 113 | try { |
113 | // Download video from youtubeDL | 114 | // Download video from youtubeDL |
114 | tempVideoPath = await downloader() | 115 | tempVideoPath = await downloader() |
@@ -144,8 +145,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
144 | // Process thumbnail | 145 | // Process thumbnail |
145 | if (options.downloadThumbnail) { | 146 | if (options.downloadThumbnail) { |
146 | if (options.thumbnailUrl) { | 147 | if (options.thumbnailUrl) { |
147 | const destThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoImport.Video.getThumbnailName()) | 148 | await downloadImage(options.thumbnailUrl, CONFIG.STORAGE.THUMBNAILS_DIR, videoImport.Video.getThumbnailName(), THUMBNAILS_SIZE) |
148 | await downloadImage(options.thumbnailUrl, destThumbnailPath, THUMBNAILS_SIZE) | ||
149 | } else { | 149 | } else { |
150 | await videoImport.Video.createThumbnail(videoFile) | 150 | await videoImport.Video.createThumbnail(videoFile) |
151 | } | 151 | } |
@@ -156,8 +156,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
156 | // Process preview | 156 | // Process preview |
157 | if (options.downloadPreview) { | 157 | if (options.downloadPreview) { |
158 | if (options.thumbnailUrl) { | 158 | if (options.thumbnailUrl) { |
159 | const destPreviewPath = join(CONFIG.STORAGE.PREVIEWS_DIR, videoImport.Video.getPreviewName()) | 159 | await downloadImage(options.thumbnailUrl, CONFIG.STORAGE.PREVIEWS_DIR, videoImport.Video.getPreviewName(), PREVIEWS_SIZE) |
160 | await downloadImage(options.thumbnailUrl, destPreviewPath, PREVIEWS_SIZE) | ||
161 | } else { | 160 | } else { |
162 | await videoImport.Video.createPreview(videoFile) | 161 | await videoImport.Video.createPreview(videoFile) |
163 | } | 162 | } |
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts index f44c3c727..fa1fd13b3 100644 --- a/server/lib/job-queue/handlers/video-views.ts +++ b/server/lib/job-queue/handlers/video-views.ts | |||
@@ -23,13 +23,9 @@ async function processVideosViews () { | |||
23 | for (const videoId of videoIds) { | 23 | for (const videoId of videoIds) { |
24 | try { | 24 | try { |
25 | const views = await Redis.Instance.getVideoViews(videoId, hour) | 25 | const views = await Redis.Instance.getVideoViews(videoId, hour) |
26 | if (isNaN(views)) { | 26 | if (views) { |
27 | logger.error('Cannot process videos views of video %d in hour %d: views number is NaN.', videoId, hour) | ||
28 | } else { | ||
29 | logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) | 27 | logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) |
30 | 28 | ||
31 | await VideoModel.incrementViews(videoId, views) | ||
32 | |||
33 | try { | 29 | try { |
34 | await VideoViewModel.create({ | 30 | await VideoViewModel.create({ |
35 | startDate, | 31 | startDate, |
@@ -39,7 +35,14 @@ async function processVideosViews () { | |||
39 | }) | 35 | }) |
40 | 36 | ||
41 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 37 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
42 | if (video.isOwned()) await federateVideoIfNeeded(video, false) | 38 | if (video.isOwned()) { |
39 | // If this is a remote video, the origin instance will send us an update | ||
40 | await VideoModel.incrementViews(videoId, views) | ||
41 | |||
42 | // Send video update | ||
43 | video.views += views | ||
44 | await federateVideoIfNeeded(video, false) | ||
45 | } | ||
43 | } catch (err) { | 46 | } catch (err) { |
44 | logger.debug('Cannot create video views for video %d in hour %d. Maybe the video does not exist anymore?', videoId, hour) | 47 | logger.debug('Cannot create video views for video %d in hour %d. Maybe the video does not exist anymore?', videoId, hour) |
45 | } | 48 | } |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index 4cfd4d253..5862e178f 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -11,6 +11,7 @@ import { processVideoFile, processVideoFileImport, VideoFileImportPayload, Video | |||
11 | import { ActivitypubFollowPayload, processActivityPubFollow } from './handlers/activitypub-follow' | 11 | import { ActivitypubFollowPayload, processActivityPubFollow } from './handlers/activitypub-follow' |
12 | import { processVideoImport, VideoImportPayload } from './handlers/video-import' | 12 | import { processVideoImport, VideoImportPayload } from './handlers/video-import' |
13 | import { processVideosViews } from './handlers/video-views' | 13 | import { processVideosViews } from './handlers/video-views' |
14 | import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' | ||
14 | 15 | ||
15 | type CreateJobArgument = | 16 | type CreateJobArgument = |
16 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | | 17 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | |
@@ -21,6 +22,7 @@ type CreateJobArgument = | |||
21 | { type: 'video-file', payload: VideoFilePayload } | | 22 | { type: 'video-file', payload: VideoFilePayload } | |
22 | { type: 'email', payload: EmailPayload } | | 23 | { type: 'email', payload: EmailPayload } | |
23 | { type: 'video-import', payload: VideoImportPayload } | | 24 | { type: 'video-import', payload: VideoImportPayload } | |
25 | { type: 'activitypub-refresher', payload: RefreshPayload } | | ||
24 | { type: 'videos-views', payload: {} } | 26 | { type: 'videos-views', payload: {} } |
25 | 27 | ||
26 | const handlers: { [ id in JobType ]: (job: Bull.Job) => Promise<any>} = { | 28 | const handlers: { [ id in JobType ]: (job: Bull.Job) => Promise<any>} = { |
@@ -32,7 +34,8 @@ const handlers: { [ id in JobType ]: (job: Bull.Job) => Promise<any>} = { | |||
32 | 'video-file': processVideoFile, | 34 | 'video-file': processVideoFile, |
33 | 'email': processEmail, | 35 | 'email': processEmail, |
34 | 'video-import': processVideoImport, | 36 | 'video-import': processVideoImport, |
35 | 'videos-views': processVideosViews | 37 | 'videos-views': processVideosViews, |
38 | 'activitypub-refresher': refreshAPObject | ||
36 | } | 39 | } |
37 | 40 | ||
38 | const jobTypes: JobType[] = [ | 41 | const jobTypes: JobType[] = [ |
@@ -44,7 +47,8 @@ const jobTypes: JobType[] = [ | |||
44 | 'video-file', | 47 | 'video-file', |
45 | 'video-file-import', | 48 | 'video-file-import', |
46 | 'video-import', | 49 | 'video-import', |
47 | 'videos-views' | 50 | 'videos-views', |
51 | 'activitypub-refresher' | ||
48 | ] | 52 | ] |
49 | 53 | ||
50 | class JobQueue { | 54 | class JobQueue { |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index abd75d512..3e25e6a2c 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -121,7 +121,14 @@ class Redis { | |||
121 | const key = this.generateVideoViewKey(videoId, hour) | 121 | const key = this.generateVideoViewKey(videoId, hour) |
122 | 122 | ||
123 | const valueString = await this.getValue(key) | 123 | const valueString = await this.getValue(key) |
124 | return parseInt(valueString, 10) | 124 | const valueInt = parseInt(valueString, 10) |
125 | |||
126 | if (isNaN(valueInt)) { | ||
127 | logger.error('Cannot get videos views of video %d in hour %d: views number is NaN (%s).', videoId, hour, valueString) | ||
128 | return undefined | ||
129 | } | ||
130 | |||
131 | return valueInt | ||
125 | } | 132 | } |
126 | 133 | ||
127 | async getVideosIdViewed (hour: number) { | 134 | async getVideosIdViewed (hour: number) { |
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index 8b7f33539..2a99a665d 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -145,13 +145,13 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
145 | 145 | ||
146 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) | 146 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) |
147 | 147 | ||
148 | const destPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(file)) | 148 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) |
149 | await rename(tmpPath, destPath) | 149 | await rename(tmpPath, destPath) |
150 | 150 | ||
151 | const createdModel = await VideoRedundancyModel.create({ | 151 | const createdModel = await VideoRedundancyModel.create({ |
152 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 152 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), |
153 | url: getVideoCacheFileActivityPubUrl(file), | 153 | url: getVideoCacheFileActivityPubUrl(file), |
154 | fileUrl: video.getVideoFileUrl(file, CONFIG.WEBSERVER.URL), | 154 | fileUrl: video.getVideoRedundancyUrl(file, CONFIG.WEBSERVER.URL), |
155 | strategy: redundancy.strategy, | 155 | strategy: redundancy.strategy, |
156 | videoFileId: file.id, | 156 | videoFileId: file.id, |
157 | actorId: serverActor.id | 157 | actorId: serverActor.id |