feat(plantuml): add cache supports

This commit is contained in:
purocean
2023-11-11 21:12:10 +08:00
parent f9f4ae0443
commit a7fa4ea3a6
2 changed files with 86 additions and 21 deletions
+2
View File
@@ -23,6 +23,8 @@ export const USER_DATA = path.join(USER_DIR, './data')
export const BIN_DIR = convertAppPath(path.join(__dirname, '../../bin'))
export const RESOURCES_DIR = convertAppPath(path.join(__dirname, 'resources'))
export const CACHE_DIR = path.join(os.tmpdir(), APP_NAME)
export const BUILD_IN_STYLES = ['github.css']
export const PANDOC_REFERENCE_FILE = 'pandoc-reference.docx'
+84 -21
View File
@@ -1,11 +1,12 @@
import request from 'request'
import fs from 'fs'
import * as crypto from 'crypto'
import fs from 'fs-extra'
import path from 'path'
import pako from 'pako'
import { PlantUmlPipe } from 'plantuml-pipe'
import commandExists from 'command-exists'
import config from '../config'
import { ASSETS_DIR, BIN_DIR } from '../constant'
import { ASSETS_DIR, BIN_DIR, CACHE_DIR } from '../constant'
import { getAction } from '../action'
function plantumlBase64 (base64: string) {
@@ -14,9 +15,57 @@ function plantumlBase64 (base64: string) {
return base64.split('').map(x => map[x] || '').join('')
}
export default async function (data: string): Promise<{ content: any, type: string }> {
const code = pako.inflateRaw(Buffer.from(data, 'base64'))
function getCacheKey (api: string, type: string, data: string) {
return crypto.createHash('sha256').update(api + type + data).digest('hex')
}
async function gcCache (cacheDir: string) {
const files = await fs.readdir(cacheDir)
if (files.length < 4000) {
return
}
const stats = await Promise.all(files.map(file => fs.stat(path.join(cacheDir, file))))
stats.sort((a, b) => a.atimeMs - b.atimeMs)
for (let i = 0; i < stats.length / 2; i++) {
await fs.remove(path.join(cacheDir, files[i]))
}
}
async function getCacheData (key: string, gen: () => Promise<any>) {
const cacheDir = path.join(CACHE_DIR, 'plantuml')
await fs.ensureDir(cacheDir)
gcCache(cacheDir)
const cacheFile = path.join(cacheDir, key)
if (await fs.pathExists(cacheFile)) {
const stat = await fs.stat(cacheFile)
if (stat.size) {
return fs.createReadStream(cacheFile)
}
}
const data = await gen()
if (!data) {
throw new Error('No data')
}
if (typeof data.pipe === 'function') {
await new Promise((resolve, reject) => {
data.on('end', resolve)
data.on('error', reject)
data.pipe(fs.createWriteStream(cacheFile))
})
return fs.createReadStream(cacheFile)
} else {
await fs.writeFile(cacheFile, data)
return fs.createReadStream(cacheFile)
}
}
export default async function (data: string): Promise<{ content: any, type: string }> {
const api: string = config.get('plantuml-api', 'local')
if (api.startsWith('local')) {
@@ -28,30 +77,44 @@ export default async function (data: string): Promise<{ content: any, type: stri
const format = api.split('-')[1] || 'png'
const type = format === 'png' ? 'image/png' : 'image/svg+xml'
const jarPath = path.join(BIN_DIR, 'plantuml.jar')
const puml = new PlantUmlPipe({
split: format === 'svg',
outputFormat: format as 'png' | 'svg',
plantUmlArgs: ['-charset', 'UTF-8'],
jarPath,
const cacheKey = getCacheKey(api, type, data)
const content = await getCacheData(cacheKey, async () => {
const jarPath = path.join(BIN_DIR, 'plantuml.jar')
const puml = new PlantUmlPipe({
split: format === 'svg',
outputFormat: format as 'png' | 'svg',
plantUmlArgs: ['-charset', 'UTF-8'],
jarPath,
})
puml.in.write(pako.inflateRaw(Buffer.from(data, 'base64')))
puml.in.end()
return puml.out
})
puml.in.write(code)
puml.in.end()
return { content: puml.out, type }
return { content, type }
} else {
const url = api.replace('{data}', plantumlBase64(data))
const agent = await getAction('get-proxy-agent')(url)
return new Promise((resolve, reject) => {
request({ agent, url, encoding: null }, function (err: any, res: any) {
if (err) {
reject(err)
} else {
resolve({ content: res.body, type: res.headers['content-type'] })
}
let type = ''
const cacheKey = getCacheKey(api, type, data)
const content = await getCacheData(cacheKey, async () => {
return new Promise((resolve, reject) => {
request({ agent, url, encoding: null }, function (err: any, res: any) {
if (err) {
reject(err)
} else {
type = res.headers['content-type']
resolve(res.body)
}
})
})
})
return { content, type }
}
}