diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt.as32 b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt.as32 new file mode 100644 index 00000000000..2e2975c2995 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinLanguageConfiguration.kt.as32 @@ -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) + } +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 b/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 index 4456b06746e..d7959a22a86 100644 --- a/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 +++ b/idea/src/org/jetbrains/kotlin/idea/update/GooglePluginUpdateVerifier.kt.as32 @@ -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 { - // 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() + 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()