diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/actor.ts | 6 | ||||
-rw-r--r-- | server/lib/activitypub/cache-file.ts | 47 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-create.ts | 21 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-undo.ts | 44 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-update.ts | 34 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-accept.ts | 8 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-announce.ts | 33 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-create.ts | 68 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-delete.ts | 25 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-follow.ts | 6 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-like.ts | 10 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-undo.ts | 63 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-update.ts | 38 | ||||
-rw-r--r-- | server/lib/activitypub/send/utils.ts | 8 | ||||
-rw-r--r-- | server/lib/activitypub/url.ts | 10 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 32 | ||||
-rw-r--r-- | server/lib/redundancy.ts | 18 | ||||
-rw-r--r-- | server/lib/schedulers/videos-redundancy-scheduler.ts | 161 |
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 @@ | |||
1 | import { CacheFileObject } from '../../../shared/index' | ||
2 | import { VideoModel } from '../../models/video/video' | ||
3 | import { ActorModel } from '../../models/activitypub/actor' | ||
4 | import { sequelizeTypescript } from '../../initializers' | ||
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | ||
6 | |||
7 | function 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 | |||
26 | function 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 | |||
34 | function 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 | |||
43 | export { | ||
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 @@ | |||
1 | import { ActivityCreate, VideoAbuseState, VideoTorrentObject } from '../../../../shared' | 1 | import { ActivityCreate, CacheFileObject, VideoAbuseState, VideoTorrentObject } from '../../../../shared' |
2 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' | 2 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' |
3 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' | 3 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' |
4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
@@ -12,6 +12,7 @@ import { addVideoComment, resolveThread } from '../video-comments' | |||
12 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 12 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
13 | import { forwardActivity, forwardVideoRelatedActivity } from '../send/utils' | 13 | import { forwardActivity, forwardVideoRelatedActivity } from '../send/utils' |
14 | import { Redis } from '../../redis' | 14 | import { Redis } from '../../redis' |
15 | import { createCacheFile } from '../cache-file' | ||
15 | 16 | ||
16 | async function processCreateActivity (activity: ActivityCreate) { | 17 | async 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 | ||
103 | async 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 | |||
100 | async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) { | 117 | async 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 @@ | |||
1 | import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' | 1 | import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo, CacheFileObject } from '../../../../shared/models/activitypub' |
2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' | 2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' |
3 | import { getActorUrl } from '../../../helpers/activitypub' | 3 | import { getActorUrl } from '../../../helpers/activitypub' |
4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
@@ -11,6 +11,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | |||
11 | import { forwardVideoRelatedActivity } from '../send/utils' | 11 | import { forwardVideoRelatedActivity } from '../send/utils' |
12 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 12 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
13 | import { VideoShareModel } from '../../../models/video/video-share' | 13 | import { VideoShareModel } from '../../../models/video/video-share' |
14 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
14 | 15 | ||
15 | async function processUndoActivity (activity: ActivityUndo) { | 16 | async 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 | ||
102 | async 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 | |||
91 | function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) { | 125 | function 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 @@ | |||
1 | import { ActivityUpdate, VideoTorrentObject } from '../../../../shared/models/activitypub' | 1 | import { ActivityUpdate, CacheFileObject, VideoTorrentObject } from '../../../../shared/models/activitypub' |
2 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' | 2 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' |
3 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
@@ -7,8 +7,11 @@ import { AccountModel } from '../../../models/account/account' | |||
7 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/activitypub/actor' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | 8 | import { VideoChannelModel } from '../../../models/video/video-channel' |
9 | import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' | 9 | import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' |
10 | import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' | 10 | import { getOrCreateVideoAndAccountAndChannel, updateVideoFromAP, getOrCreateVideoChannelFromVideoObject } from '../videos' |
11 | import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' | 11 | import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' |
12 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' | ||
13 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
14 | import { createCacheFile, updateCacheFile } from '../cache-file' | ||
12 | 15 | ||
13 | async function processUpdateActivity (activity: ActivityUpdate) { | 16 | async 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 | |||
57 | async 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 | ||
48 | async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) { | 74 | async 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' | |||
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url' | 4 | import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url' |
5 | import { unicastTo } from './utils' | 5 | import { unicastTo } from './utils' |
6 | import { followActivityData } from './send-follow' | 6 | import { buildFollowActivity } from './send-follow' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | 8 | ||
9 | async function sendAccept (actorFollow: ActorFollowModel) { | 9 | async 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 | ||
37 | function acceptActivityData (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityAccept { | 37 | function 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' | |||
4 | import { VideoModel } from '../../../models/video/video' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { VideoShareModel } from '../../../models/video/video-share' | 5 | import { VideoShareModel } from '../../../models/video/video-share' |
6 | import { broadcastToFollowers } from './utils' | 6 | import { broadcastToFollowers } from './utils' |
7 | import { getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience' | 7 | import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience' |
8 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
9 | 9 | ||
10 | async function buildVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { | 10 | async 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 | ||
18 | async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { | 21 | async 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 | ||
29 | function announceActivityData (url: string, byActor: ActorModel, object: string, audience?: ActivityAudience): ActivityAnnounce { | 30 | function 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 | ||
44 | export { | 43 | export { |
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' |
19 | import { logger } from '../../../helpers/logger' | 19 | import { logger } from '../../../helpers/logger' |
20 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
20 | 21 | ||
21 | async function sendCreateVideo (video: VideoModel, t: Transaction) { | 22 | async 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 | ||
35 | async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { | 36 | async 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 | |||
49 | async 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 | ||
48 | async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { | 63 | async 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 | ||
85 | async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transaction) { | 100 | async 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 | ||
111 | async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 126 | async 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 | ||
135 | function createActivityData (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { | 150 | function 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 | ||
149 | function createDislikeActivityData (byActor: ActorModel, video: VideoModel) { | 164 | function 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 | ||
157 | function createViewActivityData (byActor: ActorModel, video: VideoModel) { | 172 | function 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) { | |||
167 | export { | 182 | export { |
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 | ||
26 | async function sendDeleteActor (byActor: ActorModel, t: Transaction) { | 25 | async 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 | ||
38 | async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { | 37 | async 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 | ||
77 | function deleteActivityData (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { | 76 | function 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 | ||
23 | function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow { | 23 | function 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 | ||
34 | export { | 34 | export { |
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 | ||
33 | function likeActivityData (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { | 33 | function 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 | ||
49 | export { | 49 | export { |
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' | |||
13 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | 13 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' |
14 | import { broadcastToFollowers, unicastTo } from './utils' | 14 | import { broadcastToFollowers, unicastTo } from './utils' |
15 | import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience' | 15 | import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience' |
16 | import { createActivityData, createDislikeActivityData } from './send-create' | 16 | import { buildCreateActivity, buildDislikeActivity } from './send-create' |
17 | import { followActivityData } from './send-follow' | 17 | import { buildFollowActivity } from './send-follow' |
18 | import { likeActivityData } from './send-like' | 18 | import { buildLikeActivity } from './send-like' |
19 | import { VideoShareModel } from '../../../models/video/video-share' | 19 | import { VideoShareModel } from '../../../models/video/video-share' |
20 | import { buildVideoAnnounce } from './send-announce' | 20 | import { buildAnnounceWithVideoAudience } from './send-announce' |
21 | import { logger } from '../../../helpers/logger' | 21 | import { logger } from '../../../helpers/logger' |
22 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
22 | 23 | ||
23 | async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | 24 | async 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 | ||
41 | async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 42 | async 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 | ||
65 | async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 66 | async 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 | ||
88 | async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { | 89 | async 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 | |||
101 | async 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' | |||
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | 7 | import { VideoChannelModel } from '../../../models/video/video-channel' |
8 | import { VideoShareModel } from '../../../models/video/video-share' | 8 | import { VideoShareModel } from '../../../models/video/video-share' |
9 | import { getUpdateActivityPubUrl } from '../url' | 9 | import { getUpdateActivityPubUrl } from '../url' |
10 | import { broadcastToFollowers } from './utils' | 10 | import { broadcastToFollowers, unicastTo } from './utils' |
11 | import { audiencify, getAudience } from '../audience' | 11 | import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience' |
12 | import { logger } from '../../../helpers/logger' | 12 | import { logger } from '../../../helpers/logger' |
13 | import { videoFeedsValidator } from '../../../middlewares/validators' | ||
14 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 13 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
14 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
15 | 15 | ||
16 | async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) { | 16 | async 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 | ||
37 | async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { | 37 | async 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 | |||
61 | async 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 | ||
63 | export { | 78 | export { |
64 | sendUpdateActor, | 79 | sendUpdateActor, |
65 | sendUpdateVideo | 80 | sendUpdateVideo, |
81 | sendUpdateCacheFile | ||
66 | } | 82 | } |
67 | 83 | ||
68 | // --------------------------------------------------------------------------- | 84 | // --------------------------------------------------------------------------- |
69 | 85 | ||
70 | function updateActivityData (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { | 86 | function 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 ( | |||
59 | async function broadcastToFollowers ( | 59 | async 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 | ||
118 | async function computeFollowerUris (toActorFollower: ActorModel[], actorsException: ActorModel[], t: Transaction) { | 118 | async 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' | |||
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { VideoFileModel } from '../../models/video/video-file' | ||
7 | 8 | ||
8 | function getVideoActivityPubUrl (video: VideoModel) { | 9 | function 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 | ||
13 | function 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 | |||
12 | function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { | 19 | function 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' | |||
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import { join } from 'path' | 4 | import { join } from 'path' |
5 | import * as request from 'request' | 5 | import * as request from 'request' |
6 | import { ActivityIconObject, VideoState } from '../../../shared/index' | 6 | import { ActivityIconObject, ActivityVideoUrlObject, VideoState, ActivityUrlObject } from '../../../shared/index' |
7 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 7 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
8 | import { VideoPrivacy } from '../../../shared/models/videos' | 8 | import { VideoPrivacy } from '../../../shared/models/videos' |
9 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' | 9 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' |
10 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 10 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
11 | import { resetSequelizeInstance, retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils' | 11 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' |
12 | import { logger } from '../../helpers/logger' | 12 | import { logger } from '../../helpers/logger' |
13 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | 13 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' |
14 | import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers' | 14 | import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers' |
@@ -17,7 +17,7 @@ import { TagModel } from '../../models/video/tag' | |||
17 | import { VideoModel } from '../../models/video/video' | 17 | import { VideoModel } from '../../models/video/video' |
18 | import { VideoChannelModel } from '../../models/video/video-channel' | 18 | import { VideoChannelModel } from '../../models/video/video-channel' |
19 | import { VideoFileModel } from '../../models/video/video-file' | 19 | import { VideoFileModel } from '../../models/video/video-file' |
20 | import { getOrCreateActorAndServerAndModel, updateActorAvatarInstance } from './actor' | 20 | import { getOrCreateActorAndServerAndModel } from './actor' |
21 | import { addVideoComments } from './video-comments' | 21 | import { addVideoComments } from './video-comments' |
22 | import { crawlCollectionPage } from './crawl' | 22 | import { crawlCollectionPage } from './crawl' |
23 | import { sendCreateVideo, sendUpdateVideo } from './send' | 23 | import { sendCreateVideo, sendUpdateVideo } from './send' |
@@ -25,7 +25,6 @@ import { isArray } from '../../helpers/custom-validators/misc' | |||
25 | import { VideoCaptionModel } from '../../models/video/video-caption' | 25 | import { VideoCaptionModel } from '../../models/video/video-caption' |
26 | import { JobQueue } from '../job-queue' | 26 | import { JobQueue } from '../job-queue' |
27 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' | 27 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' |
28 | import { getUrlFromWebfinger } from '../../helpers/webfinger' | ||
29 | import { createRates } from './video-rates' | 28 | import { createRates } from './video-rates' |
30 | import { addVideoShares, shareVideoByServerAndChannel } from './share' | 29 | import { addVideoShares, shareVideoByServerAndChannel } from './share' |
31 | import { AccountModel } from '../../models/account/account' | 30 | import { AccountModel } from '../../models/account/account' |
@@ -137,10 +136,7 @@ async function videoActivityObjectToDBAttributes ( | |||
137 | } | 136 | } |
138 | 137 | ||
139 | function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObject: VideoTorrentObject) { | 138 | function 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> { | |||
342 | async function updateVideoFromAP ( | 338 | async 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 | |||
446 | function 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 @@ | |||
1 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
2 | import { sendUndoCacheFile } from './activitypub/send' | ||
3 | import { Transaction } from 'sequelize' | ||
4 | import { getServerActor } from '../helpers/utils' | ||
5 | |||
6 | async 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 | |||
16 | export { | ||
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 @@ | |||
1 | import { AbstractScheduler } from './abstract-scheduler' | ||
2 | import { CONFIG, JOB_TTL, REDUNDANCY, SCHEDULER_INTERVALS_MS } from '../../initializers' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' | ||
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | ||
6 | import { VideoFileModel } from '../../models/video/video-file' | ||
7 | import { sortBy } from 'lodash' | ||
8 | import { downloadWebTorrentVideo } from '../../helpers/webtorrent' | ||
9 | import { join } from 'path' | ||
10 | import { rename } from 'fs-extra' | ||
11 | import { getServerActor } from '../../helpers/utils' | ||
12 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' | ||
13 | import { VideoModel } from '../../models/video/video' | ||
14 | import { getVideoCacheFileActivityPubUrl } from '../activitypub/url' | ||
15 | import { removeVideoRedundancy } from '../redundancy' | ||
16 | import { isTestInstance } from '../../helpers/core-utils' | ||
17 | |||
18 | export 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 | } | ||