From 0f320037e689b2778959c12ddd4ce790f6e4ae4f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 11 May 2018 15:10:13 +0200 Subject: Add ability to update a video channel --- server/lib/activitypub/actor.ts | 2 +- server/lib/activitypub/process/process-undo.ts | 25 ++++++++++++- server/lib/activitypub/process/process-update.ts | 10 ++++-- server/lib/activitypub/send/send-undo.ts | 29 ++++++++++++--- server/lib/activitypub/share.ts | 45 ++++++++++++++++++------ server/lib/activitypub/videos.ts | 13 ++++--- 6 files changed, 101 insertions(+), 23 deletions(-) (limited to 'server/lib') diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index b0cf9bb17..5773fc34f 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts @@ -353,7 +353,7 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu return videoChannelCreated } -async function refreshActorIfNeeded (actor: ActorModel) { +async function refreshActorIfNeeded (actor: ActorModel): Promise { if (!actor.isOutdated()) return actor try { diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index 565e70289..9b024d15f 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts @@ -1,4 +1,4 @@ -import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' +import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' import { DislikeObject } from '../../../../shared/models/activitypub/objects' import { getActorUrl } from '../../../helpers/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' @@ -10,6 +10,7 @@ import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { forwardActivity } from '../send/misc' import { getOrCreateAccountAndVideoAndChannel } from '../videos' +import { VideoShareModel } from '../../../models/video/video-share' async function processUndoActivity (activity: ActivityUndo) { const activityToUndo = activity.object @@ -22,6 +23,8 @@ async function processUndoActivity (activity: ActivityUndo) { return processUndoDislike(actorUrl, activity) } else if (activityToUndo.type === 'Follow') { return processUndoFollow(actorUrl, activityToUndo) + } else if (activityToUndo.type === 'Announce') { + return processUndoAnnounce(actorUrl, activityToUndo) } logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id }) @@ -123,3 +126,23 @@ function undoFollow (actorUrl: string, followActivity: ActivityFollow) { return undefined }) } + +function processUndoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) { + const options = { + arguments: [ actorUrl, announceActivity ], + errorMessage: 'Cannot undo announce with many retries.' + } + + return retryTransactionWrapper(undoAnnounce, options) +} + +function undoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) { + return sequelizeTypescript.transaction(async t => { + const share = await VideoShareModel.loadByUrl(announceActivity.id, t) + if (!share) throw new Error(`'Unknown video share ${announceActivity.id}.`) + + await share.destroy({ transaction: t }) + + return undefined + }) +} diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 51e3cc4e3..0dd657c2b 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts @@ -14,7 +14,7 @@ import { VideoFileModel } from '../../../models/video/video-file' import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' import { generateThumbnailFromUrl, - getOrCreateAccountAndVideoAndChannel, + getOrCreateAccountAndVideoAndChannel, getOrCreateVideoChannel, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from '../videos' @@ -54,6 +54,10 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id) + // Fetch video channel outside the transaction + const newVideoChannelActor = await getOrCreateVideoChannel(videoAttributesToUpdate) + const newVideoChannel = newVideoChannelActor.VideoChannel + logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) let videoInstance = res.video let videoFieldsSave: any @@ -66,12 +70,13 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { videoFieldsSave = videoInstance.toJSON() + // Check actor has the right to update the video const videoChannel = videoInstance.VideoChannel if (videoChannel.Account.Actor.id !== actor.id) { throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url) } - const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to) + const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoAttributesToUpdate, activity.to) videoInstance.set('name', videoData.name) videoInstance.set('uuid', videoData.uuid) videoInstance.set('url', videoData.url) @@ -87,6 +92,7 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { videoInstance.set('updatedAt', videoData.updatedAt) videoInstance.set('views', videoData.views) videoInstance.set('privacy', videoData.privacy) + videoInstance.set('channelId', videoData.channelId) await videoInstance.save(sequelizeOptions) diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index bd49d452e..adee2192f 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts @@ -1,5 +1,12 @@ import { Transaction } from 'sequelize' -import { ActivityAudience, ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' +import { + ActivityAnnounce, + ActivityAudience, + ActivityCreate, + ActivityFollow, + ActivityLike, + ActivityUndo +} from '../../../../shared/models/activitypub' import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { VideoModel } from '../../../models/video/video' @@ -16,6 +23,8 @@ import { import { createActivityData, createDislikeActivityData } from './send-create' import { followActivityData } from './send-follow' import { likeActivityData } from './send-like' +import { VideoShareModel } from '../../../models/video/video-share' +import { buildVideoAnnounce } from './send-announce' async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { const me = actorFollow.ActorFollower @@ -58,7 +67,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) const dislikeActivity = createDislikeActivityData(byActor, video) - const object = await createActivityData(undoUrl, byActor, dislikeActivity, t) + const object = await createActivityData(dislikeUrl, byActor, dislikeActivity, t) if (video.isOwned() === false) { const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) @@ -73,12 +82,24 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) } +async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { + const undoUrl = getUndoActivityPubUrl(videoShare.url) + + const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) + const object = await buildVideoAnnounce(byActor, videoShare, video, t) + const data = await undoActivityData(undoUrl, byActor, object, t) + + const followersException = [ byActor ] + return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) +} + // --------------------------------------------------------------------------- export { sendUndoFollow, sendUndoLike, - sendUndoDislike + sendUndoDislike, + sendUndoAnnounce } // --------------------------------------------------------------------------- @@ -86,7 +107,7 @@ export { async function undoActivityData ( url: string, byActor: ActorModel, - object: ActivityFollow | ActivityLike | ActivityCreate, + object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce, t: Transaction, audience?: ActivityAudience ): Promise { diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index f256f8d21..698414867 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts @@ -3,16 +3,37 @@ import { VideoPrivacy } from '../../../shared/models/videos' import { getServerActor } from '../../helpers/utils' import { VideoModel } from '../../models/video/video' import { VideoShareModel } from '../../models/video/video-share' -import { sendVideoAnnounce } from './send' +import { sendUndoAnnounce, sendVideoAnnounce } from './send' import { getAnnounceActivityPubUrl } from './url' +import { VideoChannelModel } from '../../models/video/video-channel' async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { if (video.privacy === VideoPrivacy.PRIVATE) return undefined + return Promise.all([ + shareByServer(video, t), + shareByVideoChannel(video, t) + ]) +} + +async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { + await undoShareByVideoChannel(video, oldVideoChannel, t) + + await shareByVideoChannel(video, t) +} + +export { + changeVideoChannelShare, + shareVideoByServerAndChannel +} + +// --------------------------------------------------------------------------- + +async function shareByServer (video: VideoModel, t: Transaction) { const serverActor = await getServerActor() const serverShareUrl = getAnnounceActivityPubUrl(video.url, serverActor) - const serverSharePromise = VideoShareModel.findOrCreate({ + return VideoShareModel.findOrCreate({ defaults: { actorId: serverActor.id, videoId: video.id, @@ -27,9 +48,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) return undefined }) +} +async function shareByVideoChannel (video: VideoModel, t: Transaction) { const videoChannelShareUrl = getAnnounceActivityPubUrl(video.url, video.VideoChannel.Actor) - const videoChannelSharePromise = VideoShareModel.findOrCreate({ + return VideoShareModel.findOrCreate({ defaults: { actorId: video.VideoChannel.actorId, videoId: video.id, @@ -40,17 +63,17 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) }, transaction: t }).then(([ videoChannelShare, created ]) => { - if (created) return sendVideoAnnounce(serverActor, videoChannelShare, video, t) + if (created) return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) return undefined }) - - return Promise.all([ - serverSharePromise, - videoChannelSharePromise - ]) } -export { - shareVideoByServerAndChannel +async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { + // Load old share + const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) + if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) + + await sendUndoAnnounce(oldVideoChannel.Actor, oldShare, video, t) + await oldShare.destroy({ transaction: t }) } diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index b81acbb35..2899acff3 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -137,6 +137,13 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObje return attributes } +function getOrCreateVideoChannel (videoObject: VideoTorrentObject) { + const channel = videoObject.attributedTo.find(a => a.type === 'Group') + if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) + + return getOrCreateActorAndServerAndModel(channel.id) +} + async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel) { logger.debug('Adding remote video %s.', videoObject.id) @@ -199,10 +206,7 @@ async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentOb actor = await getOrCreateActorAndServerAndModel(actorObj.id) } - const channel = videoObject.attributedTo.find(a => a.type === 'Group') - if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) - - const channelActor = await getOrCreateActorAndServerAndModel(channel.id) + const channelActor = await getOrCreateVideoChannel(videoObject) const options = { arguments: [ videoObject, channelActor ], @@ -301,6 +305,7 @@ export { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes, getOrCreateVideo, + getOrCreateVideoChannel, addVideoShares} // --------------------------------------------------------------------------- -- cgit v1.2.3