diff options
Diffstat (limited to 'server/lib/files-cache')
-rw-r--r-- | server/lib/files-cache/avatar-permanent-file-cache.ts | 27 | ||||
-rw-r--r-- | server/lib/files-cache/index.ts | 9 | ||||
-rw-r--r-- | server/lib/files-cache/shared/abstract-permanent-file-cache.ts | 132 | ||||
-rw-r--r-- | server/lib/files-cache/shared/abstract-simple-file-cache.ts (renamed from server/lib/files-cache/abstract-video-static-file-cache.ts) | 4 | ||||
-rw-r--r-- | server/lib/files-cache/shared/index.ts | 2 | ||||
-rw-r--r-- | server/lib/files-cache/video-captions-simple-file-cache.ts (renamed from server/lib/files-cache/videos-caption-cache.ts) | 12 | ||||
-rw-r--r-- | server/lib/files-cache/video-miniature-permanent-file-cache.ts | 28 | ||||
-rw-r--r-- | server/lib/files-cache/video-previews-simple-file-cache.ts (renamed from server/lib/files-cache/videos-preview-cache.ts) | 8 | ||||
-rw-r--r-- | server/lib/files-cache/video-storyboards-simple-file-cache.ts | 53 | ||||
-rw-r--r-- | server/lib/files-cache/video-torrents-simple-file-cache.ts (renamed from server/lib/files-cache/videos-torrent-cache.ts) | 8 |
10 files changed, 265 insertions, 18 deletions
diff --git a/server/lib/files-cache/avatar-permanent-file-cache.ts b/server/lib/files-cache/avatar-permanent-file-cache.ts new file mode 100644 index 000000000..0c508b063 --- /dev/null +++ b/server/lib/files-cache/avatar-permanent-file-cache.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { CONFIG } from '@server/initializers/config' | ||
2 | import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants' | ||
3 | import { ActorImageModel } from '@server/models/actor/actor-image' | ||
4 | import { MActorImage } from '@server/types/models' | ||
5 | import { AbstractPermanentFileCache } from './shared' | ||
6 | |||
7 | export 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 index e5853f7d6..5630a9b80 100644 --- a/server/lib/files-cache/index.ts +++ b/server/lib/files-cache/index.ts | |||
@@ -1,3 +1,6 @@ | |||
1 | export * from './videos-preview-cache' | 1 | export * from './avatar-permanent-file-cache' |
2 | export * from './videos-caption-cache' | 2 | export * from './video-miniature-permanent-file-cache' |
3 | export * from './videos-torrent-cache' | 3 | export * from './video-captions-simple-file-cache' |
4 | export * from './video-previews-simple-file-cache' | ||
5 | export * from './video-storyboards-simple-file-cache' | ||
6 | export * 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 new file mode 100644 index 000000000..f990e9872 --- /dev/null +++ b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts | |||
@@ -0,0 +1,132 @@ | |||
1 | import express from 'express' | ||
2 | import { LRUCache } from 'lru-cache' | ||
3 | import { Model } from 'sequelize' | ||
4 | import { logger } from '@server/helpers/logger' | ||
5 | import { CachePromise } from '@server/helpers/promise-cache' | ||
6 | import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants' | ||
7 | import { downloadImageFromWorker } from '@server/lib/worker/parent-process' | ||
8 | import { HttpStatusCode } from '@shared/models' | ||
9 | |||
10 | type ImageModel = { | ||
11 | fileUrl: string | ||
12 | filename: string | ||
13 | onDisk: boolean | ||
14 | |||
15 | isOwned (): boolean | ||
16 | getPath (): string | ||
17 | |||
18 | save (): Promise<Model> | ||
19 | } | ||
20 | |||
21 | export 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/abstract-video-static-file-cache.ts b/server/lib/files-cache/shared/abstract-simple-file-cache.ts index a7ac88525..6fab322cd 100644 --- a/server/lib/files-cache/abstract-video-static-file-cache.ts +++ b/server/lib/files-cache/shared/abstract-simple-file-cache.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { remove } from 'fs-extra' | 1 | import { remove } from 'fs-extra' |
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import memoizee from 'memoizee' | 3 | import memoizee from 'memoizee' |
4 | 4 | ||
5 | type GetFilePathResult = { isOwned: boolean, path: string, downloadName?: string } | undefined | 5 | type GetFilePathResult = { isOwned: boolean, path: string, downloadName?: string } | undefined |
6 | 6 | ||
7 | export abstract class AbstractVideoStaticFileCache <T> { | 7 | export abstract class AbstractSimpleFileCache <T> { |
8 | 8 | ||
9 | getFilePath: (params: T) => Promise<GetFilePathResult> | 9 | getFilePath: (params: T) => Promise<GetFilePathResult> |
10 | 10 | ||
diff --git a/server/lib/files-cache/shared/index.ts b/server/lib/files-cache/shared/index.ts new file mode 100644 index 000000000..61c4aacc7 --- /dev/null +++ b/server/lib/files-cache/shared/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './abstract-permanent-file-cache' | ||
2 | export * from './abstract-simple-file-cache' | ||
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/video-captions-simple-file-cache.ts index d21acf4ef..cbeeff732 100644 --- a/server/lib/files-cache/videos-caption-cache.ts +++ b/server/lib/files-cache/video-captions-simple-file-cache.ts | |||
@@ -5,11 +5,11 @@ import { CONFIG } from '../../initializers/config' | |||
5 | import { FILES_CACHE } from '../../initializers/constants' | 5 | import { FILES_CACHE } from '../../initializers/constants' |
6 | import { VideoModel } from '../../models/video/video' | 6 | import { VideoModel } from '../../models/video/video' |
7 | import { VideoCaptionModel } from '../../models/video/video-caption' | 7 | import { VideoCaptionModel } from '../../models/video/video-caption' |
8 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 8 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' |
9 | 9 | ||
10 | class VideosCaptionCache extends AbstractVideoStaticFileCache <string> { | 10 | class VideoCaptionsSimpleFileCache extends AbstractSimpleFileCache <string> { |
11 | 11 | ||
12 | private static instance: VideosCaptionCache | 12 | private static instance: VideoCaptionsSimpleFileCache |
13 | 13 | ||
14 | private constructor () { | 14 | private constructor () { |
15 | super() | 15 | super() |
@@ -23,7 +23,9 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <string> { | |||
23 | const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename) | 23 | const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename) |
24 | if (!videoCaption) return undefined | 24 | if (!videoCaption) return undefined |
25 | 25 | ||
26 | if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) } | 26 | if (videoCaption.isOwned()) { |
27 | return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) } | ||
28 | } | ||
27 | 29 | ||
28 | return this.loadRemoteFile(filename) | 30 | return this.loadRemoteFile(filename) |
29 | } | 31 | } |
@@ -55,5 +57,5 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <string> { | |||
55 | } | 57 | } |
56 | 58 | ||
57 | export { | 59 | export { |
58 | VideosCaptionCache | 60 | VideoCaptionsSimpleFileCache |
59 | } | 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 new file mode 100644 index 000000000..35d9466f7 --- /dev/null +++ b/server/lib/files-cache/video-miniature-permanent-file-cache.ts | |||
@@ -0,0 +1,28 @@ | |||
1 | import { CONFIG } from '@server/initializers/config' | ||
2 | import { THUMBNAILS_SIZE } from '@server/initializers/constants' | ||
3 | import { ThumbnailModel } from '@server/models/video/thumbnail' | ||
4 | import { MThumbnail } from '@server/types/models' | ||
5 | import { ThumbnailType } from '@shared/models' | ||
6 | import { AbstractPermanentFileCache } from './shared' | ||
7 | |||
8 | export 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/videos-preview-cache.ts b/server/lib/files-cache/video-previews-simple-file-cache.ts index d19c3f4f4..a05e80e16 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/video-previews-simple-file-cache.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { FILES_CACHE } from '../../initializers/constants' | 2 | import { FILES_CACHE } from '../../initializers/constants' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 4 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' |
5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | 5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' |
6 | import { ThumbnailModel } from '@server/models/video/thumbnail' | 6 | import { ThumbnailModel } from '@server/models/video/thumbnail' |
7 | import { ThumbnailType } from '@shared/models' | 7 | import { ThumbnailType } from '@shared/models' |
8 | import { logger } from '@server/helpers/logger' | 8 | import { logger } from '@server/helpers/logger' |
9 | 9 | ||
10 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | 10 | class VideoPreviewsSimpleFileCache extends AbstractSimpleFileCache <string> { |
11 | 11 | ||
12 | private static instance: VideosPreviewCache | 12 | private static instance: VideoPreviewsSimpleFileCache |
13 | 13 | ||
14 | private constructor () { | 14 | private constructor () { |
15 | super() | 15 | super() |
@@ -54,5 +54,5 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
54 | } | 54 | } |
55 | 55 | ||
56 | export { | 56 | export { |
57 | VideosPreviewCache | 57 | VideoPreviewsSimpleFileCache |
58 | } | 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 new file mode 100644 index 000000000..4cd96e70c --- /dev/null +++ b/server/lib/files-cache/video-storyboards-simple-file-cache.ts | |||
@@ -0,0 +1,53 @@ | |||
1 | import { join } from 'path' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
4 | import { StoryboardModel } from '@server/models/video/storyboard' | ||
5 | import { FILES_CACHE } from '../../initializers/constants' | ||
6 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' | ||
7 | |||
8 | class 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 | |||
51 | export { | ||
52 | VideoStoryboardsSimpleFileCache | ||
53 | } | ||
diff --git a/server/lib/files-cache/videos-torrent-cache.ts b/server/lib/files-cache/video-torrents-simple-file-cache.ts index a6bf98dd4..8bcd0b9bf 100644 --- a/server/lib/files-cache/videos-torrent-cache.ts +++ b/server/lib/files-cache/video-torrents-simple-file-cache.ts | |||
@@ -6,11 +6,11 @@ import { MVideo, MVideoFile } from '@server/types/models' | |||
6 | import { CONFIG } from '../../initializers/config' | 6 | import { CONFIG } from '../../initializers/config' |
7 | import { FILES_CACHE } from '../../initializers/constants' | 7 | import { FILES_CACHE } from '../../initializers/constants' |
8 | import { VideoModel } from '../../models/video/video' | 8 | import { VideoModel } from '../../models/video/video' |
9 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 9 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' |
10 | 10 | ||
11 | class VideosTorrentCache extends AbstractVideoStaticFileCache <string> { | 11 | class VideoTorrentsSimpleFileCache extends AbstractSimpleFileCache <string> { |
12 | 12 | ||
13 | private static instance: VideosTorrentCache | 13 | private static instance: VideoTorrentsSimpleFileCache |
14 | 14 | ||
15 | private constructor () { | 15 | private constructor () { |
16 | super() | 16 | super() |
@@ -66,5 +66,5 @@ class VideosTorrentCache extends AbstractVideoStaticFileCache <string> { | |||
66 | } | 66 | } |
67 | 67 | ||
68 | export { | 68 | export { |
69 | VideosTorrentCache | 69 | VideoTorrentsSimpleFileCache |
70 | } | 70 | } |