1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

server/server -> server/core

This commit is contained in:
Chocobozzz 2023-10-04 15:13:25 +02:00
parent 114327d4ce
commit 5a3d0650c9
No known key found for this signature in database
GPG key ID: 583A612D890159BE
838 changed files with 111 additions and 111 deletions

View file

@ -0,0 +1,27 @@
import { CONFIG } from '@server/initializers/config.js'
import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants.js'
import { ActorImageModel } from '@server/models/actor/actor-image.js'
import { MActorImage } from '@server/types/models/index.js'
import { AbstractPermanentFileCache } from './shared/index.js'
export class AvatarPermanentFileCache extends AbstractPermanentFileCache<MActorImage> {
constructor () {
super(CONFIG.STORAGE.ACTOR_IMAGES_DIR)
}
protected loadModel (filename: string) {
return ActorImageModel.loadByName(filename)
}
protected getImageSize (image: MActorImage): { width: number, height: number } {
if (image.width && image.height) {
return {
height: image.height,
width: image.width
}
}
return ACTOR_IMAGES_SIZE[image.type][0]
}
}

View file

@ -0,0 +1,6 @@
export * from './avatar-permanent-file-cache.js'
export * from './video-miniature-permanent-file-cache.js'
export * from './video-captions-simple-file-cache.js'
export * from './video-previews-simple-file-cache.js'
export * from './video-storyboards-simple-file-cache.js'
export * from './video-torrents-simple-file-cache.js'

View file

@ -0,0 +1,132 @@
import express from 'express'
import { LRUCache } from 'lru-cache'
import { Model } from 'sequelize'
import { logger } from '@server/helpers/logger.js'
import { CachePromise } from '@server/helpers/promise-cache.js'
import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants.js'
import { downloadImageFromWorker } from '@server/lib/worker/parent-process.js'
import { HttpStatusCode } from '@peertube/peertube-models'
type ImageModel = {
fileUrl: string
filename: string
onDisk: boolean
isOwned (): boolean
getPath (): string
save (): Promise<Model>
}
export abstract class AbstractPermanentFileCache <M extends ImageModel> {
// Unsafe because it can return paths that do not exist anymore
private readonly filenameToPathUnsafeCache = new LRUCache<string, string>({
max: LRU_CACHE.FILENAME_TO_PATH_PERMANENT_FILE_CACHE.MAX_SIZE
})
protected abstract getImageSize (image: M): { width: number, height: number }
protected abstract loadModel (filename: string): Promise<M>
constructor (private readonly directory: string) {
}
async lazyServe (options: {
filename: string
res: express.Response
next: express.NextFunction
}) {
const { filename, res, next } = options
if (this.filenameToPathUnsafeCache.has(filename)) {
return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER })
}
const image = await this.lazyLoadIfNeeded(filename)
if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end()
const path = image.getPath()
this.filenameToPathUnsafeCache.set(filename, path)
return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }, (err: any) => {
if (!err) return
this.onServeError({ err, image, next, filename })
})
}
@CachePromise({
keyBuilder: filename => filename
})
private async lazyLoadIfNeeded (filename: string) {
const image = await this.loadModel(filename)
if (!image) return undefined
if (image.onDisk === false) {
if (!image.fileUrl) return undefined
try {
await this.downloadRemoteFile(image)
} catch (err) {
logger.warn('Cannot process remote image %s.', image.fileUrl, { err })
return undefined
}
}
return image
}
async downloadRemoteFile (image: M) {
logger.info('Download remote image %s lazily.', image.fileUrl)
const destination = await this.downloadImage({
filename: image.filename,
fileUrl: image.fileUrl,
size: this.getImageSize(image)
})
image.onDisk = true
image.save()
.catch(err => logger.error('Cannot save new image disk state.', { err }))
return destination
}
private onServeError (options: {
err: any
image: M
filename: string
next: express.NextFunction
}) {
const { err, image, filename, next } = options
// It seems this actor image is not on the disk anymore
if (err.status === HttpStatusCode.NOT_FOUND_404 && !image.isOwned()) {
logger.error('Cannot lazy serve image %s.', filename, { err })
this.filenameToPathUnsafeCache.delete(filename)
image.onDisk = false
image.save()
.catch(err => logger.error('Cannot save new image disk state.', { err }))
}
return next(err)
}
private downloadImage (options: {
fileUrl: string
filename: string
size: { width: number, height: number }
}) {
const downloaderOptions = {
url: options.fileUrl,
destDir: this.directory,
destName: options.filename,
size: options.size
}
return downloadImageFromWorker(downloaderOptions)
}
}

