Add model cache for video
When fetching only immutable attributes
This commit is contained in:
parent
e436baf0b0
commit
7eba5e1fa8
|
@ -37,7 +37,7 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
|
|||
import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
|
||||
import { VideoPlaylistModel } from '../../models/video/video-playlist'
|
||||
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||
import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models'
|
||||
import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption, MVideoId } from '@server/typings/models'
|
||||
|
||||
const activityPubClientRouter = express.Router()
|
||||
|
||||
|
@ -85,7 +85,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity',
|
|||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/announces',
|
||||
executeIfActivityPub,
|
||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoAnnouncesController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||
|
@ -95,17 +95,17 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
|||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/likes',
|
||||
executeIfActivityPub,
|
||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoLikesController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/dislikes',
|
||||
executeIfActivityPub,
|
||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoDislikesController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/comments',
|
||||
executeIfActivityPub,
|
||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoCommentsController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
|
||||
|
@ -238,7 +238,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
|
|||
}
|
||||
|
||||
async function videoAnnouncesController (req: express.Request, res: express.Response) {
|
||||
const video = res.locals.onlyVideo
|
||||
const video = res.locals.onlyImmutableVideo
|
||||
|
||||
const handler = async (start: number, count: number) => {
|
||||
const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count)
|
||||
|
@ -253,21 +253,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
|
|||
}
|
||||
|
||||
async function videoLikesController (req: express.Request, res: express.Response) {
|
||||
const video = res.locals.onlyVideo
|
||||
const video = res.locals.onlyImmutableVideo
|
||||
const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
|
||||
|
||||
return activityPubResponse(activityPubContextify(json), res)
|
||||
}
|
||||
|
||||
async function videoDislikesController (req: express.Request, res: express.Response) {
|
||||
const video = res.locals.onlyVideo
|
||||
const video = res.locals.onlyImmutableVideo
|
||||
const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
|
||||
|
||||
return activityPubResponse(activityPubContextify(json), res)
|
||||
}
|
||||
|
||||
async function videoCommentsController (req: express.Request, res: express.Response) {
|
||||
const video = res.locals.onlyVideo
|
||||
const video = res.locals.onlyImmutableVideo
|
||||
|
||||
const handler = async (start: number, count: number) => {
|
||||
const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count)
|
||||
|
@ -386,7 +386,7 @@ async function actorPlaylists (req: express.Request, account: MAccountId) {
|
|||
return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
|
||||
}
|
||||
|
||||
function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) {
|
||||
function videoRates (req: express.Request, rateType: VideoRateType, video: MVideoId, url: string) {
|
||||
const handler = async (start: number, count: number) => {
|
||||
const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,16 @@ import { Response } from 'express'
|
|||
import { fetchVideo, VideoFetchType } from '../video'
|
||||
import { UserRight } from '../../../shared/models/users'
|
||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||
import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models'
|
||||
import {
|
||||
MUser,
|
||||
MUserAccountId,
|
||||
MVideoAccountLight,
|
||||
MVideoFullLight,
|
||||
MVideoIdThumbnail,
|
||||
MVideoImmutable,
|
||||
MVideoThumbnail,
|
||||
MVideoWithRights
|
||||
} from '@server/typings/models'
|
||||
|
||||
async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') {
|
||||
const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
|
||||
|
@ -22,8 +31,12 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
|
|||
res.locals.videoAll = video as MVideoFullLight
|
||||
break
|
||||
|
||||
case 'only-immutable-attributes':
|
||||
res.locals.onlyImmutableVideo = video as MVideoImmutable
|
||||
break
|
||||
|
||||
case 'id':
|
||||
res.locals.videoId = video
|
||||
res.locals.videoId = video as MVideoIdThumbnail
|
||||
break
|
||||
|
||||
case 'only-video':
|
||||
|
|
|
@ -5,13 +5,15 @@ import {
|
|||
MVideoFullLight,
|
||||
MVideoIdThumbnail,
|
||||
MVideoThumbnail,
|
||||
MVideoWithRights
|
||||
MVideoWithRights,
|
||||
MVideoImmutable
|
||||
} from '@server/typings/models'
|
||||
import { Response } from 'express'
|
||||
|
||||
type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
|
||||
type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 'only-immutable-attributes'
|
||||
|
||||
function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight>
|
||||
function fetchVideo (id: number | string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable>
|
||||
function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail>
|
||||
function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights>
|
||||
function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail>
|
||||
|
@ -19,14 +21,16 @@ function fetchVideo (
|
|||
id: number | string,
|
||||
fetchType: VideoFetchType,
|
||||
userId?: number
|
||||
): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail>
|
||||
): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable>
|
||||
function fetchVideo (
|
||||
id: number | string,
|
||||
fetchType: VideoFetchType,
|
||||
userId?: number
|
||||
): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> {
|
||||
): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> {
|
||||
if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
|
||||
|
||||
if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
|
||||
|
||||
if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id)
|
||||
|
||||
if (fetchType === 'only-video') return VideoModel.load(id)
|
||||
|
|
|
@ -147,7 +147,10 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
|
|||
})
|
||||
}
|
||||
|
||||
const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights', authenticateInQuery = false) => {
|
||||
const videosCustomGetValidator = (
|
||||
fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes',
|
||||
authenticateInQuery = false
|
||||
) => {
|
||||
return [
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ type ModelCacheType =
|
|||
'local-account-name'
|
||||
| 'local-actor-name'
|
||||
| 'local-actor-url'
|
||||
| 'video-immutable'
|
||||
|
||||
type DeleteKey =
|
||||
'video'
|
||||
|
||||
class ModelCache {
|
||||
|
||||
|
@ -14,7 +18,14 @@ class ModelCache {
|
|||
private readonly localCache: { [id in ModelCacheType]: Map<string, any> } = {
|
||||
'local-account-name': new Map(),
|
||||
'local-actor-name': new Map(),
|
||||
'local-actor-url': new Map()
|
||||
'local-actor-url': new Map(),
|
||||
'video-immutable': new Map()
|
||||
}
|
||||
|
||||
private readonly deleteIds: {
|
||||
[deleteKey in DeleteKey]: Map<number, { cacheType: ModelCacheType, key: string }[]>
|
||||
} = {
|
||||
video: new Map()
|
||||
}
|
||||
|
||||
private constructor () {
|
||||
|
@ -29,8 +40,9 @@ class ModelCache {
|
|||
key: string
|
||||
fun: () => Bluebird<T>
|
||||
whitelist?: () => boolean
|
||||
deleteKey?: DeleteKey
|
||||
}) {
|
||||
const { cacheType, key, fun, whitelist } = options
|
||||
const { cacheType, key, fun, whitelist, deleteKey } = options
|
||||
|
||||
if (whitelist && whitelist() !== true) return fun()
|
||||
|
||||
|
@ -42,11 +54,34 @@ class ModelCache {
|
|||
}
|
||||
|
||||
return fun().then(m => {
|
||||
if (!m) return m
|
||||
|
||||
if (!whitelist || whitelist()) cache.set(key, m)
|
||||
|
||||
if (deleteKey) {
|
||||
const map = this.deleteIds[deleteKey]
|
||||
if (!map.has(m.id)) map.set(m.id, [])
|
||||
|
||||
const a = map.get(m.id)
|
||||
a.push({ cacheType, key })
|
||||
}
|
||||
|
||||
return m
|
||||
})
|
||||
}
|
||||
|
||||
invalidateCache (deleteKey: DeleteKey, modelId: number) {
|
||||
const map = this.deleteIds[deleteKey]
|
||||
|
||||
if (!map.has(modelId)) return
|
||||
|
||||
for (const toDelete of map.get(modelId)) {
|
||||
logger.debug('Removing %s -> %d of model cache %s -> %s.', deleteKey, modelId, toDelete.cacheType, toDelete.key)
|
||||
this.localCache[toDelete.cacheType].delete(toDelete.key)
|
||||
}
|
||||
|
||||
map.delete(modelId)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
|
@ -120,7 +120,7 @@ import {
|
|||
MVideoFormattableDetails,
|
||||
MVideoForUser,
|
||||
MVideoFullLight,
|
||||
MVideoIdThumbnail,
|
||||
MVideoIdThumbnail, MVideoImmutable,
|
||||
MVideoThumbnail,
|
||||
MVideoThumbnailBlacklist,
|
||||
MVideoWithAllFiles,
|
||||
|
@ -132,6 +132,7 @@ import { MThumbnail } from '../../typings/models/video/thumbnail'
|
|||
import { VideoFile } from '@shared/models/videos/video-file.model'
|
||||
import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
|
||||
import validator from 'validator'
|
||||
import { ModelCache } from '@server/models/model-cache'
|
||||
|
||||
export enum ScopeNames {
|
||||
AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
|
||||
|
@ -1074,6 +1075,11 @@ export class VideoModel extends Model<VideoModel> {
|
|||
return undefined
|
||||
}
|
||||
|
||||
@BeforeDestroy
|
||||
static invalidateCache (instance: VideoModel) {
|
||||
ModelCache.Instance.invalidateCache('video', instance.id)
|
||||
}
|
||||
|
||||
static listLocal (): Bluebird<MVideoWithAllFiles[]> {
|
||||
const query = {
|
||||
where: {
|
||||
|
@ -1468,6 +1474,28 @@ export class VideoModel extends Model<VideoModel> {
|
|||
]).findOne(options)
|
||||
}
|
||||
|
||||
static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> {
|
||||
const fun = () => {
|
||||
const where = buildWhereIdOrUUID(id)
|
||||
const options = {
|
||||
attributes: [
|
||||
'id', 'url', 'uuid'
|
||||
],
|
||||
where,
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return VideoModel.unscoped().findOne(options)
|
||||
}
|
||||
|
||||
return ModelCache.Instance.doCache({
|
||||
cacheType: 'video-immutable',
|
||||
key: '' + id,
|
||||
deleteKey: 'video',
|
||||
fun
|
||||
})
|
||||
}
|
||||
|
||||
static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
|
||||
const where = buildWhereIdOrUUID(id)
|
||||
const options = {
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from './models'
|
||||
import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist'
|
||||
import { MVideoImportDefault } from '@server/typings/models/video/video-import'
|
||||
import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile } from '@server/typings/models'
|
||||
import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile, MVideoImmutable } from '@server/typings/models'
|
||||
import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element'
|
||||
import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate'
|
||||
import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership'
|
||||
|
@ -35,6 +35,7 @@ declare module 'express' {
|
|||
|
||||
locals: {
|
||||
videoAll?: MVideoFullLight
|
||||
onlyImmutableVideo?: MVideoImmutable
|
||||
onlyVideo?: MVideoThumbnail
|
||||
onlyVideoWithRights?: MVideoWithRights
|
||||
videoId?: MVideoIdThumbnail
|
||||
|
|
|
@ -37,6 +37,7 @@ export type MVideoId = Pick<MVideo, 'id'>
|
|||
export type MVideoUrl = Pick<MVideo, 'url'>
|
||||
export type MVideoUUID = Pick<MVideo, 'uuid'>
|
||||
|
||||
export type MVideoImmutable = Pick<MVideo, 'id' | 'url' | 'uuid'>
|
||||
export type MVideoIdUrl = MVideoId & MVideoUrl
|
||||
export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'>
|
||||
|
||||
|
|
Loading…
Reference in New Issue