aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-09-11 16:27:07 +0200
committerChocobozzz <me@florianbigard.com>2018-09-13 14:05:49 +0200
commitc48e82b5e0478434de30626d14594a97f2402e7c (patch)
treea78e5272bd0fe4f5b41831e571e02d05f1515b82 /server/lib
parenta651038487faa838bda3ce04695b08bc65baff70 (diff)
downloadPeerTube-c48e82b5e0478434de30626d14594a97f2402e7c.tar.gz
PeerTube-c48e82b5e0478434de30626d14594a97f2402e7c.tar.zst
PeerTube-c48e82b5e0478434de30626d14594a97f2402e7c.zip
Basic video redundancy implementation
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/actor.ts6
-rw-r--r--server/lib/activitypub/cache-file.ts47
-rw-r--r--server/lib/activitypub/process/process-create.ts21
-rw-r--r--server/lib/activitypub/process/process-undo.ts44
-rw-r--r--server/lib/activitypub/process/process-update.ts34
-rw-r--r--server/lib/activitypub/send/send-accept.ts8
-rw-r--r--server/lib/activitypub/send/send-announce.ts33
-rw-r--r--server/lib/activitypub/send/send-create.ts68
-rw-r--r--server/lib/activitypub/send/send-delete.ts25
-rw-r--r--server/lib/activitypub/send/send-follow.ts6
-rw-r--r--server/lib/activitypub/send/send-like.ts10
-rw-r--r--server/lib/activitypub/send/send-undo.ts63
-rw-r--r--server/lib/activitypub/send/send-update.ts38
-rw-r--r--server/lib/activitypub/send/utils.ts8
-rw-r--r--server/lib/activitypub/url.ts10
-rw-r--r--server/lib/activitypub/videos.ts32
-rw-r--r--server/lib/redundancy.ts18
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts161
18 files changed, 496 insertions, 136 deletions
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 1657262d7..3464add03 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -400,17 +400,15 @@ async function refreshActorIfNeeded (actor: ActorModel): Promise<{ actor: ActorM
400 await actor.save({ transaction: t }) 400 await actor.save({ transaction: t })
401 401
402 if (actor.Account) { 402 if (actor.Account) {
403 await actor.save({ transaction: t })
404
405 actor.Account.set('name', result.name) 403 actor.Account.set('name', result.name)
406 actor.Account.set('description', result.summary) 404 actor.Account.set('description', result.summary)
405
407 await actor.Account.save({ transaction: t }) 406 await actor.Account.save({ transaction: t })
408 } else if (actor.VideoChannel) { 407 } else if (actor.VideoChannel) {
409 await actor.save({ transaction: t })
410
411 actor.VideoChannel.set('name', result.name) 408 actor.VideoChannel.set('name', result.name)
412 actor.VideoChannel.set('description', result.summary) 409 actor.VideoChannel.set('description', result.summary)
413 actor.VideoChannel.set('support', result.support) 410 actor.VideoChannel.set('support', result.support)
411
414 await actor.VideoChannel.save({ transaction: t }) 412 await actor.VideoChannel.save({ transaction: t })
415 } 413 }
416 414
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts
new file mode 100644
index 000000000..7325ddcb6
--- /dev/null
+++ b/server/lib/activitypub/cache-file.ts
@@ -0,0 +1,47 @@
1import { CacheFileObject } from '../../../shared/index'
2import { VideoModel } from '../../models/video/video'
3import { ActorModel } from '../../models/activitypub/actor'
4import { sequelizeTypescript } from '../../initializers'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6
7function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: ActorModel) {
8 const url = cacheFileObject.url
9
10 const videoFile = video.VideoFiles.find(f => {
11 return f.resolution === url.height && f.fps === url.fps
12 })
13
14 if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`)
15
16 return {
17 expiresOn: new Date(cacheFileObject.expires),
18 url: cacheFileObject.id,
19 fileUrl: cacheFileObject.url.href,
20 strategy: null,
21 videoFileId: videoFile.id,
22 actorId: byActor.id
23 }
24}
25
26function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: ActorModel) {
27 return sequelizeTypescript.transaction(async t => {
28 const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor)
29
30 return VideoRedundancyModel.create(attributes, { transaction: t })
31 })
32}
33
34function updateCacheFile (cacheFileObject: CacheFileObject, redundancyModel: VideoRedundancyModel, byActor: ActorModel) {
35 const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, redundancyModel.VideoFile.Video, byActor)
36
37 redundancyModel.set('expires', attributes.expiresOn)
38 redundancyModel.set('fileUrl', attributes.fileUrl)
39
40 return redundancyModel.save()
41}
42
43export {
44 createCacheFile,
45 updateCacheFile,
46 cacheFileActivityObjectToDBAttributes
47}
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 16f426e23..32e555acf 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,4 +1,4 @@
1import { ActivityCreate, VideoAbuseState, VideoTorrentObject } from '../../../../shared' 1import { ActivityCreate, CacheFileObject, VideoAbuseState, VideoTorrentObject } from '../../../../shared'
2import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' 2import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
3import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' 3import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
4import { retryTransactionWrapper } from '../../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../../helpers/database-utils'
@@ -12,6 +12,7 @@ import { addVideoComment, resolveThread } from '../video-comments'
12import { getOrCreateVideoAndAccountAndChannel } from '../videos' 12import { getOrCreateVideoAndAccountAndChannel } from '../videos'
13import { forwardActivity, forwardVideoRelatedActivity } from '../send/utils' 13import { forwardActivity, forwardVideoRelatedActivity } from '../send/utils'
14import { Redis } from '../../redis' 14import { Redis } from '../../redis'
15import { createCacheFile } from '../cache-file'
15 16
16async function processCreateActivity (activity: ActivityCreate) { 17async function processCreateActivity (activity: ActivityCreate) {
17 const activityObject = activity.object 18 const activityObject = activity.object
@@ -28,6 +29,8 @@ async function processCreateActivity (activity: ActivityCreate) {
28 return retryTransactionWrapper(processCreateVideoAbuse, actor, activityObject as VideoAbuseObject) 29 return retryTransactionWrapper(processCreateVideoAbuse, actor, activityObject as VideoAbuseObject)
29 } else if (activityType === 'Note') { 30 } else if (activityType === 'Note') {
30 return retryTransactionWrapper(processCreateVideoComment, actor, activity) 31 return retryTransactionWrapper(processCreateVideoComment, actor, activity)
32 } else if (activityType === 'CacheFile') {
33 return retryTransactionWrapper(processCacheFile, actor, activity)
31 } 34 }
32 35
33 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) 36 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -97,6 +100,20 @@ async function processCreateView (byActor: ActorModel, activity: ActivityCreate)
97 } 100 }
98} 101}
99 102
103async function processCacheFile (byActor: ActorModel, activity: ActivityCreate) {
104 const cacheFile = activity.object as CacheFileObject
105
106 const { video } = await getOrCreateVideoAndAccountAndChannel(cacheFile.object)
107
108 await createCacheFile(cacheFile, video, byActor)
109
110 if (video.isOwned()) {
111 // Don't resend the activity to the sender
112 const exceptions = [ byActor ]
113 await forwardActivity(activity, undefined, exceptions)
114 }
115}
116
100async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) { 117async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
101 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) 118 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
102 119
@@ -113,7 +130,7 @@ async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateDat
113 state: VideoAbuseState.PENDING 130 state: VideoAbuseState.PENDING
114 } 131 }
115 132
116 await VideoAbuseModel.create(videoAbuseData) 133 await VideoAbuseModel.create(videoAbuseData, { transaction: t })
117 134
118 logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) 135 logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
119 }) 136 })
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index 1c1de8827..0eb5fa392 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -1,4 +1,4 @@
1import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' 1import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo, CacheFileObject } from '../../../../shared/models/activitypub'
2import { DislikeObject } from '../../../../shared/models/activitypub/objects' 2import { DislikeObject } from '../../../../shared/models/activitypub/objects'
3import { getActorUrl } from '../../../helpers/activitypub' 3import { getActorUrl } from '../../../helpers/activitypub'
4import { retryTransactionWrapper } from '../../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../../helpers/database-utils'
@@ -11,6 +11,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
11import { forwardVideoRelatedActivity } from '../send/utils' 11import { forwardVideoRelatedActivity } from '../send/utils'
12import { getOrCreateVideoAndAccountAndChannel } from '../videos' 12import { getOrCreateVideoAndAccountAndChannel } from '../videos'
13import { VideoShareModel } from '../../../models/video/video-share' 13import { VideoShareModel } from '../../../models/video/video-share'
14import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
14 15
15async function processUndoActivity (activity: ActivityUndo) { 16async function processUndoActivity (activity: ActivityUndo) {
16 const activityToUndo = activity.object 17 const activityToUndo = activity.object
@@ -19,11 +20,21 @@ async function processUndoActivity (activity: ActivityUndo) {
19 20
20 if (activityToUndo.type === 'Like') { 21 if (activityToUndo.type === 'Like') {
21 return retryTransactionWrapper(processUndoLike, actorUrl, activity) 22 return retryTransactionWrapper(processUndoLike, actorUrl, activity)
22 } else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') { 23 }
23 return retryTransactionWrapper(processUndoDislike, actorUrl, activity) 24
24 } else if (activityToUndo.type === 'Follow') { 25 if (activityToUndo.type === 'Create') {
26 if (activityToUndo.object.type === 'Dislike') {
27 return retryTransactionWrapper(processUndoDislike, actorUrl, activity)
28 } else if (activityToUndo.object.type === 'CacheFile') {
29 return retryTransactionWrapper(processUndoCacheFile, actorUrl, activity)
30 }
31 }
32
33 if (activityToUndo.type === 'Follow') {
25 return retryTransactionWrapper(processUndoFollow, actorUrl, activityToUndo) 34 return retryTransactionWrapper(processUndoFollow, actorUrl, activityToUndo)
26 } else if (activityToUndo.type === 'Announce') { 35 }
36
37 if (activityToUndo.type === 'Announce') {
27 return retryTransactionWrapper(processUndoAnnounce, actorUrl, activityToUndo) 38 return retryTransactionWrapper(processUndoAnnounce, actorUrl, activityToUndo)
28 } 39 }
29 40
@@ -88,6 +99,29 @@ async function processUndoDislike (actorUrl: string, activity: ActivityUndo) {
88 }) 99 })
89} 100}
90 101
102async function processUndoCacheFile (actorUrl: string, activity: ActivityUndo) {
103 const cacheFileObject = activity.object.object as CacheFileObject
104
105 const { video } = await getOrCreateVideoAndAccountAndChannel(cacheFileObject.object)
106
107 return sequelizeTypescript.transaction(async t => {
108 const byActor = await ActorModel.loadByUrl(actorUrl)
109 if (!byActor) throw new Error('Unknown actor ' + actorUrl)
110
111 const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id)
112 if (!cacheFile) throw new Error('Unknown video cache ' + cacheFile.url)
113
114 await cacheFile.destroy()
115
116 if (video.isOwned()) {
117 // Don't resend the activity to the sender
118 const exceptions = [ byActor ]
119
120 await forwardVideoRelatedActivity(activity, t, exceptions, video)
121 }
122 })
123}
124
91function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) { 125function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) {
92 return sequelizeTypescript.transaction(async t => { 126 return sequelizeTypescript.transaction(async t => {
93 const follower = await ActorModel.loadByUrl(actorUrl, t) 127 const follower = await ActorModel.loadByUrl(actorUrl, t)
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index d2ad738a2..d3af1a181 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -1,4 +1,4 @@
1import { ActivityUpdate, VideoTorrentObject } from '../../../../shared/models/activitypub' 1import { ActivityUpdate, CacheFileObject, VideoTorrentObject } from '../../../../shared/models/activitypub'
2import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' 2import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
3import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' 3import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
@@ -7,8 +7,11 @@ import { AccountModel } from '../../../models/account/account'
7import { ActorModel } from '../../../models/activitypub/actor' 7import { ActorModel } from '../../../models/activitypub/actor'
8import { VideoChannelModel } from '../../../models/video/video-channel' 8import { VideoChannelModel } from '../../../models/video/video-channel'
9import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' 9import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
10import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' 10import { getOrCreateVideoAndAccountAndChannel, updateVideoFromAP, getOrCreateVideoChannelFromVideoObject } from '../videos'
11import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' 11import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
12import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file'
13import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
14import { createCacheFile, updateCacheFile } from '../cache-file'
12 15
13async function processUpdateActivity (activity: ActivityUpdate) { 16async function processUpdateActivity (activity: ActivityUpdate) {
14 const actor = await getOrCreateActorAndServerAndModel(activity.actor) 17 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
@@ -16,10 +19,16 @@ async function processUpdateActivity (activity: ActivityUpdate) {
16 19
17 if (objectType === 'Video') { 20 if (objectType === 'Video') {
18 return retryTransactionWrapper(processUpdateVideo, actor, activity) 21 return retryTransactionWrapper(processUpdateVideo, actor, activity)
19 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') { 22 }
23
24 if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
20 return retryTransactionWrapper(processUpdateActor, actor, activity) 25 return retryTransactionWrapper(processUpdateActor, actor, activity)
21 } 26 }
22 27
28 if (objectType === 'CacheFile') {
29 return retryTransactionWrapper(processUpdateCacheFile, actor, activity)
30 }
31
23 return undefined 32 return undefined
24} 33}
25 34
@@ -42,7 +51,24 @@ async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate)
42 const { video } = await getOrCreateVideoAndAccountAndChannel(videoObject.id) 51 const { video } = await getOrCreateVideoAndAccountAndChannel(videoObject.id)
43 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 52 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
44 53
45 return updateVideoFromAP(video, videoObject, actor, channelActor, activity.to) 54 return updateVideoFromAP(video, videoObject, actor.Account, channelActor.VideoChannel, activity.to)
55}
56
57async function processUpdateCacheFile (byActor: ActorModel, activity: ActivityUpdate) {
58 const cacheFileObject = activity.object as CacheFileObject
59
60 if (!isCacheFileObjectValid(cacheFileObject) === false) {
61 logger.debug('Cahe file object sent by update is not valid.', { cacheFileObject })
62 return undefined
63 }
64
65 const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id)
66 if (!redundancyModel) {
67 const { video } = await getOrCreateVideoAndAccountAndChannel(cacheFileObject.id)
68 return createCacheFile(cacheFileObject, video, byActor)
69 }
70
71 return updateCacheFile(cacheFileObject, redundancyModel, byActor)
46} 72}
47 73
48async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) { 74async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index ef679707b..b6abde13d 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -3,7 +3,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url' 4import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url'
5import { unicastTo } from './utils' 5import { unicastTo } from './utils'
6import { followActivityData } from './send-follow' 6import { buildFollowActivity } from './send-follow'
7import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8 8
9async function sendAccept (actorFollow: ActorFollowModel) { 9async function sendAccept (actorFollow: ActorFollowModel) {
@@ -18,10 +18,10 @@ async function sendAccept (actorFollow: ActorFollowModel) {
18 logger.info('Creating job to accept follower %s.', follower.url) 18 logger.info('Creating job to accept follower %s.', follower.url)
19 19
20 const followUrl = getActorFollowActivityPubUrl(actorFollow) 20 const followUrl = getActorFollowActivityPubUrl(actorFollow)
21 const followData = followActivityData(followUrl, follower, me) 21 const followData = buildFollowActivity(followUrl, follower, me)
22 22
23 const url = getActorFollowAcceptActivityPubUrl(actorFollow) 23 const url = getActorFollowAcceptActivityPubUrl(actorFollow)
24 const data = acceptActivityData(url, me, followData) 24 const data = buildAcceptActivity(url, me, followData)
25 25
26 return unicastTo(data, me, follower.inboxUrl) 26 return unicastTo(data, me, follower.inboxUrl)
27} 27}
@@ -34,7 +34,7 @@ export {
34 34
35// --------------------------------------------------------------------------- 35// ---------------------------------------------------------------------------
36 36
37function acceptActivityData (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityAccept { 37function buildAcceptActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityAccept {
38 return { 38 return {
39 type: 'Accept', 39 type: 'Accept',
40 id: url, 40 id: url,
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 352813d73..f137217f8 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -4,45 +4,44 @@ import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video' 4import { VideoModel } from '../../../models/video/video'
5import { VideoShareModel } from '../../../models/video/video-share' 5import { VideoShareModel } from '../../../models/video/video-share'
6import { broadcastToFollowers } from './utils' 6import { broadcastToFollowers } from './utils'
7import { getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience' 7import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience'
8import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
9 9
10async function buildVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { 10async function buildAnnounceWithVideoAudience (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
11 const announcedObject = video.url 11 const announcedObject = video.url
12 12
13 const accountsToForwardView = await getActorsInvolvedInVideo(video, t) 13 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
14 const audience = getObjectFollowersAudience(accountsToForwardView) 14 const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
15 return announceActivityData(videoShare.url, byActor, announcedObject, audience) 15
16 const activity = buildAnnounceActivity(videoShare.url, byActor, announcedObject, audience)
17
18 return { activity, actorsInvolvedInVideo }
16} 19}
17 20
18async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { 21async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
19 const data = await buildVideoAnnounce(byActor, videoShare, video, t) 22 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
20 23
21 logger.info('Creating job to send announce %s.', videoShare.url) 24 logger.info('Creating job to send announce %s.', videoShare.url)
22 25
23 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
24 const followersException = [ byActor ] 26 const followersException = [ byActor ]
25 27 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException)
26 return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException)
27} 28}
28 29
29function announceActivityData (url: string, byActor: ActorModel, object: string, audience?: ActivityAudience): ActivityAnnounce { 30function buildAnnounceActivity (url: string, byActor: ActorModel, object: string, audience?: ActivityAudience): ActivityAnnounce {
30 if (!audience) audience = getAudience(byActor) 31 if (!audience) audience = getAudience(byActor)
31 32
32 return { 33 return audiencify({
33 type: 'Announce', 34 type: 'Announce' as 'Announce',
34 to: audience.to,
35 cc: audience.cc,
36 id: url, 35 id: url,
37 actor: byActor.url, 36 actor: byActor.url,
38 object 37 object
39 } 38 }, audience)
40} 39}
41 40
42// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
43 42
44export { 43export {
45 sendVideoAnnounce, 44 sendVideoAnnounce,
46 announceActivityData, 45 buildAnnounceActivity,
47 buildVideoAnnounce 46 buildAnnounceWithVideoAudience
48} 47}
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index fc76cdd8a..6f89b1a22 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -17,6 +17,7 @@ import {
17 getVideoCommentAudience 17 getVideoCommentAudience
18} from '../audience' 18} from '../audience'
19import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
20import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
20 21
21async function sendCreateVideo (video: VideoModel, t: Transaction) { 22async function sendCreateVideo (video: VideoModel, t: Transaction) {
22 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 23 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
@@ -27,12 +28,12 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) {
27 const videoObject = video.toActivityPubObject() 28 const videoObject = video.toActivityPubObject()
28 29
29 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) 30 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
30 const data = createActivityData(video.url, byActor, videoObject, audience) 31 const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience)
31 32
32 return broadcastToFollowers(data, byActor, [ byActor ], t) 33 return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
33} 34}
34 35
35async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { 36async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel) {
36 if (!video.VideoChannel.Account.Actor.serverId) return // Local 37 if (!video.VideoChannel.Account.Actor.serverId) return // Local
37 38
38 const url = getVideoAbuseActivityPubUrl(videoAbuse) 39 const url = getVideoAbuseActivityPubUrl(videoAbuse)
@@ -40,9 +41,23 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
40 logger.info('Creating job to send video abuse %s.', url) 41 logger.info('Creating job to send video abuse %s.', url)
41 42
42 const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } 43 const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
43 const data = createActivityData(url, byActor, videoAbuse.toActivityPubObject(), audience) 44 const createActivity = buildCreateActivity(url, byActor, videoAbuse.toActivityPubObject(), audience)
44 45
45 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) 46 return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
47}
48
49async function sendCreateCacheFile (byActor: ActorModel, fileRedundancy: VideoRedundancyModel) {
50 logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
51
52 const redundancyObject = fileRedundancy.toActivityPubObject()
53
54 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(fileRedundancy.VideoFile.Video.id)
55 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, undefined)
56
57 const audience = getVideoAudience(video, actorsInvolvedInVideo)
58 const createActivity = buildCreateActivity(fileRedundancy.url, byActor, redundancyObject, audience)
59
60 return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
46} 61}
47 62
48async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { 63async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) {
@@ -66,73 +81,73 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio
66 audience = getObjectFollowersAudience(actorsInvolvedInComment.concat(parentsCommentActors)) 81 audience = getObjectFollowersAudience(actorsInvolvedInComment.concat(parentsCommentActors))
67 } 82 }
68 83
69 const data = createActivityData(comment.url, byActor, commentObject, audience) 84 const createActivity = buildCreateActivity(comment.url, byActor, commentObject, audience)
70 85
71 // This was a reply, send it to the parent actors 86 // This was a reply, send it to the parent actors
72 const actorsException = [ byActor ] 87 const actorsException = [ byActor ]
73 await broadcastToActors(data, byActor, parentsCommentActors, actorsException) 88 await broadcastToActors(createActivity, byActor, parentsCommentActors, actorsException)
74 89
75 // Broadcast to our followers 90 // Broadcast to our followers
76 await broadcastToFollowers(data, byActor, [ byActor ], t) 91 await broadcastToFollowers(createActivity, byActor, [ byActor ], t)
77 92
78 // Send to actors involved in the comment 93 // Send to actors involved in the comment
79 if (isOrigin) return broadcastToFollowers(data, byActor, actorsInvolvedInComment, t, actorsException) 94 if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException)
80 95
81 // Send to origin 96 // Send to origin
82 return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl) 97 return unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)
83} 98}
84 99
85async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transaction) { 100async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transaction) {
86 logger.info('Creating job to send view of %s.', video.url) 101 logger.info('Creating job to send view of %s.', video.url)
87 102
88 const url = getVideoViewActivityPubUrl(byActor, video) 103 const url = getVideoViewActivityPubUrl(byActor, video)
89 const viewActivityData = createViewActivityData(byActor, video) 104 const viewActivity = buildViewActivity(byActor, video)
90 105
91 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) 106 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
92 107
93 // Send to origin 108 // Send to origin
94 if (video.isOwned() === false) { 109 if (video.isOwned() === false) {
95 const audience = getVideoAudience(video, actorsInvolvedInVideo) 110 const audience = getVideoAudience(video, actorsInvolvedInVideo)
96 const data = createActivityData(url, byActor, viewActivityData, audience) 111 const createActivity = buildCreateActivity(url, byActor, viewActivity, audience)
97 112
98 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) 113 return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
99 } 114 }
100 115
101 // Send to followers 116 // Send to followers
102 const audience = getObjectFollowersAudience(actorsInvolvedInVideo) 117 const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
103 const data = createActivityData(url, byActor, viewActivityData, audience) 118 const createActivity = buildCreateActivity(url, byActor, viewActivity, audience)
104 119
105 // Use the server actor to send the view 120 // Use the server actor to send the view
106 const serverActor = await getServerActor() 121 const serverActor = await getServerActor()
107 const actorsException = [ byActor ] 122 const actorsException = [ byActor ]
108 return broadcastToFollowers(data, serverActor, actorsInvolvedInVideo, t, actorsException) 123 return broadcastToFollowers(createActivity, serverActor, actorsInvolvedInVideo, t, actorsException)
109} 124}
110 125
111async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { 126async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
112 logger.info('Creating job to dislike %s.', video.url) 127 logger.info('Creating job to dislike %s.', video.url)
113 128
114 const url = getVideoDislikeActivityPubUrl(byActor, video) 129 const url = getVideoDislikeActivityPubUrl(byActor, video)
115 const dislikeActivityData = createDislikeActivityData(byActor, video) 130 const dislikeActivity = buildDislikeActivity(byActor, video)
116 131
117 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) 132 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
118 133
119 // Send to origin 134 // Send to origin
120 if (video.isOwned() === false) { 135 if (video.isOwned() === false) {
121 const audience = getVideoAudience(video, actorsInvolvedInVideo) 136 const audience = getVideoAudience(video, actorsInvolvedInVideo)
122 const data = createActivityData(url, byActor, dislikeActivityData, audience) 137 const createActivity = buildCreateActivity(url, byActor, dislikeActivity, audience)
123 138
124 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) 139 return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
125 } 140 }
126 141
127 // Send to followers 142 // Send to followers
128 const audience = getObjectFollowersAudience(actorsInvolvedInVideo) 143 const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
129 const data = createActivityData(url, byActor, dislikeActivityData, audience) 144 const createActivity = buildCreateActivity(url, byActor, dislikeActivity, audience)
130 145
131 const actorsException = [ byActor ] 146 const actorsException = [ byActor ]
132 return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, actorsException) 147 return broadcastToFollowers(createActivity, byActor, actorsInvolvedInVideo, t, actorsException)
133} 148}
134 149
135function createActivityData (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { 150function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate {
136 if (!audience) audience = getAudience(byActor) 151 if (!audience) audience = getAudience(byActor)
137 152
138 return audiencify( 153 return audiencify(
@@ -146,7 +161,7 @@ function createActivityData (url: string, byActor: ActorModel, object: any, audi
146 ) 161 )
147} 162}
148 163
149function createDislikeActivityData (byActor: ActorModel, video: VideoModel) { 164function buildDislikeActivity (byActor: ActorModel, video: VideoModel) {
150 return { 165 return {
151 type: 'Dislike', 166 type: 'Dislike',
152 actor: byActor.url, 167 actor: byActor.url,
@@ -154,7 +169,7 @@ function createDislikeActivityData (byActor: ActorModel, video: VideoModel) {
154 } 169 }
155} 170}
156 171
157function createViewActivityData (byActor: ActorModel, video: VideoModel) { 172function buildViewActivity (byActor: ActorModel, video: VideoModel) {
158 return { 173 return {
159 type: 'View', 174 type: 'View',
160 actor: byActor.url, 175 actor: byActor.url,
@@ -167,9 +182,10 @@ function createViewActivityData (byActor: ActorModel, video: VideoModel) {
167export { 182export {
168 sendCreateVideo, 183 sendCreateVideo,
169 sendVideoAbuse, 184 sendVideoAbuse,
170 createActivityData, 185 buildCreateActivity,
171 sendCreateView, 186 sendCreateView,
172 sendCreateDislike, 187 sendCreateDislike,
173 createDislikeActivityData, 188 buildDislikeActivity,
174 sendCreateVideoComment 189 sendCreateVideoComment,
190 sendCreateCacheFile
175} 191}
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 3d1dfb699..479182543 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -15,24 +15,23 @@ async function sendDeleteVideo (video: VideoModel, t: Transaction) {
15 const url = getDeleteActivityPubUrl(video.url) 15 const url = getDeleteActivityPubUrl(video.url)
16 const byActor = video.VideoChannel.Account.Actor 16 const byActor = video.VideoChannel.Account.Actor
17 17
18 const data = deleteActivityData(url, video.url, byActor) 18 const activity = buildDeleteActivity(url, video.url, byActor)
19 19
20 const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t) 20 const actorsInvolved = await getActorsInvolvedInVideo(video, t)
21 actorsInvolved.push(byActor)
22 21
23 return broadcastToFollowers(data, byActor, actorsInvolved, t) 22 return broadcastToFollowers(activity, byActor, actorsInvolved, t)
24} 23}
25 24
26async function sendDeleteActor (byActor: ActorModel, t: Transaction) { 25async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
27 logger.info('Creating job to broadcast delete of actor %s.', byActor.url) 26 logger.info('Creating job to broadcast delete of actor %s.', byActor.url)
28 27
29 const url = getDeleteActivityPubUrl(byActor.url) 28 const url = getDeleteActivityPubUrl(byActor.url)
30 const data = deleteActivityData(url, byActor.url, byActor) 29 const activity = buildDeleteActivity(url, byActor.url, byActor)
31 30
32 const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t) 31 const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t)
33 actorsInvolved.push(byActor) 32 actorsInvolved.push(byActor)
34 33
35 return broadcastToFollowers(data, byActor, actorsInvolved, t) 34 return broadcastToFollowers(activity, byActor, actorsInvolved, t)
36} 35}
37 36
38async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { 37async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) {
@@ -45,23 +44,23 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans
45 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t) 44 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t)
46 45
47 const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t) 46 const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t)
48 actorsInvolvedInComment.push(byActor) 47 actorsInvolvedInComment.push(byActor) // Add the actor that commented the video
49 48
50 const audience = getVideoCommentAudience(videoComment, threadParentComments, actorsInvolvedInComment, isVideoOrigin) 49 const audience = getVideoCommentAudience(videoComment, threadParentComments, actorsInvolvedInComment, isVideoOrigin)
51 const data = deleteActivityData(url, videoComment.url, byActor, audience) 50 const activity = buildDeleteActivity(url, videoComment.url, byActor, audience)
52 51
53 // This was a reply, send it to the parent actors 52 // This was a reply, send it to the parent actors
54 const actorsException = [ byActor ] 53 const actorsException = [ byActor ]
55 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), actorsException) 54 await broadcastToActors(activity, byActor, threadParentComments.map(c => c.Account.Actor), actorsException)
56 55
57 // Broadcast to our followers 56 // Broadcast to our followers
58 await broadcastToFollowers(data, byActor, [ byActor ], t) 57 await broadcastToFollowers(activity, byActor, [ byActor ], t)
59 58
60 // Send to actors involved in the comment 59 // Send to actors involved in the comment
61 if (isVideoOrigin) return broadcastToFollowers(data, byActor, actorsInvolvedInComment, t, actorsException) 60 if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException)
62 61
63 // Send to origin 62 // Send to origin
64 return unicastTo(data, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl) 63 return unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)
65} 64}
66 65
67// --------------------------------------------------------------------------- 66// ---------------------------------------------------------------------------
@@ -74,7 +73,7 @@ export {
74 73
75// --------------------------------------------------------------------------- 74// ---------------------------------------------------------------------------
76 75
77function deleteActivityData (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { 76function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete {
78 const activity = { 77 const activity = {
79 type: 'Delete' as 'Delete', 78 type: 'Delete' as 'Delete',
80 id: url, 79 id: url,
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index 46d08c17b..170b46b48 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -15,12 +15,12 @@ function sendFollow (actorFollow: ActorFollowModel) {
15 logger.info('Creating job to send follow request to %s.', following.url) 15 logger.info('Creating job to send follow request to %s.', following.url)
16 16
17 const url = getActorFollowActivityPubUrl(actorFollow) 17 const url = getActorFollowActivityPubUrl(actorFollow)
18 const data = followActivityData(url, me, following) 18 const data = buildFollowActivity(url, me, following)
19 19
20 return unicastTo(data, me, following.inboxUrl) 20 return unicastTo(data, me, following.inboxUrl)
21} 21}
22 22
23function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow { 23function buildFollowActivity (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow {
24 return { 24 return {
25 type: 'Follow', 25 type: 'Follow',
26 id: url, 26 id: url,
@@ -33,5 +33,5 @@ function followActivityData (url: string, byActor: ActorModel, targetActor: Acto
33 33
34export { 34export {
35 sendFollow, 35 sendFollow,
36 followActivityData 36 buildFollowActivity
37} 37}
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 83225f5df..a5408ac6a 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -17,20 +17,20 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction)
17 // Send to origin 17 // Send to origin
18 if (video.isOwned() === false) { 18 if (video.isOwned() === false) {
19 const audience = getVideoAudience(video, accountsInvolvedInVideo) 19 const audience = getVideoAudience(video, accountsInvolvedInVideo)
20 const data = likeActivityData(url, byActor, video, audience) 20 const data = buildLikeActivity(url, byActor, video, audience)
21 21
22 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) 22 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
23 } 23 }
24 24
25 // Send to followers 25 // Send to followers
26 const audience = getObjectFollowersAudience(accountsInvolvedInVideo) 26 const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
27 const data = likeActivityData(url, byActor, video, audience) 27 const activity = buildLikeActivity(url, byActor, video, audience)
28 28
29 const followersException = [ byActor ] 29 const followersException = [ byActor ]
30 return broadcastToFollowers(data, byActor, accountsInvolvedInVideo, t, followersException) 30 return broadcastToFollowers(activity, byActor, accountsInvolvedInVideo, t, followersException)
31} 31}
32 32
33function likeActivityData (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { 33function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike {
34 if (!audience) audience = getAudience(byActor) 34 if (!audience) audience = getAudience(byActor)
35 35
36 return audiencify( 36 return audiencify(
@@ -48,5 +48,5 @@ function likeActivityData (url: string, byActor: ActorModel, video: VideoModel,
48 48
49export { 49export {
50 sendLike, 50 sendLike,
51 likeActivityData 51 buildLikeActivity
52} 52}
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 30d0fd98b..a50673c79 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -13,12 +13,13 @@ import { VideoModel } from '../../../models/video/video'
13import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 13import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
14import { broadcastToFollowers, unicastTo } from './utils' 14import { broadcastToFollowers, unicastTo } from './utils'
15import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience' 15import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience'
16import { createActivityData, createDislikeActivityData } from './send-create' 16import { buildCreateActivity, buildDislikeActivity } from './send-create'
17import { followActivityData } from './send-follow' 17import { buildFollowActivity } from './send-follow'
18import { likeActivityData } from './send-like' 18import { buildLikeActivity } from './send-like'
19import { VideoShareModel } from '../../../models/video/video-share' 19import { VideoShareModel } from '../../../models/video/video-share'
20import { buildVideoAnnounce } from './send-announce' 20import { buildAnnounceWithVideoAudience } from './send-announce'
21import { logger } from '../../../helpers/logger' 21import { logger } from '../../../helpers/logger'
22import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
22 23
23async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { 24async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
24 const me = actorFollow.ActorFollower 25 const me = actorFollow.ActorFollower
@@ -32,10 +33,10 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
32 const followUrl = getActorFollowActivityPubUrl(actorFollow) 33 const followUrl = getActorFollowActivityPubUrl(actorFollow)
33 const undoUrl = getUndoActivityPubUrl(followUrl) 34 const undoUrl = getUndoActivityPubUrl(followUrl)
34 35
35 const object = followActivityData(followUrl, me, following) 36 const followActivity = buildFollowActivity(followUrl, me, following)
36 const data = undoActivityData(undoUrl, me, object) 37 const undoActivity = undoActivityData(undoUrl, me, followActivity)
37 38
38 return unicastTo(data, me, following.inboxUrl) 39 return unicastTo(undoActivity, me, following.inboxUrl)
39} 40}
40 41
41async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { 42async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -45,21 +46,21 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact
45 const undoUrl = getUndoActivityPubUrl(likeUrl) 46 const undoUrl = getUndoActivityPubUrl(likeUrl)
46 47
47 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) 48 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
48 const object = likeActivityData(likeUrl, byActor, video) 49 const likeActivity = buildLikeActivity(likeUrl, byActor, video)
49 50
50 // Send to origin 51 // Send to origin
51 if (video.isOwned() === false) { 52 if (video.isOwned() === false) {
52 const audience = getVideoAudience(video, actorsInvolvedInVideo) 53 const audience = getVideoAudience(video, actorsInvolvedInVideo)
53 const data = undoActivityData(undoUrl, byActor, object, audience) 54 const undoActivity = undoActivityData(undoUrl, byActor, likeActivity, audience)
54 55
55 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) 56 return unicastTo(undoActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
56 } 57 }
57 58
58 const audience = getObjectFollowersAudience(actorsInvolvedInVideo) 59 const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
59 const data = undoActivityData(undoUrl, byActor, object, audience) 60 const undoActivity = undoActivityData(undoUrl, byActor, likeActivity, audience)
60 61
61 const followersException = [ byActor ] 62 const followersException = [ byActor ]
62 return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) 63 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
63} 64}
64 65
65async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { 66async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -69,20 +70,20 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
69 const undoUrl = getUndoActivityPubUrl(dislikeUrl) 70 const undoUrl = getUndoActivityPubUrl(dislikeUrl)
70 71
71 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) 72 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
72 const dislikeActivity = createDislikeActivityData(byActor, video) 73 const dislikeActivity = buildDislikeActivity(byActor, video)
73 const object = createActivityData(dislikeUrl, byActor, dislikeActivity) 74 const createDislikeActivity = buildCreateActivity(dislikeUrl, byActor, dislikeActivity)
74 75
75 if (video.isOwned() === false) { 76 if (video.isOwned() === false) {
76 const audience = getVideoAudience(video, actorsInvolvedInVideo) 77 const audience = getVideoAudience(video, actorsInvolvedInVideo)
77 const data = undoActivityData(undoUrl, byActor, object, audience) 78 const undoActivity = undoActivityData(undoUrl, byActor, createDislikeActivity, audience)
78 79
79 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) 80 return unicastTo(undoActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
80 } 81 }
81 82
82 const data = undoActivityData(undoUrl, byActor, object) 83 const undoActivity = undoActivityData(undoUrl, byActor, createDislikeActivity)
83 84
84 const followersException = [ byActor ] 85 const followersException = [ byActor ]
85 return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) 86 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
86} 87}
87 88
88async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { 89async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
@@ -90,12 +91,27 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode
90 91
91 const undoUrl = getUndoActivityPubUrl(videoShare.url) 92 const undoUrl = getUndoActivityPubUrl(videoShare.url)
92 93
93 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) 94 const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
94 const object = await buildVideoAnnounce(byActor, videoShare, video, t) 95 const undoActivity = undoActivityData(undoUrl, byActor, announceActivity)
95 const data = undoActivityData(undoUrl, byActor, object)
96 96
97 const followersException = [ byActor ] 97 const followersException = [ byActor ]
98 return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) 98 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
99}
100
101async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) {
102 logger.info('Creating job to undo cache file %s.', redundancyModel.url)
103
104 const undoUrl = getUndoActivityPubUrl(redundancyModel.url)
105
106 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.VideoFile.Video.id)
107 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
108
109 const audience = getVideoAudience(video, actorsInvolvedInVideo)
110 const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject())
111
112 const undoActivity = undoActivityData(undoUrl, byActor, createActivity, audience)
113
114 return unicastTo(undoActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
99} 115}
100 116
101// --------------------------------------------------------------------------- 117// ---------------------------------------------------------------------------
@@ -104,7 +120,8 @@ export {
104 sendUndoFollow, 120 sendUndoFollow,
105 sendUndoLike, 121 sendUndoLike,
106 sendUndoDislike, 122 sendUndoDislike,
107 sendUndoAnnounce 123 sendUndoAnnounce,
124 sendUndoCacheFile
108} 125}
109 126
110// --------------------------------------------------------------------------- 127// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 6f1d80898..605473338 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -7,11 +7,11 @@ import { VideoModel } from '../../../models/video/video'
7import { VideoChannelModel } from '../../../models/video/video-channel' 7import { VideoChannelModel } from '../../../models/video/video-channel'
8import { VideoShareModel } from '../../../models/video/video-share' 8import { VideoShareModel } from '../../../models/video/video-share'
9import { getUpdateActivityPubUrl } from '../url' 9import { getUpdateActivityPubUrl } from '../url'
10import { broadcastToFollowers } from './utils' 10import { broadcastToFollowers, unicastTo } from './utils'
11import { audiencify, getAudience } from '../audience' 11import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience'
12import { logger } from '../../../helpers/logger' 12import { logger } from '../../../helpers/logger'
13import { videoFeedsValidator } from '../../../middlewares/validators'
14import { VideoCaptionModel } from '../../../models/video/video-caption' 13import { VideoCaptionModel } from '../../../models/video/video-caption'
14import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
15 15
16async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) { 16async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
17 logger.info('Creating job to update video %s.', video.url) 17 logger.info('Creating job to update video %s.', video.url)
@@ -26,12 +26,12 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct
26 const videoObject = video.toActivityPubObject() 26 const videoObject = video.toActivityPubObject()
27 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) 27 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
28 28
29 const data = updateActivityData(url, byActor, videoObject, audience) 29 const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience)
30 30
31 const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t) 31 const actorsInvolved = await getActorsInvolvedInVideo(video, t)
32 actorsInvolved.push(byActor) 32 if (overrodeByActor) actorsInvolved.push(overrodeByActor)
33 33
34 return broadcastToFollowers(data, byActor, actorsInvolved, t) 34 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
35} 35}
36 36
37async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { 37async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) {
@@ -42,7 +42,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
42 const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) 42 const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString())
43 const accountOrChannelObject = accountOrChannel.toActivityPubObject() 43 const accountOrChannelObject = accountOrChannel.toActivityPubObject()
44 const audience = getAudience(byActor) 44 const audience = getAudience(byActor)
45 const data = updateActivityData(url, byActor, accountOrChannelObject, audience) 45 const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience)
46 46
47 let actorsInvolved: ActorModel[] 47 let actorsInvolved: ActorModel[]
48 if (accountOrChannel instanceof AccountModel) { 48 if (accountOrChannel instanceof AccountModel) {
@@ -55,19 +55,35 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
55 55
56 actorsInvolved.push(byActor) 56 actorsInvolved.push(byActor)
57 57
58 return broadcastToFollowers(data, byActor, actorsInvolved, t) 58 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
59}
60
61async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) {
62 logger.info('Creating job to update cache file %s.', redundancyModel.url)
63
64 const url = getUpdateActivityPubUrl(redundancyModel.url, redundancyModel.updatedAt.toISOString())
65 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.VideoFile.Video.id)
66
67 const redundancyObject = redundancyModel.toActivityPubObject()
68
69 const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, undefined)
70 const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
71
72 const updateActivity = buildUpdateActivity(url, byActor, redundancyObject, audience)
73 return unicastTo(updateActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
59} 74}
60 75
61// --------------------------------------------------------------------------- 76// ---------------------------------------------------------------------------
62 77
63export { 78export {
64 sendUpdateActor, 79 sendUpdateActor,
65 sendUpdateVideo 80 sendUpdateVideo,
81 sendUpdateCacheFile
66} 82}
67 83
68// --------------------------------------------------------------------------- 84// ---------------------------------------------------------------------------
69 85
70function updateActivityData (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { 86function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate {
71 if (!audience) audience = getAudience(byActor) 87 if (!audience) audience = getAudience(byActor)
72 88
73 return audiencify( 89 return audiencify(
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts
index da437292e..c20c15633 100644
--- a/server/lib/activitypub/send/utils.ts
+++ b/server/lib/activitypub/send/utils.ts
@@ -59,11 +59,11 @@ async function forwardActivity (
59async function broadcastToFollowers ( 59async function broadcastToFollowers (
60 data: any, 60 data: any,
61 byActor: ActorModel, 61 byActor: ActorModel,
62 toActorFollowers: ActorModel[], 62 toFollowersOf: ActorModel[],
63 t: Transaction, 63 t: Transaction,
64 actorsException: ActorModel[] = [] 64 actorsException: ActorModel[] = []
65) { 65) {
66 const uris = await computeFollowerUris(toActorFollowers, actorsException, t) 66 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
67 return broadcastTo(uris, data, byActor) 67 return broadcastTo(uris, data, byActor)
68} 68}
69 69
@@ -115,8 +115,8 @@ export {
115 115
116// --------------------------------------------------------------------------- 116// ---------------------------------------------------------------------------
117 117
118async function computeFollowerUris (toActorFollower: ActorModel[], actorsException: ActorModel[], t: Transaction) { 118async function computeFollowerUris (toFollowersOf: ActorModel[], actorsException: ActorModel[], t: Transaction) {
119 const toActorFollowerIds = toActorFollower.map(a => a.id) 119 const toActorFollowerIds = toFollowersOf.map(a => a.id)
120 120
121 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) 121 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
122 const sharedInboxesException = await buildSharedInboxesException(actorsException) 122 const sharedInboxesException = await buildSharedInboxesException(actorsException)
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index 262463310..2e7c56955 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -4,11 +4,18 @@ import { ActorFollowModel } from '../../models/activitypub/actor-follow'
4import { VideoModel } from '../../models/video/video' 4import { VideoModel } from '../../models/video/video'
5import { VideoAbuseModel } from '../../models/video/video-abuse' 5import { VideoAbuseModel } from '../../models/video/video-abuse'
6import { VideoCommentModel } from '../../models/video/video-comment' 6import { VideoCommentModel } from '../../models/video/video-comment'
7import { VideoFileModel } from '../../models/video/video-file'
7 8
8function getVideoActivityPubUrl (video: VideoModel) { 9function getVideoActivityPubUrl (video: VideoModel) {
9 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid 10 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
10} 11}
11 12
13function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) {
14 const suffixFPS = videoFile.fps ? '-' + videoFile.fps : ''
15
16 return `${CONFIG.WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}`
17}
18
12function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { 19function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) {
13 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id 20 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id
14} 21}
@@ -101,5 +108,6 @@ export {
101 getVideoSharesActivityPubUrl, 108 getVideoSharesActivityPubUrl,
102 getVideoCommentsActivityPubUrl, 109 getVideoCommentsActivityPubUrl,
103 getVideoLikesActivityPubUrl, 110 getVideoLikesActivityPubUrl,
104 getVideoDislikesActivityPubUrl 111 getVideoDislikesActivityPubUrl,
112 getVideoCacheFileActivityPubUrl
105} 113}
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 6c2095897..783f78d3e 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -3,12 +3,12 @@ import * as sequelize from 'sequelize'
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import { join } from 'path' 4import { join } from 'path'
5import * as request from 'request' 5import * as request from 'request'
6import { ActivityIconObject, VideoState } from '../../../shared/index' 6import { ActivityIconObject, ActivityVideoUrlObject, VideoState, ActivityUrlObject } from '../../../shared/index'
7import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 7import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
8import { VideoPrivacy } from '../../../shared/models/videos' 8import { VideoPrivacy } from '../../../shared/models/videos'
9import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' 9import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos'
10import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 10import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
11import { resetSequelizeInstance, retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils' 11import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
12import { logger } from '../../helpers/logger' 12import { logger } from '../../helpers/logger'
13import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 13import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
14import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers' 14import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers'
@@ -17,7 +17,7 @@ import { TagModel } from '../../models/video/tag'
17import { VideoModel } from '../../models/video/video' 17import { VideoModel } from '../../models/video/video'
18import { VideoChannelModel } from '../../models/video/video-channel' 18import { VideoChannelModel } from '../../models/video/video-channel'
19import { VideoFileModel } from '../../models/video/video-file' 19import { VideoFileModel } from '../../models/video/video-file'
20import { getOrCreateActorAndServerAndModel, updateActorAvatarInstance } from './actor' 20import { getOrCreateActorAndServerAndModel } from './actor'
21import { addVideoComments } from './video-comments' 21import { addVideoComments } from './video-comments'
22import { crawlCollectionPage } from './crawl' 22import { crawlCollectionPage } from './crawl'
23import { sendCreateVideo, sendUpdateVideo } from './send' 23import { sendCreateVideo, sendUpdateVideo } from './send'
@@ -25,7 +25,6 @@ import { isArray } from '../../helpers/custom-validators/misc'
25import { VideoCaptionModel } from '../../models/video/video-caption' 25import { VideoCaptionModel } from '../../models/video/video-caption'
26import { JobQueue } from '../job-queue' 26import { JobQueue } from '../job-queue'
27import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' 27import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
28import { getUrlFromWebfinger } from '../../helpers/webfinger'
29import { createRates } from './video-rates' 28import { createRates } from './video-rates'
30import { addVideoShares, shareVideoByServerAndChannel } from './share' 29import { addVideoShares, shareVideoByServerAndChannel } from './share'
31import { AccountModel } from '../../models/account/account' 30import { AccountModel } from '../../models/account/account'
@@ -137,10 +136,7 @@ async function videoActivityObjectToDBAttributes (
137} 136}
138 137
139function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObject: VideoTorrentObject) { 138function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObject: VideoTorrentObject) {
140 const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT) 139 const fileUrls = videoObject.url.filter(u => isActivityVideoUrlObject(u)) as ActivityVideoUrlObject[]
141 const fileUrls = videoObject.url.filter(u => {
142 return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/')
143 })
144 140
145 if (fileUrls.length === 0) { 141 if (fileUrls.length === 0) {
146 throw new Error('Cannot find video files for ' + videoCreated.url) 142 throw new Error('Cannot find video files for ' + videoCreated.url)
@@ -331,8 +327,8 @@ async function refreshVideoIfNeeded (video: VideoModel): Promise<VideoModel> {
331 327
332 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 328 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
333 const account = await AccountModel.load(channelActor.VideoChannel.accountId) 329 const account = await AccountModel.load(channelActor.VideoChannel.accountId)
334 return updateVideoFromAP(video, videoObject, account.Actor, channelActor)
335 330
331 return updateVideoFromAP(video, videoObject, account, channelActor.VideoChannel)
336 } catch (err) { 332 } catch (err) {
337 logger.warn('Cannot refresh video.', { err }) 333 logger.warn('Cannot refresh video.', { err })
338 return video 334 return video
@@ -342,8 +338,8 @@ async function refreshVideoIfNeeded (video: VideoModel): Promise<VideoModel> {
342async function updateVideoFromAP ( 338async function updateVideoFromAP (
343 video: VideoModel, 339 video: VideoModel,
344 videoObject: VideoTorrentObject, 340 videoObject: VideoTorrentObject,
345 accountActor: ActorModel, 341 account: AccountModel,
346 channelActor: ActorModel, 342 channel: VideoChannelModel,
347 overrideTo?: string[] 343 overrideTo?: string[]
348) { 344) {
349 logger.debug('Updating remote video "%s".', videoObject.uuid) 345 logger.debug('Updating remote video "%s".', videoObject.uuid)
@@ -359,12 +355,12 @@ async function updateVideoFromAP (
359 355
360 // Check actor has the right to update the video 356 // Check actor has the right to update the video
361 const videoChannel = video.VideoChannel 357 const videoChannel = video.VideoChannel
362 if (videoChannel.Account.Actor.id !== accountActor.id) { 358 if (videoChannel.Account.id !== account.id) {
363 throw new Error('Account ' + accountActor.url + ' does not own video channel ' + videoChannel.Actor.url) 359 throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url)
364 } 360 }
365 361
366 const to = overrideTo ? overrideTo : videoObject.to 362 const to = overrideTo ? overrideTo : videoObject.to
367 const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, to) 363 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to)
368 video.set('name', videoData.name) 364 video.set('name', videoData.name)
369 video.set('uuid', videoData.uuid) 365 video.set('uuid', videoData.uuid)
370 video.set('url', videoData.url) 366 video.set('url', videoData.url)
@@ -444,3 +440,11 @@ export {
444 addVideoShares, 440 addVideoShares,
445 createRates 441 createRates
446} 442}
443
444// ---------------------------------------------------------------------------
445
446function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject {
447 const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
448
449 return mimeTypes.indexOf(url.mimeType) !== -1 && url.mimeType.startsWith('video/')
450}
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts
new file mode 100644
index 000000000..78221cc3d
--- /dev/null
+++ b/server/lib/redundancy.ts
@@ -0,0 +1,18 @@
1import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
2import { sendUndoCacheFile } from './activitypub/send'
3import { Transaction } from 'sequelize'
4import { getServerActor } from '../helpers/utils'
5
6async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) {
7 const serverActor = await getServerActor()
8
9 await sendUndoCacheFile(serverActor, videoRedundancy, t)
10
11 await videoRedundancy.destroy({ transaction: t })
12}
13
14// ---------------------------------------------------------------------------
15
16export {
17 removeVideoRedundancy
18}
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts
new file mode 100644
index 000000000..ee9ba1766
--- /dev/null
+++ b/server/lib/schedulers/videos-redundancy-scheduler.ts
@@ -0,0 +1,161 @@
1import { AbstractScheduler } from './abstract-scheduler'
2import { CONFIG, JOB_TTL, REDUNDANCY, SCHEDULER_INTERVALS_MS } from '../../initializers'
3import { logger } from '../../helpers/logger'
4import { VideoRedundancyStrategy } from '../../../shared/models/redundancy'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6import { VideoFileModel } from '../../models/video/video-file'
7import { sortBy } from 'lodash'
8import { downloadWebTorrentVideo } from '../../helpers/webtorrent'
9import { join } from 'path'
10import { rename } from 'fs-extra'
11import { getServerActor } from '../../helpers/utils'
12import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send'
13import { VideoModel } from '../../models/video/video'
14import { getVideoCacheFileActivityPubUrl } from '../activitypub/url'
15import { removeVideoRedundancy } from '../redundancy'
16import { isTestInstance } from '../../helpers/core-utils'
17
18export class VideosRedundancyScheduler extends AbstractScheduler {
19
20 private static instance: AbstractScheduler
21 private executing = false
22
23 protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.videosRedundancy
24
25 private constructor () {
26 super()
27 }
28
29 async execute () {
30 if (this.executing) return
31
32 this.executing = true
33
34 for (const obj of CONFIG.REDUNDANCY.VIDEOS) {
35
36 try {
37 const videoToDuplicate = await this.findVideoToDuplicate(obj.strategy)
38 if (!videoToDuplicate) continue
39
40 const videoFiles = videoToDuplicate.VideoFiles
41 videoFiles.forEach(f => f.Video = videoToDuplicate)
42
43 const videosRedundancy = await VideoRedundancyModel.getVideoFiles(obj.strategy)
44 if (this.isTooHeavy(videosRedundancy, videoFiles, obj.size)) {
45 if (!isTestInstance()) logger.info('Video %s is too big for our cache, skipping.', videoToDuplicate.url)
46 continue
47 }
48
49 logger.info('Will duplicate video %s in redundancy scheduler "%s".', videoToDuplicate.url, obj.strategy)
50
51 await this.createVideoRedundancy(obj.strategy, videoFiles)
52 } catch (err) {
53 logger.error('Cannot run videos redundancy %s.', obj.strategy, { err })
54 }
55 }
56
57 const expired = await VideoRedundancyModel.listAllExpired()
58
59 for (const m of expired) {
60 logger.info('Removing expired video %s from our redundancy system.', this.buildEntryLogId(m))
61
62 try {
63 await m.destroy()
64 } catch (err) {
65 logger.error('Cannot remove %s video from our redundancy system.', this.buildEntryLogId(m))
66 }
67 }
68
69 this.executing = false
70 }
71
72 static get Instance () {
73 return this.instance || (this.instance = new this())
74 }
75
76 private findVideoToDuplicate (strategy: VideoRedundancyStrategy) {
77 if (strategy === 'most-views') return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR)
78 }
79
80 private async createVideoRedundancy (strategy: VideoRedundancyStrategy, filesToDuplicate: VideoFileModel[]) {
81 const serverActor = await getServerActor()
82
83 for (const file of filesToDuplicate) {
84 const existing = await VideoRedundancyModel.loadByFileId(file.id)
85 if (existing) {
86 logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', file.Video.url, file.resolution, strategy)
87
88 existing.expiresOn = this.buildNewExpiration()
89 await existing.save()
90
91 await sendUpdateCacheFile(serverActor, existing)
92 continue
93 }
94
95 // We need more attributes and check if the video still exists
96 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(file.Video.id)
97 if (!video) continue
98
99 logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy)
100
101 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
102 const magnetUri = video.generateMagnetUri(file, baseUrlHttp, baseUrlWs)
103
104 const tmpPath = await downloadWebTorrentVideo({ magnetUri }, JOB_TTL['video-import'])
105
106 const destPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(file))
107 await rename(tmpPath, destPath)
108
109 const createdModel = await VideoRedundancyModel.create({
110 expiresOn: new Date(Date.now() + REDUNDANCY.VIDEOS.EXPIRES_AFTER_MS),
111 url: getVideoCacheFileActivityPubUrl(file),
112 fileUrl: video.getVideoFileUrl(file, CONFIG.WEBSERVER.URL),
113 strategy,
114 videoFileId: file.id,
115 actorId: serverActor.id
116 })
117 createdModel.VideoFile = file
118
119 await sendCreateCacheFile(serverActor, createdModel)
120 }
121 }
122
123 // Unused, but could be useful in the future, with a custom strategy
124 private async purgeVideosIfNeeded (videosRedundancy: VideoRedundancyModel[], filesToDuplicate: VideoFileModel[], maxSize: number) {
125 const sortedVideosRedundancy = sortBy(videosRedundancy, 'createdAt')
126
127 while (this.isTooHeavy(sortedVideosRedundancy, filesToDuplicate, maxSize)) {
128 const toDelete = sortedVideosRedundancy.shift()
129
130 const videoFile = toDelete.VideoFile
131 logger.info('Purging video %s (resolution %d) from our redundancy system.', videoFile.Video.url, videoFile.resolution)
132
133 await removeVideoRedundancy(toDelete, undefined)
134 }
135
136 return sortedVideosRedundancy
137 }
138
139 private isTooHeavy (videosRedundancy: VideoRedundancyModel[], filesToDuplicate: VideoFileModel[], maxSizeArg: number) {
140 const maxSize = maxSizeArg - this.getTotalFileSizes(filesToDuplicate)
141
142 const redundancyReducer = (previous: number, current: VideoRedundancyModel) => previous + current.VideoFile.size
143 const totalDuplicated = videosRedundancy.reduce(redundancyReducer, 0)
144
145 return totalDuplicated > maxSize
146 }
147
148 private buildNewExpiration () {
149 return new Date(Date.now() + REDUNDANCY.VIDEOS.EXPIRES_AFTER_MS)
150 }
151
152 private buildEntryLogId (object: VideoRedundancyModel) {
153 return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
154 }
155
156 private getTotalFileSizes (files: VideoFileModel[]) {
157 const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size
158
159 return files.reduce(fileReducer, 0)
160 }
161}