aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-06-06 15:59:51 +0200
committerChocobozzz <me@florianbigard.com>2023-06-29 10:19:33 +0200
commitf162d32da098aa55f6de2367142faa166edb7c08 (patch)
tree31c6a96972994171853cb6c4e0b88b63241f8979 /server/lib/files-cache/shared/abstract-permanent-file-cache.ts
parenta673d9e848e51186602548a621e05925663b98be (diff)
downloadPeerTube-f162d32da098aa55f6de2367142faa166edb7c08.tar.gz
PeerTube-f162d32da098aa55f6de2367142faa166edb7c08.tar.zst
PeerTube-f162d32da098aa55f6de2367142faa166edb7c08.zip
Support lazy download thumbnails
Diffstat (limited to 'server/lib/files-cache/shared/abstract-permanent-file-cache.ts')
-rw-r--r--server/lib/files-cache/shared/abstract-permanent-file-cache.ts119
1 files changed, 119 insertions, 0 deletions
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..22596c3eb
--- /dev/null
+++ b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
@@ -0,0 +1,119 @@
1import express from 'express'
2import { LRUCache } from 'lru-cache'
3import { logger } from '@server/helpers/logger'
4import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants'
5import { downloadImageFromWorker } from '@server/lib/worker/parent-process'
6import { HttpStatusCode } from '@shared/models'
7import { Model } from 'sequelize'
8
9type ImageModel = {
10 fileUrl: string
11 filename: string
12 onDisk: boolean
13
14 isOwned (): boolean
15 getPath (): string
16
17 save (): Promise<Model>
18}
19
20export abstract class AbstractPermanentFileCache <M extends ImageModel> {
21 // Unsafe because it can return paths that do not exist anymore
22 private readonly filenameToPathUnsafeCache = new LRUCache<string, string>({
23 max: LRU_CACHE.FILENAME_TO_PATH_PERMANENT_FILE_CACHE.MAX_SIZE
24 })
25
26 protected abstract getImageSize (image: M): { width: number, height: number }
27 protected abstract loadModel (filename: string): Promise<M>
28
29 constructor (private readonly directory: string) {
30
31 }
32
33 async lazyServe (options: {
34 filename: string
35 res: express.Response
36 next: express.NextFunction
37 }) {
38 const { filename, res, next } = options
39
40 if (this.filenameToPathUnsafeCache.has(filename)) {
41 return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER })
42 }
43
44 const image = await this.loadModel(filename)
45 if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end()
46
47 if (image.onDisk === false) {
48 if (!image.fileUrl) return res.status(HttpStatusCode.NOT_FOUND_404).end()
49
50 try {
51 await this.downloadRemoteFile(image)
52 } catch (err) {
53 logger.warn('Cannot process remote image %s.', image.fileUrl, { err })
54
55 return res.status(HttpStatusCode.NOT_FOUND_404).end()
56 }
57 }
58
59 const path = image.getPath()
60 this.filenameToPathUnsafeCache.set(filename, path)
61
62 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }, (err: any) => {
63 if (!err) return
64
65 this.onServeError({ err, image, next, filename })
66 })
67 }
68
69 private async downloadRemoteFile (image: M) {
70 logger.info('Download remote image %s lazily.', image.fileUrl)
71
72 await this.downloadImage({
73 filename: image.filename,
74 fileUrl: image.fileUrl,
75 size: this.getImageSize(image)
76 })
77
78 image.onDisk = true
79 image.save()
80 .catch(err => logger.error('Cannot save new image disk state.', { err }))
81 }
82
83 private onServeError (options: {
84 err: any
85 image: M
86 filename: string
87 next: express.NextFunction
88 }) {
89 const { err, image, filename, next } = options
90
91 // It seems this actor image is not on the disk anymore
92 if (err.status === HttpStatusCode.NOT_FOUND_404 && !image.isOwned()) {
93 logger.error('Cannot lazy serve image %s.', filename, { err })
94
95 this.filenameToPathUnsafeCache.delete(filename)
96
97 image.onDisk = false
98 image.save()
99 .catch(err => logger.error('Cannot save new image disk state.', { err }))
100 }
101
102 return next(err)
103 }
104
105 private downloadImage (options: {
106 fileUrl: string
107 filename: string
108 size: { width: number, height: number }
109 }) {
110 const downloaderOptions = {
111 url: options.fileUrl,
112 destDir: this.directory,
113 destName: options.filename,
114 size: options.size
115 }
116
117 return downloadImageFromWorker(downloaderOptions)
118 }
119}