as32: Implementation for GooglePluginUpdateVerifier

This commit is contained in:
Juan Chen
2018-06-20 16:08:12 +03:00
committed by Nikolay Krasko
parent 34111e8944
commit 6523d4e306
2 changed files with 212 additions and 47 deletions
@@ -0,0 +1,174 @@
/*
* 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.configuration
import com.intellij.ide.IdeBundle
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.SearchableConfigurable
import com.intellij.openapi.updateSettings.impl.UpdateSettings
import org.jetbrains.kotlin.idea.KotlinPluginUpdater
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.PluginUpdateStatus
import org.jetbrains.kotlin.idea.configuration.ui.ConfigurePluginUpdatesForm
import javax.swing.JComponent
class KotlinUpdatesSettingsConfigurable : SearchableConfigurable, Configurable.NoScroll {
companion object {
const val ID = "preferences.language.Kotlin"
private fun saveSelectedChannel(channel: Int) {
val hosts = UpdateSettings.getInstance().storedPluginHosts
hosts.removeIf {
it.startsWith("https://plugins.jetbrains.com/plugins/") &&
(it.endsWith("/6954") || it.endsWith(KotlinPluginUtil.KOTLIN_PLUGIN_ID.idString))
}
when (channel) {
EAPChannels.EAP_1_3.uiIndex -> hosts.add(EAPChannels.EAP_1_3.url)
EAPChannels.EAP_1_2.uiIndex -> hosts.add(EAPChannels.EAP_1_2.url)
}
}
enum class EAPChannels(val url: String, val uiIndex: Int) {
EAP_1_2("https://plugins.jetbrains.com/plugins/eap-1.2/${KotlinPluginUtil.KOTLIN_PLUGIN_ID.idString}", 1),
EAP_1_3("https://plugins.jetbrains.com/plugins/eap-next/${KotlinPluginUtil.KOTLIN_PLUGIN_ID.idString}", 2);
private val hasChannel: Boolean get() = url in UpdateSettings.getInstance().pluginHosts
fun indexIfAvailable() = if (hasChannel) uiIndex else null
}
}
private val form = ConfigurePluginUpdatesForm()
private var update: PluginUpdateStatus.Update? = null
private var savedChannel = -1
private var versionForInstallation: String? = null
private var installedVersion: String? = null
private var installingStatus: String? = null
override fun getId(): String = ID
override fun getDisplayName(): String = "Kotlin Updates"
override fun isModified() = false
override fun apply() {
// Selected channel is now saved automatically
}
private fun setInstalledVersion(installedVersion: String?, installingStatus: String?) {
this.installedVersion = installedVersion
this.installingStatus = installingStatus
}
override fun createComponent(): JComponent? {
form.updateCheckProgressIcon.suspend()
form.updateCheckProgressIcon.setPaintPassiveIcon(false)
form.reCheckButton.addActionListener {
checkForUpdates()
}
form.installButton.isVisible = false
form.installButton.addActionListener {
update?.let {
form.hideInstallButton()
setInstalledVersion(it.pluginDescriptor.version, "Installing...")
form.installStatusLabel.text = installingStatus
KotlinPluginUpdater.getInstance().installPluginUpdate(
it,
successCallback = {
setInstalledVersion(it.pluginDescriptor.version, IdeBundle.message("plugin.manager.installed.tooltip"))
if (versionForInstallation == it.pluginDescriptor.version) {
form.installStatusLabel.text = installingStatus
}
},
cancelCallback = {
if (versionForInstallation == it.pluginDescriptor.version) {
form.installStatusLabel.text = ""
form.showInstallButton()
setInstalledVersion(null, null)
}
},
errorCallback = {
if (versionForInstallation == it.pluginDescriptor.version) {
form.installStatusLabel.text = "Installation failed"
form.showInstallButton()
setInstalledVersion(null, null)
}
}
)
}
}
savedChannel = EAPChannels.EAP_1_3.indexIfAvailable() ?: EAPChannels.EAP_1_2.indexIfAvailable() ?: 0
form.channelCombo.selectedIndex = savedChannel
form.channelCombo.addActionListener {
val newChannel = form.channelCombo.selectedIndex
if (newChannel != savedChannel) {
savedChannel = newChannel
checkForUpdates()
}
}
checkForUpdates()
return form.mainPanel
}
private fun checkForUpdates() {
saveChannelSettings()
form.updateCheckProgressIcon.resume()
form.resetUpdateStatus()
KotlinPluginUpdater.getInstance().runUpdateCheck{ pluginUpdateStatus ->
// Need this to show something is happening when check is very fast
Thread.sleep(30)
form.updateCheckProgressIcon.suspend()
when (pluginUpdateStatus) {
PluginUpdateStatus.LatestVersionInstalled -> {
form.setUpdateStatus(
"You have the latest version of the plugin (${KotlinPluginUtil.getPluginVersion()}) installed.",
false
)
}
is PluginUpdateStatus.Update -> {
update = pluginUpdateStatus
versionForInstallation = update?.pluginDescriptor?.version
form.setUpdateStatus("A new version ${pluginUpdateStatus.pluginDescriptor.version} is available", true)
if (installedVersion != null && installedVersion == versionForInstallation) {
// Installation of the plugin has been started or finished
form.hideInstallButton()
form.installStatusLabel.text = installingStatus
}
}
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, but not yet verified by Android Studio: ${pluginUpdateStatus.message}", false)
}
}
false // do not auto-retry update check
}
}
private fun saveChannelSettings() {
saveSelectedChannel(form.channelCombo.selectedIndex)
}
}
@@ -6,75 +6,66 @@
package org.jetbrains.kotlin.idea.update
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.PluginManagerCore
import org.intellij.lang.annotations.Language
import java.net.URL
import java.util.*
import javax.xml.bind.JAXBContext
import javax.xml.bind.annotation.*
import kotlin.collections.HashSet
import com.intellij.ide.plugins.PluginNode
class GooglePluginUpdateVerifier : PluginUpdateVerifier() {
// Verifies if a plugin can be installed in Android Studio 3.2+.
// Currently used only by KotlinPluginUpdater.
override fun verify(pluginDescriptor: IdeaPluginDescriptor): PluginVerifyResult? {
if (pluginDescriptor.pluginId.idString != "org.jetbrains.kotlin") {
if (pluginDescriptor.pluginId.idString != KOTLIN_PLUGIN_ID) {
return null
}
// val approvedKotlinVersions = fetchApprovedKotlinVersions()
// if (pluginDescriptor.version !in approvedKotlinVersions) {
// // TODO: Modify message
// return PluginVerifyResult.decline("Plugin is waiting for Google approve.")
// }
try {
val url = URL(METADATA_FILE_URL)
val stream = url.openStream()
val context = JAXBContext.newInstance(PluginCompatibility::class.java)
val unmarshaller = context.createUnmarshaller()
val pluginCompatibility = unmarshaller.unmarshal(stream) as PluginCompatibility
return PluginVerifyResult.accept()
val release = getRelease(pluginCompatibility) ?: return PluginVerifyResult.decline("no verified versions for this build")
val version = pluginDescriptor.version
if (release.plugins().any { version == it.version }) {
return PluginVerifyResult.accept()
} else {
return PluginVerifyResult.decline("version to be verified")
}
} catch (e : Exception) {
val reason = String.format("Failed to verify plugin %s version %s due to exception %s",
pluginDescriptor.name, pluginDescriptor.version, e.message)
return PluginVerifyResult.decline(reason)
}
}
private fun fetchApprovedKotlinVersions(): Set<String> {
// 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 = """
<plugin-compatibility>
<studio-release name="3.2 Canary" channel="" since-build="181.1" until-build="181.*">
<idea-plugin id="org.jetbrains.kotlin" version="1.2.41-release-Studio3.2c12-3" channel="stable" sha256="">
<idea-version since-build="181.3007.1" until-build="181.*"/>
</idea-plugin>
<idea-plugin id="org.jetbrains.kotlin" version="1.2.50-eap-17-Studio3.2-1" channel="eap-1.2" sha256="">
<idea-version since-build="181.3007.1" until-build="181.*"/>
</idea-plugin>
</studio-release>
<studio-release name="3.1.2" channel="" since-build="173.4" until-build="173.*">
<idea-plugin id="org.jetbrains.kotlin" version="1.2.41-release-Studio3.1-1" channel="stable" sha256="">
<idea-version since-build="173.1" until-build="173.4301.25"/>
</idea-plugin>
</studio-release>
</plugin-compatibility>
""".trimIndent()
val context = JAXBContext.newInstance(PluginCompatibility::class.java)
val unmarshaller = context.createUnmarshaller()
val pluginCompatibility = unmarshaller.unmarshal(answer.byteInputStream()) as PluginCompatibility
val approvedKotlinVersions = HashSet<String>()
private fun getRelease(pluginCompatibility: PluginCompatibility) : StudioRelease? {
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)
}
}
if (buildInRange(studioRelease.name, studioRelease.sinceBuild, studioRelease.untilBuild)) {
return studioRelease
}
}
return null
}
return approvedKotlinVersions
private fun buildInRange(name: String?, sinceBuild: String?, untilBuild: String?): Boolean {
val descriptor = PluginNode()
descriptor.name = name
descriptor.sinceBuild = sinceBuild
descriptor.untilBuild = untilBuild
return PluginManagerCore.isCompatible(descriptor)
}
companion object {
private const val KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin"
private const val METADATA_FILE_URL =
"https://dl.google.com/android/studio/plugins/compatibility.xml"
private fun PluginCompatibility.releases() = studioRelease ?: emptyArray()
private fun StudioRelease.plugins() = ideaPlugin ?: emptyArray()