aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/cache
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/cache')
-rw-r--r--server/lib/cache/abstract-video-static-file-cache.ts54
-rw-r--r--server/lib/cache/videos-caption-cache.ts53
-rw-r--r--server/lib/cache/videos-preview-cache.ts60
3 files changed, 121 insertions, 46 deletions
diff --git a/server/lib/cache/abstract-video-static-file-cache.ts b/server/lib/cache/abstract-video-static-file-cache.ts
new file mode 100644
index 000000000..7eeeb6b3a
--- /dev/null
+++ b/server/lib/cache/abstract-video-static-file-cache.ts
@@ -0,0 +1,54 @@
1import * as AsyncLRU from 'async-lru'
2import { createWriteStream } from 'fs'
3import { join } from 'path'
4import { unlinkPromise } from '../../helpers/core-utils'
5import { logger } from '../../helpers/logger'
6import { CACHE, CONFIG } from '../../initializers'
7import { VideoModel } from '../../models/video/video'
8import { fetchRemoteVideoStaticFile } from '../activitypub'
9import { VideoCaptionModel } from '../../models/video/video-caption'
10
11export abstract class AbstractVideoStaticFileCache <T> {
12
13 protected lru
14
15 abstract getFilePath (params: T): Promise<string>
16
17 // Load and save the remote file, then return the local path from filesystem
18 protected abstract loadRemoteFile (key: string): Promise<string>
19
20 init (max: number) {
21 this.lru = new AsyncLRU({
22 max,
23 load: (key, cb) => {
24 this.loadRemoteFile(key)
25 .then(res => cb(null, res))
26 .catch(err => cb(err))
27 }
28 })
29
30 this.lru.on('evict', (obj: { key: string, value: string }) => {
31 unlinkPromise(obj.value).then(() => logger.debug('%s evicted from %s', obj.value, this.constructor.name))
32 })
33 }
34
35 protected loadFromLRU (key: string) {
36 return new Promise<string>((res, rej) => {
37 this.lru.get(key, (err, value) => {
38 err ? rej(err) : res(value)
39 })
40 })
41 }
42
43 protected saveRemoteVideoFileAndReturnPath (video: VideoModel, remoteStaticPath: string, destPath: string) {
44 return new Promise<string>((res, rej) => {
45 const req = fetchRemoteVideoStaticFile(video, remoteStaticPath, rej)
46
47 const stream = createWriteStream(destPath)
48
49 req.pipe(stream)
50 .on('error', (err) => rej(err))
51 .on('finish', () => res(destPath))
52 })
53 }
54}
diff --git a/server/lib/cache/videos-caption-cache.ts b/server/lib/cache/videos-caption-cache.ts
new file mode 100644
index 000000000..1336610b2
--- /dev/null
+++ b/server/lib/cache/videos-caption-cache.ts
@@ -0,0 +1,53 @@
1import { join } from 'path'
2import { CACHE, CONFIG } from '../../initializers'
3import { VideoModel } from '../../models/video/video'
4import { VideoCaptionModel } from '../../models/video/video-caption'
5import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
6
7type GetPathParam = { videoId: string, language: string }
8
9class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
10
11 private static readonly KEY_DELIMITER = '%'
12 private static instance: VideosCaptionCache
13
14 private constructor () {
15 super()
16 }
17
18 static get Instance () {
19 return this.instance || (this.instance = new this())
20 }
21
22 async getFilePath (params: GetPathParam) {
23 const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language)
24 if (!videoCaption) return undefined
25
26 if (videoCaption.isOwned()) return join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName())
27
28 const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language
29 return this.loadFromLRU(key)
30 }
31
32 protected async loadRemoteFile (key: string) {
33 const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER)
34
35 const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language)
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.loadByUUIDAndPopulateAccountAndServerAndTags(videoId)
42 if (!video) return undefined
43
44 const remoteStaticPath = videoCaption.getCaptionStaticPath()
45 const destPath = join(CACHE.DIRECTORIES.VIDEO_CAPTIONS, videoCaption.getCaptionName())
46
47 return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
48 }
49}
50
51export {
52 VideosCaptionCache
53}
diff --git a/server/lib/cache/videos-preview-cache.ts b/server/lib/cache/videos-preview-cache.ts
index d09d55e11..1c0e7ed9d 100644
--- a/server/lib/cache/videos-preview-cache.ts
+++ b/server/lib/cache/videos-preview-cache.ts
@@ -1,71 +1,39 @@
1import * as asyncLRU from 'async-lru'
2import { createWriteStream } from 'fs'
3import { join } from 'path' 1import { join } from 'path'
4import { unlinkPromise } from '../../helpers/core-utils' 2import { CACHE, CONFIG, STATIC_PATHS } from '../../initializers'
5import { logger } from '../../helpers/logger'
6import { CACHE, CONFIG } from '../../initializers'
7import { VideoModel } from '../../models/video/video' 3import { VideoModel } from '../../models/video/video'
8import { fetchRemoteVideoPreview } from '../activitypub' 4import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
9 5
10class VideosPreviewCache { 6class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
11 7
12 private static instance: VideosPreviewCache 8 private static instance: VideosPreviewCache
13 9
14 private lru 10 private constructor () {
15 11 super()
16 private constructor () { } 12 }
17 13
18 static get Instance () { 14 static get Instance () {
19 return this.instance || (this.instance = new this()) 15 return this.instance || (this.instance = new this())
20 } 16 }
21 17
22 init (max: number) { 18 async getFilePath (videoUUID: string) {
23 this.lru = new asyncLRU({ 19 const video = await VideoModel.loadByUUID(videoUUID)
24 max,
25 load: (key, cb) => {
26 this.loadPreviews(key)
27 .then(res => cb(null, res))
28 .catch(err => cb(err))
29 }
30 })
31
32 this.lru.on('evict', (obj: { key: string, value: string }) => {
33 unlinkPromise(obj.value).then(() => logger.debug('%s evicted from VideosPreviewCache', obj.value))
34 })
35 }
36
37 async getPreviewPath (key: string) {
38 const video = await VideoModel.loadByUUID(key)
39 if (!video) return undefined 20 if (!video) return undefined
40 21
41 if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()) 22 if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName())
42 23
43 return new Promise<string>((res, rej) => { 24 return this.loadFromLRU(videoUUID)
44 this.lru.get(key, (err, value) => {
45 err ? rej(err) : res(value)
46 })
47 })
48 } 25 }
49 26
50 private async loadPreviews (key: string) { 27 protected async loadRemoteFile (key: string) {
51 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key) 28 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key)
52 if (!video) return undefined 29 if (!video) return undefined
53 30
54 if (video.isOwned()) throw new Error('Cannot load preview of owned video.') 31 if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.')
55
56 return this.saveRemotePreviewAndReturnPath(video)
57 }
58 32
59 private saveRemotePreviewAndReturnPath (video: VideoModel) { 33 const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
60 return new Promise<string>((res, rej) => { 34 const destPath = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName())
61 const req = fetchRemoteVideoPreview(video, rej)
62 const path = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName())
63 const stream = createWriteStream(path)
64 35
65 req.pipe(stream) 36 return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
66 .on('error', (err) => rej(err))
67 .on('finish', () => res(path))
68 })
69 } 37 }
70} 38}
71 39