diff options
Diffstat (limited to 'server/lib/files-cache')
-rw-r--r-- | server/lib/files-cache/abstract-video-static-file-cache.ts | 30 | ||||
-rw-r--r-- | server/lib/files-cache/actor-follow-score-cache.ts | 46 | ||||
-rw-r--r-- | server/lib/files-cache/index.ts | 3 | ||||
-rw-r--r-- | server/lib/files-cache/videos-caption-cache.ts | 61 | ||||
-rw-r--r-- | server/lib/files-cache/videos-preview-cache.ts | 47 |
5 files changed, 187 insertions, 0 deletions
diff --git a/server/lib/files-cache/abstract-video-static-file-cache.ts b/server/lib/files-cache/abstract-video-static-file-cache.ts new file mode 100644 index 000000000..1908cfb06 --- /dev/null +++ b/server/lib/files-cache/abstract-video-static-file-cache.ts | |||
@@ -0,0 +1,30 @@ | |||
1 | import { remove } from 'fs-extra' | ||
2 | import { logger } from '../../helpers/logger' | ||
3 | import * as memoizee from 'memoizee' | ||
4 | |||
5 | type GetFilePathResult = { isOwned: boolean, path: string } | undefined | ||
6 | |||
7 | export abstract class AbstractVideoStaticFileCache <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.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/actor-follow-score-cache.ts b/server/lib/files-cache/actor-follow-score-cache.ts new file mode 100644 index 000000000..5f8ee806f --- /dev/null +++ b/server/lib/files-cache/actor-follow-score-cache.ts | |||
@@ -0,0 +1,46 @@ | |||
1 | import { ACTOR_FOLLOW_SCORE } from '../../initializers/constants' | ||
2 | import { logger } from '../../helpers/logger' | ||
3 | |||
4 | // Cache follows scores, instead of writing them too often in database | ||
5 | // Keep data in memory, we don't really need Redis here as we don't really care to loose some scores | ||
6 | class ActorFollowScoreCache { | ||
7 | |||
8 | private static instance: ActorFollowScoreCache | ||
9 | private pendingFollowsScore: { [ url: string ]: number } = {} | ||
10 | |||
11 | private constructor () {} | ||
12 | |||
13 | static get Instance () { | ||
14 | return this.instance || (this.instance = new this()) | ||
15 | } | ||
16 | |||
17 | updateActorFollowsScore (goodInboxes: string[], badInboxes: string[]) { | ||
18 | if (goodInboxes.length === 0 && badInboxes.length === 0) return | ||
19 | |||
20 | logger.info('Updating %d good actor follows and %d bad actor follows scores in cache.', goodInboxes.length, badInboxes.length) | ||
21 | |||
22 | for (const goodInbox of goodInboxes) { | ||
23 | if (this.pendingFollowsScore[goodInbox] === undefined) this.pendingFollowsScore[goodInbox] = 0 | ||
24 | |||
25 | this.pendingFollowsScore[goodInbox] += ACTOR_FOLLOW_SCORE.BONUS | ||
26 | } | ||
27 | |||
28 | for (const badInbox of badInboxes) { | ||
29 | if (this.pendingFollowsScore[badInbox] === undefined) this.pendingFollowsScore[badInbox] = 0 | ||
30 | |||
31 | this.pendingFollowsScore[badInbox] += ACTOR_FOLLOW_SCORE.PENALTY | ||
32 | } | ||
33 | } | ||
34 | |||
35 | getPendingFollowsScoreCopy () { | ||
36 | return this.pendingFollowsScore | ||
37 | } | ||
38 | |||
39 | clearPendingFollowsScore () { | ||
40 | this.pendingFollowsScore = {} | ||
41 | } | ||
42 | } | ||
43 | |||
44 | export { | ||
45 | ActorFollowScoreCache | ||
46 | } | ||
diff --git a/server/lib/files-cache/index.ts b/server/lib/files-cache/index.ts new file mode 100644 index 000000000..e921d04a7 --- /dev/null +++ b/server/lib/files-cache/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './actor-follow-score-cache' | ||
2 | export * from './videos-preview-cache' | ||
3 | export * from './videos-caption-cache' | ||
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts new file mode 100644 index 000000000..440c3fde8 --- /dev/null +++ b/server/lib/files-cache/videos-caption-cache.ts | |||
@@ -0,0 +1,61 @@ | |||
1 | import { join } from 'path' | ||
2 | import { FILES_CACHE } from '../../initializers/constants' | ||
3 | import { VideoModel } from '../../models/video/video' | ||
4 | import { VideoCaptionModel } from '../../models/video/video-caption' | ||
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | ||
6 | import { CONFIG } from '../../initializers/config' | ||
7 | import { logger } from '../../helpers/logger' | ||
8 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
9 | |||
10 | type GetPathParam = { videoId: string, language: string } | ||
11 | |||
12 | class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | ||
13 | |||
14 | private static readonly KEY_DELIMITER = '%' | ||
15 | private static instance: VideosCaptionCache | ||
16 | |||
17 | private constructor () { | ||
18 | super() | ||
19 | } | ||
20 | |||
21 | static get Instance () { | ||
22 | return this.instance || (this.instance = new this()) | ||
23 | } | ||
24 | |||
25 | async getFilePathImpl (params: GetPathParam) { | ||
26 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) | ||
27 | if (!videoCaption) return undefined | ||
28 | |||
29 | if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) } | ||
30 | |||
31 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language | ||
32 | return this.loadRemoteFile(key) | ||
33 | } | ||
34 | |||
35 | protected async loadRemoteFile (key: string) { | ||
36 | logger.debug('Loading remote caption file %s.', key) | ||
37 | |||
38 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) | ||
39 | |||
40 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) | ||
41 | if (!videoCaption) return undefined | ||
42 | |||
43 | if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.') | ||
44 | |||
45 | // Used to fetch the path | ||
46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | ||
47 | if (!video) return undefined | ||
48 | |||
49 | // FIXME: use URL | ||
50 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | ||
51 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) | ||
52 | |||
53 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | ||
54 | |||
55 | return { isOwned: false, path: destPath } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | export { | ||
60 | VideosCaptionCache | ||
61 | } | ||
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts new file mode 100644 index 000000000..14be7f24a --- /dev/null +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import { join } from 'path' | ||
2 | import { FILES_CACHE, STATIC_PATHS } from '../../initializers/constants' | ||
3 | import { VideoModel } from '../../models/video/video' | ||
4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | ||
5 | import { CONFIG } from '../../initializers/config' | ||
6 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
7 | |||
8 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | ||
9 | |||
10 | private static instance: VideosPreviewCache | ||
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 (videoUUID: string) { | ||
21 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) | ||
22 | if (!video) return undefined | ||
23 | |||
24 | if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) } | ||
25 | |||
26 | return this.loadRemoteFile(videoUUID) | ||
27 | } | ||
28 | |||
29 | protected async loadRemoteFile (key: string) { | ||
30 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(key) | ||
31 | if (!video) return undefined | ||
32 | |||
33 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') | ||
34 | |||
35 | // FIXME: use URL | ||
36 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) | ||
37 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename) | ||
38 | |||
39 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | ||
40 | |||
41 | return { isOwned: false, path: destPath } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | export { | ||
46 | VideosPreviewCache | ||
47 | } | ||