View file

@ -0,0 +1,30 @@
import { remove } from 'fs-extra/esm'
import { logger } from '../../../helpers/logger.js'
import memoizee from 'memoizee'
type GetFilePathResult = { isOwned: boolean, path: string, downloadName?: string } | undefined
export abstract class AbstractSimpleFileCache <T> {
getFilePath: (params: T) => Promise<GetFilePathResult>
abstract getFilePathImpl (params: T): Promise<GetFilePathResult>
// Load and save the remote file, then return the local path from filesystem
protected abstract loadRemoteFile (key: string): Promise<GetFilePathResult>
init (max: number, maxAge: number) {
this.getFilePath = memoizee(this.getFilePathImpl, {
maxAge,
max,
promise: true,
dispose: (result?: GetFilePathResult) => {
if (result && result.isOwned !== true) {
remove(result.path)
.then(() => logger.debug('%s removed from %s', result.path, this.constructor.name))
.catch(err => logger.error('Cannot remove %s from cache %s.', result.path, this.constructor.name, { err }))
}
}
})
}
}

View file

@ -0,0 +1,2 @@
export * from './abstract-permanent-file-cache.js'
export * from './abstract-simple-file-cache.js'

View file

@ -0,0 +1,61 @@
import { join } from 'path'
import { logger } from '@server/helpers/logger.js'
import { doRequestAndSaveToFile } from '@server/helpers/requests.js'
import { CONFIG } from '../../initializers/config.js'
import { FILES_CACHE } from '../../initializers/constants.js'
import { VideoModel } from '../../models/video/video.js'
import { VideoCaptionModel } from '../../models/video/video-caption.js'
import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache.js'
class VideoCaptionsSimpleFileCache extends AbstractSimpleFileCache <string> {
private static instance: VideoCaptionsSimpleFileCache
private constructor () {
super()
}
static get Instance () {
return this.instance || (this.instance = new this())
}
async getFilePathImpl (filename: string) {
const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename)
if (!videoCaption) return undefined
if (videoCaption.isOwned()) {
return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) }
}
return this.loadRemoteFile(filename)
}
// Key is the caption filename
protected async loadRemoteFile (key: string) {
const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(key)
if (!videoCaption) return undefined
if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.')
// Used to fetch the path
const video = await VideoModel.loadFull(videoCaption.videoId)
if (!video) return undefined
const remoteUrl = videoCaption.getFileUrl(video)
const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.filename)
try {
await doRequestAndSaveToFile(remoteUrl, destPath)
return { isOwned: false, path: destPath }
} catch (err) {
logger.info('Cannot fetch remote caption file %s.', remoteUrl, { err })
return undefined
}
}
}
export {
VideoCaptionsSimpleFileCache
}

View file

@ -0,0 +1,28 @@
import { CONFIG } from '@server/initializers/config.js'
import { THUMBNAILS_SIZE } from '@server/initializers/constants.js'
import { ThumbnailModel } from '@server/models/video/thumbnail.js'
import { MThumbnail } from '@server/types/models/index.js'
import { ThumbnailType } from '@peertube/peertube-models'
import { AbstractPermanentFileCache } from './shared/index.js'
export class VideoMiniaturePermanentFileCache extends AbstractPermanentFileCache<MThumbnail> {
constructor () {
super(CONFIG.STORAGE.THUMBNAILS_DIR)
}
protected loadModel (filename: string) {
return ThumbnailModel.loadByFilename(filename, ThumbnailType.MINIATURE)
}
protected getImageSize (image: MThumbnail): { width: number, height: number } {
if (image.width && image.height) {
return {
height: image.height,
width: image.width
}
}
return THUMBNAILS_SIZE
}
}

View file

