From 34111e894433c6e8f91181cc27b8af742eb9b5d3 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 7 Jun 2018 15:43:16 +0300 Subject: [PATCH] Prototype verify extension and apply it for Kotlin updater in AS 3.2 --- idea/src/META-INF/plugin.xml | 3 + idea/src/META-INF/plugin.xml.172 | 3 + idea/src/META-INF/plugin.xml.173 | 3 + idea/src/META-INF/plugin.xml.182 | 3 + idea/src/META-INF/plugin.xml.as31 | 3 + idea/src/META-INF/plugin.xml.as32 | 5 + .../kotlin/idea/KotlinPluginUpdater.kt | 11 +- .../KotlinLanguageConfiguration.kt | 5 + .../update/GooglePluginUpdateVerifier.kt.as32 | 147 ++++++++++++++++++ .../idea/update/PluginUpdateVerifier.kt | 21 +++ .../kotlin/idea/update/PluginVerifyResult.kt | 25 +++ .../jetbrains/kotlin/idea/update/verify.kt | 13 ++ .../kotlin/idea/update/verify.kt.as32 | 32 ++++ 13 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 create mode 100644 idea/src/org/jetbrains/kotlin/idea/update/PluginUpdateVerifier.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/update/PluginVerifyResult.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/update/verify.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/update/verify.kt.as32 diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index d07033c49ff..3a1a95a66f2 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2946,6 +2946,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 0b6483e17f2..f11b9cd2837 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -2946,6 +2946,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 37720d79315..c0a5e4a529f 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -2944,6 +2944,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 329fc96bf59..a0a0d901209 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2945,6 +2945,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 3b3cadef1c6..db1b77e1f00 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2944,6 +2944,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index 73934004f5c..0d2c6928645 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2944,6 +2944,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -2971,6 +2974,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt index ecb56b9a008..3bf866732c9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt @@ -27,6 +27,8 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.Alarm import com.intellij.util.io.HttpRequests import com.intellij.util.text.VersionComparatorUtil +import org.jetbrains.kotlin.idea.update.PluginUpdateVerifier +import org.jetbrains.kotlin.idea.update.verify import java.io.File import java.io.IOException import java.io.PrintWriter @@ -46,6 +48,8 @@ sealed class PluginUpdateStatus { class CheckFailed(val message: String, val detail: String? = null) : PluginUpdateStatus() + class Unverified(val message: String, val updateStatus: Update) : PluginUpdateStatus() + fun mergeWith(other: PluginUpdateStatus): PluginUpdateStatus { if (other is Update) { when (this) { @@ -57,7 +61,7 @@ sealed class PluginUpdateStatus { return other } } - is PluginUpdateStatus.CheckFailed -> { + is CheckFailed, is Unverified -> { // proceed to return this } } @@ -150,9 +154,14 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos lastUpdateStatus = updateStatus checkQueued = false + if (updateStatus is PluginUpdateStatus.Update) { + updateStatus = verify(updateStatus) + } + if (updateStatus !is PluginUpdateStatus.CheckFailed) { recordSuccessfulUpdateCheck() } + ApplicationManager.getApplication().invokeLater({ callback(updateStatus) }, ModalityState.any()) diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt index ea2abf70c92..e8101bc181e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt @@ -156,6 +156,11 @@ class KotlinUpdatesSettingsConfigurable : SearchableConfigurable, Configurable.N is PluginUpdateStatus.CheckFailed -> form.setUpdateStatus("Update check failed: ${pluginUpdateStatus.message}", false) + + is PluginUpdateStatus.Unverified -> { + val version = pluginUpdateStatus.updateStatus.pluginDescriptor.version + form.setUpdateStatus("A new version $version is found. ${pluginUpdateStatus.message}", false) + } } false // do not auto-retry update check diff --git a/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 b/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 new file mode 100644 index 00000000000..4456b06746e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 @@ -0,0 +1,147 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.update + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import org.intellij.lang.annotations.Language +import java.util.* +import javax.xml.bind.JAXBContext +import javax.xml.bind.annotation.* +import kotlin.collections.HashSet + +class GooglePluginUpdateVerifier : PluginUpdateVerifier() { + override fun verify(pluginDescriptor: IdeaPluginDescriptor): PluginVerifyResult? { + if (pluginDescriptor.pluginId.idString != "org.jetbrains.kotlin") { + return null + } + + // val approvedKotlinVersions = fetchApprovedKotlinVersions() + // if (pluginDescriptor.version !in approvedKotlinVersions) { + // // TODO: Modify message + // return PluginVerifyResult.decline("Plugin is waiting for Google approve.") + // } + + return PluginVerifyResult.accept() + } + + private fun fetchApprovedKotlinVersions(): Set { + // TODO: Choose the host and do an actual request + // TODO: Fetching the full list each time might be time-consuming + // TODO: Process parsing and fetching errors + + // TODO: Emulate long operation to check progress indicators. Remove after actual implementation is ready. + Thread.sleep(200) + + @Language("XML") + val answer = """ + + + + + + + + + + + + + + + + """.trimIndent() + + val context = JAXBContext.newInstance(PluginCompatibility::class.java) + val unmarshaller = context.createUnmarshaller() + val pluginCompatibility = unmarshaller.unmarshal(answer.byteInputStream()) as PluginCompatibility + + val approvedKotlinVersions = HashSet() + for (studioRelease in pluginCompatibility.releases()) { + for (ideaPlugin in studioRelease.plugins()) { + if (ideaPlugin.id == KOTLIN_PLUGIN_ID) { + val version = ideaPlugin.version + if (version != null) { + approvedKotlinVersions.add(version) + } + } + } + } + + return approvedKotlinVersions + } + + companion object { + private const val KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin" + + private fun PluginCompatibility.releases() = studioRelease ?: emptyArray() + private fun StudioRelease.plugins() = ideaPlugin ?: emptyArray() + + @XmlRootElement(name = "plugin-compatibility") + @XmlAccessorType(XmlAccessType.FIELD) + class PluginCompatibility { + @XmlElement(name = "studio-release") + var studioRelease: Array? = null + + override fun toString(): String { + return "PluginCompatibility(studioRelease=${Arrays.toString(studioRelease)})" + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + class StudioRelease { + @XmlAttribute(name = "until-build") + var untilBuild: String? = null + @XmlAttribute(name = "since-build") + var sinceBuild: String? = null + @XmlAttribute + var name: String? = null + @XmlAttribute + var channel: String? = null + + @XmlElement(name = "idea-plugin") + var ideaPlugin: Array? = null + + override fun toString(): String { + return "StudioRelease(" + + "untilBuild=$untilBuild, name=$name, ideaPlugin=${Arrays.toString(ideaPlugin)}, " + + "sinceBuild=$sinceBuild, channel=$channel" + + ")" + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + class IdeaPlugin { + @XmlAttribute + var id: String? = null + @XmlAttribute + var sha256: String? = null + @XmlAttribute + var channel: String? = null + @XmlAttribute + var version: String? = null + + @XmlElement(name = "idea-version") + var ideaVersion: IdeaVersion? = null + + override fun toString(): String { + return "IdeaPlugin(id=$id, sha256=$sha256, ideaVersion=$ideaVersion, channel=$channel, version=$version)" + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + class IdeaVersion { + @XmlAttribute(name = "until-build") + var untilBuild: String? = null + @XmlAttribute(name = "since-build") + var sinceBuild: String? = null + + override fun toString(): String { + return "IdeaVersion(untilBuild=$untilBuild, sinceBuild=$sinceBuild)" + } + } + } +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/update/PluginUpdateVerifier.kt b/idea/src/org/jetbrains/kotlin/idea/update/PluginUpdateVerifier.kt new file mode 100644 index 00000000000..7e223c781d9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/update/PluginUpdateVerifier.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.update + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.openapi.extensions.ExtensionPointName + +abstract class PluginUpdateVerifier { + /** + * @param pluginDescriptor + * @return null means verifier is not responsible for the given plugin descriptor. + */ + abstract fun verify(pluginDescriptor: IdeaPluginDescriptor): PluginVerifyResult? + + companion object { + internal var EP_NAME = ExtensionPointName("org.jetbrains.kotlin.pluginUpdateVerifier") + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/update/PluginVerifyResult.kt b/idea/src/org/jetbrains/kotlin/idea/update/PluginVerifyResult.kt new file mode 100644 index 00000000000..ecd96c0fe31 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/update/PluginVerifyResult.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.update + +class PluginVerifyResult { + var verified: Boolean = true + + /** + * Short sentence explaining why plugin is available in JetBrains repository but can't be installed. + * Unapproved result should have a not-null declined message. + */ + var declineMessage: String? = null + + companion object { + fun decline(reason: String): PluginVerifyResult = PluginVerifyResult().apply { + declineMessage = reason + verified = false + } + + fun accept() = PluginVerifyResult() + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/update/verify.kt b/idea/src/org/jetbrains/kotlin/idea/update/verify.kt new file mode 100644 index 00000000000..19e3e6cf518 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/update/verify.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.update + +import org.jetbrains.kotlin.idea.PluginUpdateStatus + +// Do an additional verification with PluginUpdateVerifier. Enabled only in AS 32. +fun verify(updateStatus: PluginUpdateStatus.Update): PluginUpdateStatus { + return updateStatus +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/update/verify.kt.as32 b/idea/src/org/jetbrains/kotlin/idea/update/verify.kt.as32 new file mode 100644 index 00000000000..48b9630e851 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/update/verify.kt.as32 @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.update + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import org.jetbrains.kotlin.idea.PluginUpdateStatus + +// Do an additional verification with PluginUpdateVerifier. Enabled only in AS 32. +fun verify(updateStatus: PluginUpdateStatus.Update): PluginUpdateStatus { + val pluginDescriptor: IdeaPluginDescriptor = updateStatus.pluginDescriptor + val pluginVerifiers = PluginUpdateVerifier.EP_NAME.extensions + + val declineMessage = pluginVerifiers.asSequence() + .map { verifier -> + val verifyResult = verifier.verify(pluginDescriptor) + if (verifyResult != null && !verifyResult.verified) { + verifyResult.declineMessage + } else { + null + } + } + .firstOrNull { reason -> reason != null } + + if (declineMessage != null) { + return PluginUpdateStatus.Unverified(declineMessage, updateStatus) + } + + return updateStatus +} \ No newline at end of file