From 5a5c335cf673a81638d0ba1e9145bf69d65cb6cb Mon Sep 17 00:00:00 2001 From: Ekaterina Berezhko Date: Thu, 9 May 2024 13:28:45 +0300 Subject: [PATCH] [Markdown][IJPL-90975] Replace file system cache for plantUML extension GitOrigin-RevId: f1c843d6060e9158f7245592f0b8730732cd68b5 --- plugins/markdown/core/intellij.markdown.iml | 1 + .../PlantUMLCodeGeneratingProvider.kt | 71 ++++++++--------- .../ui/preview/PreviewStaticServer.kt | 3 +- .../data/extensions/plantuml/plantUML1.md | 5 -- .../data/extensions/plantuml/plantUML2.md | 3 - .../test/data/extensions/plantuml/puml.md | 5 -- .../extensions/PlantUmlGenerationTest.kt | 77 ------------------- 7 files changed, 38 insertions(+), 127 deletions(-) delete mode 100644 plugins/markdown/test/data/extensions/plantuml/plantUML1.md delete mode 100644 plugins/markdown/test/data/extensions/plantuml/plantUML2.md delete mode 100644 plugins/markdown/test/data/extensions/plantuml/puml.md delete mode 100644 plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/PlantUmlGenerationTest.kt diff --git a/plugins/markdown/core/intellij.markdown.iml b/plugins/markdown/core/intellij.markdown.iml index 2839d1456a9a..7d180994b4e9 100644 --- a/plugins/markdown/core/intellij.markdown.iml +++ b/plugins/markdown/core/intellij.markdown.iml @@ -34,5 +34,6 @@ + \ No newline at end of file diff --git a/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/common/plantuml/PlantUMLCodeGeneratingProvider.kt b/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/common/plantuml/PlantUMLCodeGeneratingProvider.kt index 8e2275a30cdb..a9e1af409419 100644 --- a/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/common/plantuml/PlantUMLCodeGeneratingProvider.kt +++ b/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/common/plantuml/PlantUMLCodeGeneratingProvider.kt @@ -1,25 +1,25 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.intellij.plugins.markdown.extensions.common.plantuml -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.util.io.FileUtil +import com.github.benmanes.caffeine.cache.Caffeine import com.intellij.openapi.util.registry.Registry import org.intellij.markdown.ast.ASTNode import org.intellij.plugins.markdown.MarkdownBundle +import org.intellij.plugins.markdown.extensions.CodeFenceGeneratingProvider import org.intellij.plugins.markdown.extensions.MarkdownBrowserPreviewExtension -import org.intellij.plugins.markdown.extensions.MarkdownCodeFenceCacheableProvider import org.intellij.plugins.markdown.extensions.MarkdownExtensionWithDownloadableFiles import org.intellij.plugins.markdown.extensions.MarkdownExtensionWithDownloadableFiles.FileEntry import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel -import org.intellij.plugins.markdown.ui.preview.html.MarkdownCodeFencePluginCacheCollector +import org.intellij.plugins.markdown.ui.preview.html.MarkdownUtil import org.jetbrains.annotations.ApiStatus -import java.io.File +import java.io.ByteArrayOutputStream import java.io.IOException +import java.util.* @ApiStatus.Internal -class PlantUMLCodeGeneratingProvider( - collector: MarkdownCodeFencePluginCacheCollector? = null -): MarkdownCodeFenceCacheableProvider(collector), MarkdownExtensionWithDownloadableFiles, MarkdownBrowserPreviewExtension.Provider { +class PlantUMLCodeGeneratingProvider: CodeFenceGeneratingProvider, MarkdownExtensionWithDownloadableFiles, MarkdownBrowserPreviewExtension.Provider { + private val cache = Caffeine.newBuilder().softValues().build() + override val externalFiles: Iterable get() = ownFiles @@ -31,10 +31,21 @@ class PlantUMLCodeGeneratingProvider( } override fun generateHtml(language: String, raw: String, node: ASTNode): String { - val key = getUniqueFile(language.lowercase(), raw, "png").toFile() - cacheDiagram(key, raw) - collector?.addAliveCachedFile(this, key) - return "" + val content = obtainGeneratedContent(raw) + val header = "data:image/png;base64," + return """""" + } + + // Not thread safe + private fun obtainGeneratedContent(raw: String): String { + val key = MarkdownUtil.md5(raw, "salt") + val cached = cache.getIfPresent(key) + if (cached != null) { + return cached + } + val generated = generateDiagram(raw) + cache.put(key, generated) + return generated } override val displayName: String @@ -60,36 +71,26 @@ class PlantUMLCodeGeneratingProvider( return null } - private fun cacheDiagram(path: File, text: String) { - if (!path.exists()) { - generateDiagram(text, path) - } - } - @Throws(IOException::class) - private fun generateDiagram(text: CharSequence, diagramPath: File) { - var innerText: String = text.toString().trim() - if (!innerText.startsWith("@startuml")) { - innerText = "@startuml\n$innerText" + private fun generateDiagram(text: CharSequence): String { + val content = buildString { + if (!text.startsWith("@startuml")) { + append("@startuml\n") + } + append(text) + if (!text.endsWith("@enduml")) { + append("\n@enduml") + } } - if (!innerText.endsWith("@enduml")) { - innerText += "\n@enduml" - } - FileUtil.createParentDirs(diagramPath) - storeDiagram(innerText, diagramPath) + val stream = ByteArrayOutputStream() + PlantUMLJarManager.getInstance().generateImage(content, stream) + val encodedContent = Base64.getEncoder().encode(stream.toByteArray()) + return encodedContent.toString(Charsets.UTF_8) } companion object { const val jarFilename = "plantuml.jar" private val ownFiles = listOf(jarFilename) private val dowloadableFiles = listOf(FileEntry(jarFilename) { Registry.stringValue("markdown.plantuml.download.link") }) - - private fun storeDiagram(source: String, file: File) { - try { - file.outputStream().buffered().use { PlantUMLJarManager.getInstance().generateImage(source, it) } - } catch (exception: Exception) { - thisLogger().warn("Cannot save diagram PlantUML diagram. ", exception) - } - } } } diff --git a/plugins/markdown/core/src/org/intellij/plugins/markdown/ui/preview/PreviewStaticServer.kt b/plugins/markdown/core/src/org/intellij/plugins/markdown/ui/preview/PreviewStaticServer.kt index 2a61e1127b7e..f6cf3ab12c1f 100644 --- a/plugins/markdown/core/src/org/intellij/plugins/markdown/ui/preview/PreviewStaticServer.kt +++ b/plugins/markdown/core/src/org/intellij/plugins/markdown/ui/preview/PreviewStaticServer.kt @@ -9,7 +9,6 @@ import io.netty.buffer.Unpooled import io.netty.channel.Channel import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.http.* -import org.jetbrains.annotations.ApiStatus import org.jetbrains.ide.BuiltInServerManager.Companion.getInstance import org.jetbrains.ide.HttpRequestHandler import org.jetbrains.io.FileResponses.checkCache @@ -96,7 +95,7 @@ class PreviewStaticServer : HttpRequestHandler() { default-src 'none'; script-src ${StringUtil.join(scripts.map(::stripQueryParameters), " ")}; style-src https: ${StringUtil.join(styles.map(::stripQueryParameters), " ")} 'unsafe-inline'; - img-src file: *; connect-src 'none'; font-src * data: *; + img-src file: * data:; connect-src 'none'; font-src * data: *; object-src 'none'; media-src 'none'; child-src 'none'; """ } diff --git a/plugins/markdown/test/data/extensions/plantuml/plantUML1.md b/plugins/markdown/test/data/extensions/plantuml/plantUML1.md deleted file mode 100644 index 99c16bf2b7b3..000000000000 --- a/plugins/markdown/test/data/extensions/plantuml/plantUML1.md +++ /dev/null @@ -1,5 +0,0 @@ -```plantuml -@startuml -Alice -> Bob: test -@enduml -``` \ No newline at end of file diff --git a/plugins/markdown/test/data/extensions/plantuml/plantUML2.md b/plugins/markdown/test/data/extensions/plantuml/plantUML2.md deleted file mode 100644 index 4a516912078e..000000000000 --- a/plugins/markdown/test/data/extensions/plantuml/plantUML2.md +++ /dev/null @@ -1,3 +0,0 @@ -```plantuml -Alice -> Bob: test -``` \ No newline at end of file diff --git a/plugins/markdown/test/data/extensions/plantuml/puml.md b/plugins/markdown/test/data/extensions/plantuml/puml.md deleted file mode 100644 index da24db2050c1..000000000000 --- a/plugins/markdown/test/data/extensions/plantuml/puml.md +++ /dev/null @@ -1,5 +0,0 @@ -```puml -@startuml -Alice -> Bob: test -@enduml -``` \ No newline at end of file diff --git a/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/PlantUmlGenerationTest.kt b/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/PlantUmlGenerationTest.kt deleted file mode 100644 index 9119c8be660d..000000000000 --- a/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/PlantUmlGenerationTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.intellij.plugins.markdown.extensions - -import com.intellij.openapi.Disposable -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.vfs.VfsUtilCore -import com.intellij.testFramework.LightPlatformCodeInsightTestCase -import com.intellij.testFramework.rules.TempDirectory -import org.intellij.plugins.markdown.MarkdownTestingUtil -import org.intellij.plugins.markdown.extensions.common.plantuml.PlantUMLCodeGeneratingProvider -import org.intellij.plugins.markdown.extensions.common.plantuml.PlantUMLJarManager -import org.intellij.plugins.markdown.ui.preview.html.MarkdownCodeFenceHtmlCache -import org.intellij.plugins.markdown.ui.preview.html.MarkdownUtil -import org.junit.Rule -import org.junit.Test -import org.junit.rules.ExternalResource -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import java.util.* - - -@RunWith(JUnit4::class) -class PlantUmlGenerationTest: LightPlatformCodeInsightTestCase() { - private val tempDirectory = TempDirectory() - - @Rule - @JvmField - val rules: TestRule = RuleChain.outerRule(tempDirectory).around(SetupRule(tempDirectory)) - - class SetupRule(private val tempDirectory: TempDirectory): ExternalResource() { - private var disposable: Disposable? = null - - override fun before() { - disposable = Disposer.newDisposable() - ExtensionTestingUtil.mockPathManager(tempDirectory.newDirectoryPath(), disposable!!) - val extension = requireNotNull(MarkdownExtensionsUtil.findCodeFenceGeneratingProvider()) - PlantUMLJarManager.getInstance().dropCache() - ExtensionTestingUtil.downloadExtension(extension, project = null) - val state = Collections.singletonMap(extension.id, true) - ExtensionTestingUtil.replaceExtensionsState(state, disposable!!) - } - - override fun after() { - disposable?.let(Disposer::dispose) - disposable = null - PlantUMLJarManager.getInstance().dropCache() - } - } - - @Test - fun `test plantUML1`() = doTest() - - @Test - fun `test plantUML2`() = doTest() - - @Test - fun `test puml`() = doTest() - - private fun doTest() { - val virtualFile = getVirtualFile("${getTestName(true)}.md") - val result = MarkdownUtil.generateMarkdownHtml(virtualFile, VfsUtilCore.loadText(virtualFile), project) - val hash = MarkdownUtil.md5(virtualFile.path, MarkdownCodeFenceHtmlCache.MARKDOWN_FILE_PATH_KEY) - println(result) - assertTrue(result.contains(hash)) - } - - override fun getTestName(lowercaseFirstLetter: Boolean): String { - val name = super.getTestName(lowercaseFirstLetter) - return name.trimStart().replace(' ', '_') - } - - override fun getTestDataPath(): String { - return "${MarkdownTestingUtil.TEST_DATA_PATH}/extensions/plantuml/" - } -}