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