diff --git a/server/helpers/promise-cache.ts b/server/helpers/promise-cache.ts
index 07e8a9962..303bab976 100644
--- a/server/helpers/promise-cache.ts
+++ b/server/helpers/promise-cache.ts
@@ -1,4 +1,4 @@
-export class PromiseCache {
+export class CachePromiseFactory {
private readonly running = new Map>()
constructor (
@@ -8,14 +8,32 @@ export class PromiseCache {
}
run (arg: A) {
+ return this.runWithContext(null, arg)
+ }
+
+ runWithContext (ctx: any, arg: A) {
const key = this.keyBuilder(arg)
if (this.running.has(key)) return this.running.get(key)
- const p = this.fn(arg)
+ const p = this.fn.apply(ctx || this, [ arg ])
this.running.set(key, p)
return p.finally(() => this.running.delete(key))
}
}
+
+export function CachePromise (options: {
+ keyBuilder: (...args: any[]) => string
+}) {
+ return function (_target, _key, descriptor: PropertyDescriptor) {
+ const promiseCache = new CachePromiseFactory(descriptor.value, options.keyBuilder)
+
+ descriptor.value = function () {
+ if (arguments.length !== 1) throw new Error('Cache promise only support methods with 1 argument')
+
+ return promiseCache.runWithContext(this, arguments[0])
+ }
+ }
+}
diff --git a/server/lib/activitypub/actors/refresh.ts b/server/lib/activitypub/actors/refresh.ts
index 6d8428d66..d15cb5e90 100644
--- a/server/lib/activitypub/actors/refresh.ts
+++ b/server/lib/activitypub/actors/refresh.ts
@@ -1,5 +1,5 @@
import { logger, loggerTagsFactory } from '@server/helpers/logger'
-import { PromiseCache } from '@server/helpers/promise-cache'
+import { CachePromiseFactory } from '@server/helpers/promise-cache'
import { PeerTubeRequestError } from '@server/helpers/requests'
import { ActorLoadByUrlType } from '@server/lib/model-loaders'
import { ActorModel } from '@server/models/actor/actor'
@@ -16,7 +16,7 @@ type RefreshOptions = {
fetchedType: ActorLoadByUrlType
}
-const promiseCache = new PromiseCache(doRefresh, (options: RefreshOptions) => options.actor.url)
+const promiseCache = new CachePromiseFactory(doRefresh, (options: RefreshOptions) => options.actor.url)
function refreshActorIfNeeded (options: RefreshOptions): RefreshResult {
const actorArg = options.actor
diff --git a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
index 297461035..f990e9872 100644
--- a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
+++ b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts
@@ -1,10 +1,11 @@
import express from 'express'
import { LRUCache } from 'lru-cache'
+import { Model } from 'sequelize'
import { logger } from '@server/helpers/logger'
+import { CachePromise } from '@server/helpers/promise-cache'
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
@@ -41,21 +42,9 @@ export abstract class AbstractPermanentFileCache {
return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER })
}
- const image = await this.loadModel(filename)
+ const image = await this.lazyLoadIfNeeded(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)
@@ -66,6 +55,28 @@ export abstract class AbstractPermanentFileCache {
})
}
+ @CachePromise({
+ keyBuilder: filename => filename
+ })
+ private async lazyLoadIfNeeded (filename: string) {
+ const image = await this.loadModel(filename)
+ if (!image) return undefined
+
+ if (image.onDisk === false) {
+ if (!image.fileUrl) return undefined
+
+ try {
+ await this.downloadRemoteFile(image)
+ } catch (err) {
+ logger.warn('Cannot process remote image %s.', image.fileUrl, { err })
+
+ return undefined
+ }
+ }
+
+ return image
+ }
+
async downloadRemoteFile (image: M) {
logger.info('Download remote image %s lazily.', image.fileUrl)