diff options
Diffstat (limited to 'server/lib/files-cache')
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 @@ | |||
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 deleted file mode 100644 index 5630a9b80..000000000 --- a/server/lib/files-cache/index.ts +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | export * from './avatar-permanent-file-cache' | ||
2 | export * from './video-miniature-permanent-file-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 deleted file mode 100644 index f990e9872..000000000 --- a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts +++ /dev/null | |||
@@ -1,132 +0,0 @@ | |||
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/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 @@ | |||
1 | import { remove } from 'fs-extra' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import memoizee from 'memoizee' | ||
4 | |||
5 | type GetFilePathResult = { isOwned: boolean, path: string, downloadName?: string } | undefined | ||
6 | |||
7 | export 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 @@ | |||
1 | export * from './abstract-permanent-file-cache' | ||
2 | export * 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 @@ | |||
1 | import { join } from 'path' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
4 | import { CONFIG } from '../../initializers/config' | ||
5 | import { FILES_CACHE } from '../../initializers/constants' | ||
6 | import { VideoModel } from '../../models/video/video' | ||
7 | import { VideoCaptionModel } from '../../models/video/video-caption' | ||
8 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' | ||
9 | |||
10 | class 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 | |||
59 | export { | ||
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 @@ | |||
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/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 @@ | |||
1 | import { join } from 'path' | ||
2 | import { FILES_CACHE } from '../../initializers/constants' | ||
3 | import { VideoModel } from '../../models/video/video' | ||
4 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' | ||
5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
6 | import { ThumbnailModel } from '@server/models/video/thumbnail' | ||
7 | import { ThumbnailType } from '@shared/models' | ||
8 | import { logger } from '@server/helpers/logger' | ||
9 | |||
10 | class 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 | |||
56 | export { | ||
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 @@ | |||
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/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 @@ | |||
1 | import { join } from 'path' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
4 | import { VideoFileModel } from '@server/models/video/video-file' | ||
5 | import { MVideo, MVideoFile } from '@server/types/models' | ||
6 | import { CONFIG } from '../../initializers/config' | ||
7 | import { FILES_CACHE } from '../../initializers/constants' | ||
8 | import { VideoModel } from '../../models/video/video' | ||
9 | import { AbstractSimpleFileCache } from './shared/abstract-simple-file-cache' | ||
10 | |||
11 | class 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 | |||
68 | export { | ||
69 | VideoTorrentsSimpleFileCache | ||
70 | } | ||