X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fcontrollers%2Fapi%2Fvideos%2Fupdate.ts;h=15899307de27407f6f43e559da71747079e216c7;hb=7b51ede977c299a74728171d8c124bcc4cbba6ea;hp=2450abd0ead1e5ee9264e95e10c920902843790d;hpb=819b656439e5f0ec2ae5de9357502cdfe3196197;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts index 2450abd0e..15899307d 100644 --- a/server/controllers/api/videos/update.ts +++ b/server/controllers/api/videos/update.ts @@ -1,16 +1,16 @@ -import * as express from 'express' +import express from 'express' import { Transaction } from 'sequelize/types' +import { updateTorrentMetadata } from '@server/helpers/webtorrent' import { changeVideoChannelShare } from '@server/lib/activitypub/share' import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' +import { openapiOperationDoc } from '@server/middlewares/doc' import { FilteredModelAttributes } from '@server/types' import { MVideoFullLight } from '@server/types/models' -import { VideoUpdate } from '../../../../shared' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs' +import { HttpStatusCode, VideoUpdate } from '@shared/models' import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' import { resetSequelizeInstance } from '../../../helpers/database-utils' import { createReqFiles } from '../../../helpers/express-utils' import { logger, loggerTagsFactory } from '../../../helpers/logger' -import { CONFIG } from '../../../initializers/config' import { MIMETYPES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' @@ -25,16 +25,10 @@ const lTags = loggerTagsFactory('api', 'video') const auditLogger = auditLoggerFactory('videos') const updateRouter = express.Router() -const reqVideoFileUpdate = createReqFiles( - [ 'thumbnailfile', 'previewfile' ], - MIMETYPES.IMAGE.MIMETYPE_EXT, - { - thumbnailfile: CONFIG.STORAGE.TMP_DIR, - previewfile: CONFIG.STORAGE.TMP_DIR - } -) +const reqVideoFileUpdate = createReqFiles([ 'thumbnailfile', 'previewfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) updateRouter.put('/:id', + openapiOperationDoc({ operationId: 'putVideo' }), authenticate, reqVideoFileUpdate, asyncMiddleware(videosUpdateValidator), @@ -49,26 +43,29 @@ export { // --------------------------------------------------------------------------- -export async function updateVideo (req: express.Request, res: express.Response) { - const videoInstance = res.locals.videoAll - const videoFieldsSave = videoInstance.toJSON() - const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) +async function updateVideo (req: express.Request, res: express.Response) { + const videoFromReq = res.locals.videoAll + const videoFieldsSave = videoFromReq.toJSON() + const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON()) const videoInfoToUpdate: VideoUpdate = req.body - const wasConfidentialVideo = videoInstance.isConfidential() - const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation() + const wasConfidentialVideo = videoFromReq.isConfidential() + const hadPrivacyForFederation = videoFromReq.hasPrivacyForFederation() const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ - video: videoInstance, + video: videoFromReq, files: req.files, fallback: () => Promise.resolve(undefined), automaticallyGenerated: false }) try { - const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { + const { videoInstanceUpdated, isNewVideo } = await sequelizeTypescript.transaction(async t => { + // Refresh video since thumbnails to prevent concurrent updates + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoFromReq.id, t) + const sequelizeOptions = { transaction: t } - const oldVideoChannel = videoInstance.VideoChannel + const oldVideoChannel = video.VideoChannel const keysToUpdate: (keyof VideoUpdate & FilteredModelAttributes)[] = [ 'name', @@ -84,20 +81,25 @@ export async function updateVideo (req: express.Request, res: express.Response) ] for (const key of keysToUpdate) { - if (videoInfoToUpdate[key] !== undefined) videoInstance.set(key, videoInfoToUpdate[key]) + if (videoInfoToUpdate[key] !== undefined) video.set(key, videoInfoToUpdate[key]) } if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) { - videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt) + video.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt) } // Privacy update? let isNewVideo = false if (videoInfoToUpdate.privacy !== undefined) { - isNewVideo = await updateVideoPrivacy({ videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction: t }) + isNewVideo = await updateVideoPrivacy({ videoInstance: video, videoInfoToUpdate, hadPrivacyForFederation, transaction: t }) + } + + // Force updatedAt attribute change + if (!video.changed()) { + await video.setAsRefreshed(t) } - const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight + const videoInstanceUpdated = await video.save(sequelizeOptions) as MVideoFullLight // Thumbnail & preview updates? if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) @@ -127,28 +129,30 @@ export async function updateVideo (req: express.Request, res: express.Response) transaction: t }) - await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) - auditLogger.update( getAuditIdFromRes(res), new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), oldVideoAuditView ) - logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid)) + logger.info('Video with name %s and uuid %s updated.', video.name, video.uuid, lTags(video.uuid)) - return videoInstanceUpdated + return { videoInstanceUpdated, isNewVideo } }) - if (wasConfidentialVideo) { - Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) + if (videoInstanceUpdated.isLive !== true && videoInfoToUpdate.name) { + await updateTorrentsMetadata(videoInstanceUpdated) } - Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body }) + await sequelizeTypescript.transaction(t => federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)) + + if (wasConfidentialVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) + + Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res }) } catch (err) { // Force fields we want to update // If the transaction is retried, sequelize will think the object has not changed // So it will skip the SQL request, even if the last one was ROLLBACKed! - resetSequelizeInstance(videoInstance, videoFieldsSave) + resetSequelizeInstance(videoFromReq, videoFieldsSave) throw err } @@ -189,3 +193,20 @@ function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: Vide return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction) } } + +async function updateTorrentsMetadata (video: MVideoFullLight) { + for (const file of (video.VideoFiles || [])) { + await updateTorrentMetadata(video, file) + + await file.save() + } + + const hls = video.getHLSPlaylist() + if (!hls) return + + for (const file of (hls.VideoFiles || [])) { + await updateTorrentMetadata(hls, file) + + await file.save() + } +}