@ -0,0 +1,58 @@
import { join } from 'path'
import { FILES_CACHE } from '../../initializers/constants.js'
import { VideoModel } from '../../models/video/video.js'
import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache.js'
import { doRequestAndSaveToFile } from '@server/helpers/requests.js'
import { ThumbnailModel } from '@server/models/video/thumbnail.js'
import { ThumbnailType } from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
class VideoPreviewsSimpleFileCache extends AbstractSimpleFileCache <string> {
private static instance: VideoPreviewsSimpleFileCache
private constructor () {
super()
}
static get Instance () {
return this.instance || (this.instance = new this())
}
async getFilePathImpl (filename: string) {
const thumbnail = await ThumbnailModel.loadWithVideoByFilename(filename, ThumbnailType.PREVIEW)
if (!thumbnail) return undefined
if (thumbnail.Video.isOwned()) return { isOwned: true, path: thumbnail.getPath() }
return this.loadRemoteFile(thumbnail.Video.uuid)
}
// Key is the video UUID
protected async loadRemoteFile (key: string) {
const video = await VideoModel.loadFull(key)
if (!video) return undefined
if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.')
const preview = video.getPreview()
const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename)
const remoteUrl = preview.getOriginFileUrl(video)
try {
await doRequestAndSaveToFile(remoteUrl, destPath)
logger.debug('Fetched remote preview %s to %s.', remoteUrl, destPath)
return { isOwned: false, path: destPath }
} catch (err) {
logger.info('Cannot fetch remote preview file %s.', remoteUrl, { err })
return undefined
}
}
}
export {
VideoPreviewsSimpleFileCache
}

View file

@ -0,0 +1,53 @@
import { join } from 'path'
import { logger } from '@server/helpers/logger.js'
import { doRequestAndSaveToFile } from '@server/helpers/requests.js'
import { StoryboardModel } from '@server/models/video/storyboard.js'
import { FILES_CACHE } from '../../initializers/constants.js'
import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache.js'
class VideoStoryboardsSimpleFileCache extends AbstractSimpleFileCache <string> {
private static instance: VideoStoryboardsSimpleFileCache
private constructor () {
super()
}
static get Instance () {
return this.instance || (this.instance = new this())
}
async getFilePathImpl (filename: string) {
const storyboard = await StoryboardModel.loadWithVideoByFilename(filename)
if (!storyboard) return undefined
if (storyboard.Video.isOwned()) return { isOwned: true, path: storyboard.getPath() }
return this.loadRemoteFile(storyboard.filename)
}
// Key is the storyboard filename
protected async loadRemoteFile (key: string) {
const storyboard = await StoryboardModel.loadWithVideoByFilename(key)
if (!storyboard) return undefined
const destPath = join(FILES_CACHE.STORYBOARDS.DIRECTORY, storyboard.filename)
const remoteUrl = storyboard.getOriginFileUrl(storyboard.Video)
try {
await doRequestAndSaveToFile(remoteUrl, destPath)
logger.debug('Fetched remote storyboard %s to %s.', remoteUrl, destPath)
return { isOwned: false, path: destPath }
} catch (err) {
logger.info('Cannot fetch remote storyboard file %s.', remoteUrl, { err })
return undefined
}
}
}
export {
VideoStoryboardsSimpleFileCache
}

View file

@ -0,0 +1,70 @@
import { join } from 'path'
import { logger } from '@server/helpers/logger.js'
import { doRequestAndSaveToFile } from '@server/helpers/requests.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { MVideo, MVideoFile } from '@server/types/models/index.js'
import { CONFIG } from '../../initializers/config.js'
import { FILES_CACHE } from '../../initializers/constants.js'
import { VideoModel } from '../../models/video/video.js'
import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache.js'
class VideoTorrentsSimpleFileCache extends AbstractSimpleFileCache <string> {
private static instance: VideoTorrentsSimpleFileCache
private constructor () {
super()
}
static get Instance () {
return this.instance || (this.instance = new this())
}
async getFilePathImpl (filename: string) {
const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(filename)
if (!file) return undefined
if (file.getVideo().isOwned()) {
const downloadName = this.buildDownloadName(file.getVideo(), file)
return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename), downloadName }
}
return this.loadRemoteFile(filename)
}
// Key is the torrent filename
protected async loadRemoteFile (key: string) {
const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(key)
if (!file) return undefined
if (file.getVideo().isOwned()) throw new Error('Cannot load remote file of owned video.')
// Used to fetch the path
const video = await VideoModel.loadFull(file.getVideo().id)
if (!video) return undefined
const remoteUrl = file.getRemoteTorrentUrl(video)
const destPath = join(FILES_CACHE.TORRENTS.DIRECTORY, file.torrentFilename)
try {
await doRequestAndSaveToFile(remoteUrl, destPath)
const downloadName = this.buildDownloadName(video, file)
return { isOwned: false, path: destPath, downloadName }
} catch (err) {
logger.info('Cannot fetch remote torrent file %s.', remoteUrl, { err })
return undefined
}
}
private buildDownloadName (video: MVideo, file: MVideoFile) {
return `${video.name}-${file.resolution}p.torrent`
}
}
export {
VideoTorrentsSimpleFileCache
}