aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/files-cache
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/files-cache')
-rw-r--r--server/lib/files-cache/avatar-permanent-file-cache.ts27
-rw-r--r--server/lib/files-cache/index.ts6
-rw-r--r--server/lib/files-cache/shared/abstract-permanent-file-cache.ts132
-rw-r--r--server/lib/files-cache/shared/abstract-simple-file-cache.ts30
-rw-r--r--server/lib/files-cache/shared/index.ts2
-rw-r--r--server/lib/files-cache/video-captions-simple-file-cache.ts61
-rw-r--r--server/lib/files-cache/video-miniature-permanent-file-cache.ts28
-rw-r--r--server/lib/files-cache/video-previews-simple-file-cache.ts58
-rw-r--r--server/lib/files-cache/video-storyboards-simple-file-cache.ts53
-rw-r--r--server/lib/files-cache/video-torrents-simple-file-cache.ts70
10 files changed, 0 insertions, 467 deletions
diff --git a/server/lib/files-cache/avatar-permanent-file-cache.ts b/server/lib/files-cache/avatar-permanent-file-cache.ts
deleted file mode 100644
index 0c508b063..000000000
--- a/server/lib/files-cache/avatar-permanent-file-cache.ts
+++ /dev/null
@@ -1,27 +0,0 @@
1import { CONFIG } from '@server/initializers/config'
2import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants'
3import { ActorImageModel } from '@server/models/actor/actor-image'
4import { MActorImage } from '@server/types/models'
5import { AbstractPermanentFileCache } from './shared'
6
7export class AvatarPermanentFileCache extends AbstractPermanentFileCache<MActorImage> {
8
9 constructor () {
10 super(CONFIG.STORAGE.ACTOR_IMAGES_DIR)
11 }
12
13 protected loadModel (filename: string) {
14 return ActorImageModel.loadByName(filename)
15 }
16
17 protected getImageSize (image: MActorImage): { width: number, height: number } {
18 if (image.width && image.height) {
19 return {
20 height: image.height,
21 width: image.width
22 }
23 }
24
25 return ACTOR_IMAGES_SIZE[image.type][0]
26 }
27}
diff --git a/server/lib/files-cache/index.ts b/server/lib/files-cache/index.ts
deleted file mode 100644
index 5630a9b80..000000000
--- a/server/lib/files-cache/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
1export * from './avatar-permanent-file-cache'
2export * from './video-miniature-permanent-file-cache'
3export * from './video-captions-simple-file-cache'
4export * from './video-previews-simple-file-cache'
5export * from './video-storyboards-simple-file-cache'
6export * from './video-torrents-simple-file-cache'
diff --git a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
deleted file mode 100644
index f990e9872..000000000
--- a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
+++ /dev/null
@@ -1,132 +0,0 @@
1import express from 'express'
2import { LRUCache } from 'lru-cache'
3import { Model } from 'sequelize'
4import { logger } from '@server/helpers/logger'
5import { CachePromise } from '@server/helpers/promise-cache'
6import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants'
7import { downloadImageFromWorker } from '@server/lib/worker/parent-process'
8import { HttpStatusCode } from '@shared/models'
9
10type ImageModel = {
11 fileUrl: string
12 filename: string
13 onDisk: boolean
14
15 isOwned (): boolean
16 getPath (): string
17
18 save (): Promise<Model>
19}
20
21export abstract class AbstractPermanentFileCache <M extends ImageModel> {
22 // Unsafe because it can return paths that do not exist anymore
23 private readonly filenameToPathUnsafeCache = new LRUCache<string, string>({
24 max: LRU_CACHE.FILENAME_TO_PATH_PERMANENT_FILE_CACHE.MAX_SIZE
25 })
26
27 protected abstract getImageSize (image: M): { width: number, height: number }
28 protected abstract loadModel (filename: string): Promise<M>
29
30 constructor (private readonly directory: string) {
31
32 }
33
34 async lazyServe (options: {
35 filename: string
36 res: express.Response
37 next: express.NextFunction
38 }) {
39 const { filename, res, next } = options
40
41 if (this.filenameToPathUnsafeCache.has(filename)) {
42 return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER })
43 }
44
45 const image = await this.lazyLoadIfNeeded(filename)
46 if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end()
47
48 const path = image.getPath()
49 this.filenameToPathUnsafeCache.set(filename, path)
50
51 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }, (err: any) => {
52 if (!err) return
53
54 this.onServeError({ err, image, next, filename })
55 })
56 }
57
58 @CachePromise({
59 keyBuilder: filename => filename
60 })
61 private async lazyLoadIfNeeded (filename: string) {
62 const image = await this.loadModel(filename)
63 if (!image) return undefined
64
65 if (image.onDisk === false) {
66 if (!image.fileUrl) return undefined
67
68 try {
69 await this.downloadRemoteFile(image)
70 } catch (err) {
71 logger.warn('Cannot process remote image %s.', image.fileUrl, { err })
72
73 return undefined
74 }
75 }
76
77 return image
78 }
79
80 async downloadRemoteFile (image: M) {
81 logger.info('Download remote image %s lazily.', image.fileUrl)
82
83 const destination = await this.downloadImage({
84 filename: image.filename,
85 fileUrl: image.fileUrl,
86 size: this.getImageSize(image)
87 })
88
89 image.onDisk = true
90 image.save()
91 .catch(err => logger.error('Cannot save new image disk state.', { err }))
92
93 return destination
94 }
95
96 private onServeError (options: {
97 err: any
98 image: M
99 filename: string
100 next: express.NextFunction
101 }) {
102 const { err, image, filename, next } = options
103
104 // It seems this actor image is not on the disk anymore
105 if (err.status === HttpStatusCode.NOT_FOUND_404 && !image.isOwned()) {
106 logger.error('Cannot lazy serve image %s.', filename, { err })
107
108 this.filenameToPathUnsafeCache.delete(filename)
109
110 image.onDisk = false
111 image.save()
112 .catch(err => logger.error('Cannot save new image disk state.', { err }))
113 }
114
115 return next(err)
116 }
117
118 private downloadImage (options: {
119 fileUrl: string
120 filename: string
121 size: { width: number, height: number }
122 }) {
123 const downloaderOptions = {
124 url: options.fileUrl,
125 destDir: this.directory,
126 destName: options.filename,
127 size: options.size
128 }
129
130 return downloadImageFromWorker(downloaderOptions)
131 }
132}
diff --git a/server/lib/files-cache/shared/abstract-simple-file-cache.ts b/server/lib/files-cache/shared/abstract-simple-file-cache.ts
deleted file mode 100644
index 6fab322cd..000000000
--- a/server/lib/files-cache/shared/abstract-simple-file-cache.ts
+++ /dev/null
@@ -1,30 +0,0 @@
1import { remove } from 'fs-extra'
2import { logger } from '../../../helpers/logger'
3import memoizee from 'memoizee'
4
5type GetFilePathResult = { isOwned: boolean, path: string, downloadName?: string } | undefined
6
7export abstract class AbstractSimpleFileCache <T> {
8
9 getFilePath: (params: T) => Promise<GetFilePathResult>
10
11 abstract getFilePathImpl (params: T): Promise<GetFilePathResult>
12
13 // Load and save the remote file, then return the local path from filesystem
14 protected abstract loadRemoteFile (key: string): Promise<GetFilePathResult>
15
16 init (max: number, maxAge: number) {
17 this.getFilePath = memoizee(this.getFilePathImpl, {
18 maxAge,
19 max,
20 promise: true,
21 dispose: (result?: GetFilePathResult) => {
22 if (result && result.isOwned !== true) {
23 remove(result.path)
24 .then(() => logger.debug('%s removed from %s', result.path, this.constructor.name))
25 .catch(err => logger.error('Cannot remove %s from cache %s.', result.path, this.constructor.name, { err }))
26 }
27 }
28 })
29 }
30}
diff --git a/server/lib/files-cache/shared/index.ts b/server/lib/files-cache/shared/index.ts
deleted file mode 100644
index 61c4aacc7..000000000
--- a/server/lib/files-cache/shared/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './abstract-permanent-file-cache'
2export * from './abstract-simple-file-cache'
diff --git a/server/lib/files-cache/video-captions-simple-file-cache.ts b/server/lib/files-cache/video-captions-simple-file-cache.ts
deleted file mode 100644
index cbeeff732..000000000
--- a/server/lib/files-cache/video-captions-simple-file-cache.ts
+++ /dev/null
@@ -1,61 +0,0 @@
1import { join } from 'path'
2import { logger } from '@server/helpers/logger'
3import { doRequestAndSaveToFile } from '@server/helpers/requests'
4import { CONFIG } from '../../initializers/config'
5import { FILES_CACHE } from '../../initializers/constants'
6import { VideoModel } from '../../models/video/video'
7import { VideoCaptionModel } from '../../models/video/video-caption'
8import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache'
9
10class VideoCaptionsSimpleFileCache extends AbstractSimpleFileCache <string> {
11
12 private static instance: VideoCaptionsSimpleFileCache
13
14 private constructor () {
15 super()
16 }
17
18 static get Instance () {
19 return this.instance || (this.instance = new this())
20 }
21
22 async getFilePathImpl (filename: string) {
23 const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename)
24 if (!videoCaption) return undefined
25
26 if (videoCaption.isOwned()) {
27 return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) }
28 }
29
30 return this.loadRemoteFile(filename)
31 }
32
33 // Key is the caption filename
34 protected async loadRemoteFile (key: string) {
35 const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(key)
36 if (!videoCaption) return undefined
37
38 if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.')
39
40 // Used to fetch the path
41 const video = await VideoModel.loadFull(videoCaption.videoId)
42 if (!video) return undefined
43
44 const remoteUrl = videoCaption.getFileUrl(video)
45 const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.filename)
46
47 try {
48 await doRequestAndSaveToFile(remoteUrl, destPath)
49
50 return { isOwned: false, path: destPath }
51 } catch (err) {
52 logger.info('Cannot fetch remote caption file %s.', remoteUrl, { err })
53
54 return undefined
55 }
56 }
57}
58
59export {
60 VideoCaptionsSimpleFileCache
61}
diff --git a/server/lib/files-cache/video-miniature-permanent-file-cache.ts b/server/lib/files-cache/video-miniature-permanent-file-cache.ts
deleted file mode 100644
index 35d9466f7..000000000
--- a/server/lib/files-cache/video-miniature-permanent-file-cache.ts
+++ /dev/null
@@ -1,28 +0,0 @@
1import { CONFIG } from '@server/initializers/config'
2import { THUMBNAILS_SIZE } from '@server/initializers/constants'
3import { ThumbnailModel } from '@server/models/video/thumbnail'
4import { MThumbnail } from '@server/types/models'
5import { ThumbnailType } from '@shared/models'
6import { AbstractPermanentFileCache } from './shared'
7
8export class VideoMiniaturePermanentFileCache extends AbstractPermanentFileCache<MThumbnail> {
9
10 constructor () {
11 super(CONFIG.STORAGE.THUMBNAILS_DIR)
12 }
13
14 protected loadModel (filename: string) {
15 return ThumbnailModel.loadByFilename(filename, ThumbnailType.MINIATURE)
16 }
17
18 protected getImageSize (image: MThumbnail): { width: number, height: number } {
19 if (image.width && image.height) {
20 return {
21 height: image.height,
22 width: image.width
23 }
24 }
25
26 return THUMBNAILS_SIZE
27 }
28}
diff --git a/server/lib/files-cache/video-previews-simple-file-cache.ts b/server/lib/files-cache/video-previews-simple-file-cache.ts
deleted file mode 100644
index a05e80e16..000000000
--- a/server/lib/files-cache/video-previews-simple-file-cache.ts
+++ /dev/null
@@ -1,58 +0,0 @@
1import { join } from 'path'
2import { FILES_CACHE } from '../../initializers/constants'
3import { VideoModel } from '../../models/video/video'
4import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache'
5import { doRequestAndSaveToFile } from '@server/helpers/requests'
6import { ThumbnailModel } from '@server/models/video/thumbnail'
7import { ThumbnailType } from '@shared/models'
8import { logger } from '@server/helpers/logger'
9
10class VideoPreviewsSimpleFileCache extends AbstractSimpleFileCache <string> {
11
12 private static instance: VideoPreviewsSimpleFileCache
13
14 private constructor () {
15 super()
16 }
17
18 static get Instance () {
19 return this.instance || (this.instance = new this())
20 }
21
22 async getFilePathImpl (filename: string) {
23 const thumbnail = await ThumbnailModel.loadWithVideoByFilename(filename, ThumbnailType.PREVIEW)
24 if (!thumbnail) return undefined
25
26 if (thumbnail.Video.isOwned()) return { isOwned: true, path: thumbnail.getPath() }
27
28 return this.loadRemoteFile(thumbnail.Video.uuid)
29 }
30
31 // Key is the video UUID
32 protected async loadRemoteFile (key: string) {
33 const video = await VideoModel.loadFull(key)
34 if (!video) return undefined
35
36 if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.')
37
38 const preview = video.getPreview()
39 const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename)
40 const remoteUrl = preview.getOriginFileUrl(video)
41
42 try {
43 await doRequestAndSaveToFile(remoteUrl, destPath)
44
45 logger.debug('Fetched remote preview %s to %s.', remoteUrl, destPath)
46
47 return { isOwned: false, path: destPath }
48 } catch (err) {
49 logger.info('Cannot fetch remote preview file %s.', remoteUrl, { err })
50
51 return undefined
52 }
53 }
54}
55
56export {
57 VideoPreviewsSimpleFileCache
58}
diff --git a/server/lib/files-cache/video-storyboards-simple-file-cache.ts b/server/lib/files-cache/video-storyboards-simple-file-cache.ts
deleted file mode 100644
index 4cd96e70c..000000000
--- a/server/lib/files-cache/video-storyboards-simple-file-cache.ts
+++ /dev/null
@@ -1,53 +0,0 @@
1import { join } from 'path'
2import { logger } from '@server/helpers/logger'
3import { doRequestAndSaveToFile } from '@server/helpers/requests'
4import { StoryboardModel } from '@server/models/video/storyboard'
5import { FILES_CACHE } from '../../initializers/constants'
6import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache'
7
8class VideoStoryboardsSimpleFileCache extends AbstractSimpleFileCache <string> {
9
10 private static instance: VideoStoryboardsSimpleFileCache
11
12 private constructor () {
13 super()
14 }
15
16 static get Instance () {
17 return this.instance || (this.instance = new this())
18 }
19
20 async getFilePathImpl (filename: string) {
21 const storyboard = await StoryboardModel.loadWithVideoByFilename(filename)
22 if (!storyboard) return undefined
23
24 if (storyboard.Video.isOwned()) return { isOwned: true, path: storyboard.getPath() }
25
26 return this.loadRemoteFile(storyboard.filename)
27 }
28
29 // Key is the storyboard filename
30 protected async loadRemoteFile (key: string) {
31 const storyboard = await StoryboardModel.loadWithVideoByFilename(key)
32 if (!storyboard) return undefined
33
34 const destPath = join(FILES_CACHE.STORYBOARDS.DIRECTORY, storyboard.filename)
35 const remoteUrl = storyboard.getOriginFileUrl(storyboard.Video)
36
37 try {
38 await doRequestAndSaveToFile(remoteUrl, destPath)
39
40 logger.debug('Fetched remote storyboard %s to %s.', remoteUrl, destPath)
41
42 return { isOwned: false, path: destPath }
43 } catch (err) {
44 logger.info('Cannot fetch remote storyboard file %s.', remoteUrl, { err })
45
46 return undefined
47 }
48 }
49}
50
51export {
52 VideoStoryboardsSimpleFileCache
53}
diff --git a/server/lib/files-cache/video-torrents-simple-file-cache.ts b/server/lib/files-cache/video-torrents-simple-file-cache.ts
deleted file mode 100644
index 8bcd0b9bf..000000000
--- a/server/lib/files-cache/video-torrents-simple-file-cache.ts
+++ /dev/null
@@ -1,70 +0,0 @@
1import { join } from 'path'
2import { logger } from '@server/helpers/logger'
3import { doRequestAndSaveToFile } from '@server/helpers/requests'
4import { VideoFileModel } from '@server/models/video/video-file'
5import { MVideo, MVideoFile } from '@server/types/models'
6import { CONFIG } from '../../initializers/config'
7import { FILES_CACHE } from '../../initializers/constants'
8import { VideoModel } from '../../models/video/video'
9import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache'
10
11class VideoTorrentsSimpleFileCache extends AbstractSimpleFileCache <string> {
12
13 private static instance: VideoTorrentsSimpleFileCache
14
15 private constructor () {
16 super()
17 }
18
19 static get Instance () {
20 return this.instance || (this.instance = new this())
21 }
22
23 async getFilePathImpl (filename: string) {
24 const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(filename)
25 if (!file) return undefined
26
27 if (file.getVideo().isOwned()) {
28 const downloadName = this.buildDownloadName(file.getVideo(), file)
29
30 return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename), downloadName }
31 }
32
33 return this.loadRemoteFile(filename)
34 }
35
36 // Key is the torrent filename
37 protected async loadRemoteFile (key: string) {
38 const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(key)
39 if (!file) return undefined
40
41 if (file.getVideo().isOwned()) throw new Error('Cannot load remote file of owned video.')
42
43 // Used to fetch the path
44 const video = await VideoModel.loadFull(file.getVideo().id)
45 if (!video) return undefined
46
47 const remoteUrl = file.getRemoteTorrentUrl(video)
48 const destPath = join(FILES_CACHE.TORRENTS.DIRECTORY, file.torrentFilename)
49
50 try {
51 await doRequestAndSaveToFile(remoteUrl, destPath)
52
53 const downloadName = this.buildDownloadName(video, file)
54
55 return { isOwned: false, path: destPath, downloadName }
56 } catch (err) {
57 logger.info('Cannot fetch remote torrent file %s.', remoteUrl, { err })
58
59 return undefined
60 }
61 }
62
63 private buildDownloadName (video: MVideo, file: MVideoFile) {
64 return `${video.name}-${file.resolution}p.torrent`
65 }
66}
67
68export {
69 VideoTorrentsSimpleFileCache
70}