From 3545e72c686ff1725bbdfd8d16d693e2f4aa75a3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 12 Oct 2022 16:09:02 +0200 Subject: Put private videos under a specific subdirectory --- server/controllers/api/server/debug.ts | 2 + server/controllers/api/videos/index.ts | 2 + server/controllers/api/videos/token.ts | 33 ++++++++++++++ server/controllers/api/videos/update.ts | 76 +++++++++------------------------ 4 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 server/controllers/api/videos/token.ts (limited to 'server/controllers/api') diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts index 4e5333782..f3792bfc8 100644 --- a/server/controllers/api/server/debug.ts +++ b/server/controllers/api/server/debug.ts @@ -8,6 +8,7 @@ import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' import { UserRight } from '../../../../shared/models/users' import { authenticate, ensureUserHasRight } from '../../../middlewares' import { VideoChannelSyncLatestScheduler } from '@server/lib/schedulers/video-channel-sync-latest-scheduler' +import { UpdateVideosScheduler } from '@server/lib/schedulers/update-videos-scheduler' const debugRouter = express.Router() @@ -45,6 +46,7 @@ async function runCommand (req: express.Request, res: express.Response) { 'remove-dandling-resumable-uploads': () => RemoveDanglingResumableUploadsScheduler.Instance.execute(), 'process-video-views-buffer': () => VideoViewsBufferScheduler.Instance.execute(), 'process-video-viewers': () => VideoViewsManager.Instance.processViewerStats(), + 'process-update-videos-scheduler': () => UpdateVideosScheduler.Instance.execute(), 'process-video-channel-sync-latest': () => VideoChannelSyncLatestScheduler.Instance.execute() } diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index b301515df..ea081e5ab 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -41,6 +41,7 @@ import { ownershipVideoRouter } from './ownership' import { rateVideoRouter } from './rate' import { statsRouter } from './stats' import { studioRouter } from './studio' +import { tokenRouter } from './token' import { transcodingRouter } from './transcoding' import { updateRouter } from './update' import { uploadRouter } from './upload' @@ -63,6 +64,7 @@ videosRouter.use('/', uploadRouter) videosRouter.use('/', updateRouter) videosRouter.use('/', filesRouter) videosRouter.use('/', transcodingRouter) +videosRouter.use('/', tokenRouter) videosRouter.get('/categories', openapiOperationDoc({ operationId: 'getCategories' }), diff --git a/server/controllers/api/videos/token.ts b/server/controllers/api/videos/token.ts new file mode 100644 index 000000000..009b6dfb6 --- /dev/null +++ b/server/controllers/api/videos/token.ts @@ -0,0 +1,33 @@ +import express from 'express' +import { VideoTokensManager } from '@server/lib/video-tokens-manager' +import { VideoToken } from '@shared/models' +import { asyncMiddleware, authenticate, videosCustomGetValidator } from '../../../middlewares' + +const tokenRouter = express.Router() + +tokenRouter.post('/:id/token', + authenticate, + asyncMiddleware(videosCustomGetValidator('only-video')), + generateToken +) + +// --------------------------------------------------------------------------- + +export { + tokenRouter +} + +// --------------------------------------------------------------------------- + +function generateToken (req: express.Request, res: express.Response) { + const video = res.locals.onlyVideo + + const { token, expires } = VideoTokensManager.Instance.create(video.uuid) + + return res.json({ + files: { + token, + expires + } + } as VideoToken) +} diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts index ab1a23d9a..0a910379a 100644 --- a/server/controllers/api/videos/update.ts +++ b/server/controllers/api/videos/update.ts @@ -1,12 +1,12 @@ import express from 'express' import { Transaction } from 'sequelize/types' import { changeVideoChannelShare } from '@server/lib/activitypub/share' -import { CreateJobArgument, JobQueue } from '@server/lib/job-queue' -import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' +import { addVideoJobsAfterUpdate, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' +import { setVideoPrivacy } from '@server/lib/video-privacy' import { openapiOperationDoc } from '@server/middlewares/doc' import { FilteredModelAttributes } from '@server/types' import { MVideoFullLight } from '@server/types/models' -import { HttpStatusCode, ManageVideoTorrentPayload, VideoUpdate } from '@shared/models' +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' @@ -18,6 +18,7 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares' import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' import { VideoModel } from '../../../models/video/video' +import { VideoPathManager } from '@server/lib/video-path-manager' const lTags = loggerTagsFactory('api', 'video') const auditLogger = auditLoggerFactory('videos') @@ -47,8 +48,8 @@ async function updateVideo (req: express.Request, res: express.Response) { const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON()) const videoInfoToUpdate: VideoUpdate = req.body - const wasConfidentialVideo = videoFromReq.isConfidential() const hadPrivacyForFederation = videoFromReq.hasPrivacyForFederation() + const oldPrivacy = videoFromReq.privacy const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ video: videoFromReq, @@ -57,12 +58,13 @@ async function updateVideo (req: express.Request, res: express.Response) { automaticallyGenerated: false }) + const videoFileLockReleaser = await VideoPathManager.Instance.lockFiles(videoFromReq.uuid) + try { const { videoInstanceUpdated, isNewVideo } = await sequelizeTypescript.transaction(async t => { // Refresh video since thumbnails to prevent concurrent updates const video = await VideoModel.loadFull(videoFromReq.id, t) - const sequelizeOptions = { transaction: t } const oldVideoChannel = video.VideoChannel const keysToUpdate: (keyof VideoUpdate & FilteredModelAttributes)[] = [ @@ -97,7 +99,7 @@ async function updateVideo (req: express.Request, res: express.Response) { await video.setAsRefreshed(t) } - const videoInstanceUpdated = await video.save(sequelizeOptions) as MVideoFullLight + const videoInstanceUpdated = await video.save({ transaction: t }) as MVideoFullLight // Thumbnail & preview updates? if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) @@ -113,7 +115,9 @@ async function updateVideo (req: express.Request, res: express.Response) { await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t }) videoInstanceUpdated.VideoChannel = res.locals.videoChannel - if (hadPrivacyForFederation === true) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) + if (hadPrivacyForFederation === true) { + await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) + } } // Schedule an update in the future? @@ -139,7 +143,12 @@ async function updateVideo (req: express.Request, res: express.Response) { Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res }) - await addVideoJobsAfterUpdate({ video: videoInstanceUpdated, videoInfoToUpdate, wasConfidentialVideo, isNewVideo }) + await addVideoJobsAfterUpdate({ + video: videoInstanceUpdated, + nameChanged: !!videoInfoToUpdate.name, + oldPrivacy, + isNewVideo + }) } catch (err) { // Force fields we want to update // If the transaction is retried, sequelize will think the object has not changed @@ -147,6 +156,8 @@ async function updateVideo (req: express.Request, res: express.Response) { resetSequelizeInstance(videoFromReq, videoFieldsSave) throw err + } finally { + videoFileLockReleaser() } return res.type('json') @@ -164,7 +175,7 @@ async function updateVideoPrivacy (options: { const isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy) const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) - videoInstance.setPrivacy(newPrivacy) + setVideoPrivacy(videoInstance, newPrivacy) // Unfederate the video if the new privacy is not compatible with federation if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) { @@ -185,50 +196,3 @@ function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: Vide return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction) } } - -async function addVideoJobsAfterUpdate (options: { - video: MVideoFullLight - videoInfoToUpdate: VideoUpdate - wasConfidentialVideo: boolean - isNewVideo: boolean -}) { - const { video, videoInfoToUpdate, wasConfidentialVideo, isNewVideo } = options - const jobs: CreateJobArgument[] = [] - - if (!video.isLive && videoInfoToUpdate.name) { - - for (const file of (video.VideoFiles || [])) { - const payload: ManageVideoTorrentPayload = { action: 'update-metadata', videoId: video.id, videoFileId: file.id } - - jobs.push({ type: 'manage-video-torrent', payload }) - } - - const hls = video.getHLSPlaylist() - - for (const file of (hls?.VideoFiles || [])) { - const payload: ManageVideoTorrentPayload = { action: 'update-metadata', streamingPlaylistId: hls.id, videoFileId: file.id } - - jobs.push({ type: 'manage-video-torrent', payload }) - } - } - - jobs.push({ - type: 'federate-video', - payload: { - videoUUID: video.uuid, - isNewVideo - } - }) - - if (wasConfidentialVideo) { - jobs.push({ - type: 'notify', - payload: { - action: 'new-video', - videoUUID: video.uuid - } - }) - } - - return JobQueue.Instance.createSequentialJobFlow(...jobs) -} -- cgit v1.2.3