Use promise cache to load remote thumbnails
This commit is contained in:
parent
2b5dfa2fe0
commit
cf069671f4
|
@ -1,4 +1,4 @@
|
||||||
export class PromiseCache <A, R> {
|
export class CachePromiseFactory <A, R> {
|
||||||
private readonly running = new Map<string, Promise<R>>()
|
private readonly running = new Map<string, Promise<R>>()
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
@ -8,14 +8,32 @@ export class PromiseCache <A, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
run (arg: A) {
|
run (arg: A) {
|
||||||
|
return this.runWithContext(null, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
runWithContext (ctx: any, arg: A) {
|
||||||
const key = this.keyBuilder(arg)
|
const key = this.keyBuilder(arg)
|
||||||
|
|
||||||
if (this.running.has(key)) return this.running.get(key)
|
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)
|
this.running.set(key, p)
|
||||||
|
|
||||||
return p.finally(() => this.running.delete(key))
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
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 { PeerTubeRequestError } from '@server/helpers/requests'
|
||||||
import { ActorLoadByUrlType } from '@server/lib/model-loaders'
|
import { ActorLoadByUrlType } from '@server/lib/model-loaders'
|
||||||
import { ActorModel } from '@server/models/actor/actor'
|
import { ActorModel } from '@server/models/actor/actor'
|
||||||
|
@ -16,7 +16,7 @@ type RefreshOptions <T> = {
|
||||||
fetchedType: ActorLoadByUrlType
|
fetchedType: ActorLoadByUrlType
|
||||||
}
|
}
|
||||||
|
|
||||||
const promiseCache = new PromiseCache(doRefresh, (options: RefreshOptions<MActorFull | MActorAccountChannelId>) => options.actor.url)
|
const promiseCache = new CachePromiseFactory(doRefresh, (options: RefreshOptions<MActorFull | MActorAccountChannelId>) => options.actor.url)
|
||||||
|
|
||||||
function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (options: RefreshOptions<T>): RefreshResult <T> {
|
function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (options: RefreshOptions<T>): RefreshResult <T> {
|
||||||
const actorArg = options.actor
|
const actorArg = options.actor
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { LRUCache } from 'lru-cache'
|
import { LRUCache } from 'lru-cache'
|
||||||
|
import { Model } from 'sequelize'
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { CachePromise } from '@server/helpers/promise-cache'
|
||||||
import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants'
|
import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants'
|
||||||
import { downloadImageFromWorker } from '@server/lib/worker/parent-process'
|
import { downloadImageFromWorker } from '@server/lib/worker/parent-process'
|
||||||
import { HttpStatusCode } from '@shared/models'
|
import { HttpStatusCode } from '@shared/models'
|
||||||
import { Model } from 'sequelize'
|
|
||||||
|
|
||||||
type ImageModel = {
|
type ImageModel = {
|
||||||
fileUrl: string
|
fileUrl: string
|
||||||
|
@ -41,21 +42,9 @@ export abstract class AbstractPermanentFileCache <M extends ImageModel> {
|
||||||
return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER })
|
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) 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()
|
const path = image.getPath()
|
||||||
this.filenameToPathUnsafeCache.set(filename, path)
|
this.filenameToPathUnsafeCache.set(filename, path)
|
||||||
|
|
||||||
|
@ -66,6 +55,28 @@ export abstract class AbstractPermanentFileCache <M extends ImageModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
async downloadRemoteFile (image: M) {
|
||||||
logger.info('Download remote image %s lazily.', image.fileUrl)
|
logger.info('Download remote image %s lazily.', image.fileUrl)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue