UI for enabling EAP channel for plugin updates
This commit is contained in:
@@ -84,9 +84,9 @@
|
||||
<add-to-group group-id="KotlinToolsGroup"/>
|
||||
</action>
|
||||
|
||||
<action id="KotlinCheckForUpdates" class="org.jetbrains.kotlin.idea.actions.CheckPluginUpdatesAction"
|
||||
text="Check for Kotlin plugin updates"
|
||||
description="Check if a new version of the Kotlin plugin is available and prompt to install it">
|
||||
<action id="KotlinConfigureUpdates" class="org.jetbrains.kotlin.idea.actions.ConfigurePluginUpdatesAction"
|
||||
text="Configure Kotlin Plugin Updates"
|
||||
description="Configure automatic updates for the Kotlin plugin">
|
||||
<add-to-group group-id="KotlinToolsGroup"/>
|
||||
</action>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationInfo
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
@@ -45,6 +46,16 @@ import com.intellij.util.text.VersionComparatorUtil
|
||||
import java.net.URLEncoder
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
sealed class PluginUpdateStatus {
|
||||
object LatestVersionInstalled : PluginUpdateStatus()
|
||||
|
||||
class Update(val newVersion: String,
|
||||
val descriptorToInstall: IdeaPluginDescriptor?,
|
||||
val hostToInstallFrom: String?) : PluginUpdateStatus()
|
||||
|
||||
class CheckFailed(val message: String) : PluginUpdateStatus()
|
||||
}
|
||||
|
||||
class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Disposable {
|
||||
private val INITIAL_UPDATE_DELAY = 5000L
|
||||
private var updateDelay = INITIAL_UPDATE_DELAY
|
||||
@@ -53,25 +64,30 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
||||
private val notificationGroup = NotificationGroup("Kotlin plugin updates", NotificationDisplayType.STICKY_BALLOON, true)
|
||||
|
||||
fun kotlinFileEdited() {
|
||||
val lastUpdateTime = java.lang.Long.parseLong(propertiesComponent.getValue(PROPERTY_NAME, "0"))
|
||||
if (lastUpdateTime == 0L || System.currentTimeMillis() - lastUpdateTime > TimeUnit.DAYS.toMillis(1)) {
|
||||
queueUpdateCheck()
|
||||
}
|
||||
}
|
||||
|
||||
fun queueUpdateCheck(reportNoUpdates: Boolean = false) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return
|
||||
if (!UpdateSettings.getInstance().isCheckNeeded) return
|
||||
|
||||
val lastUpdateTime = java.lang.Long.parseLong(propertiesComponent.getValue(PROPERTY_NAME, "0"))
|
||||
if (lastUpdateTime == 0L || System.currentTimeMillis() - lastUpdateTime > TimeUnit.DAYS.toMillis(1)) {
|
||||
queueUpdateCheck { updateStatus ->
|
||||
if (updateStatus is PluginUpdateStatus.Update) {
|
||||
notifyPluginUpdateAvailable(updateStatus)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun queueUpdateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread()
|
||||
if (!checkQueued) {
|
||||
checkQueued = true
|
||||
alarm.addRequest({ updateCheck(reportNoUpdates) }, updateDelay)
|
||||
alarm.addRequest({ updateCheck(callback) }, updateDelay)
|
||||
updateDelay *= 2 // exponential backoff
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCheck(reportNoUpdates: Boolean) {
|
||||
private fun updateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||
try {
|
||||
var (mainRepoUpdateSuccess, latestVersionInRepository) = getPluginVersionFromMainRepository()
|
||||
var descriptorToInstall: IdeaPluginDescriptor? = null
|
||||
@@ -99,28 +115,28 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
||||
if (mainRepoUpdateSuccess || latestVersionInRepository != null) {
|
||||
recordSuccessfulUpdateCheck()
|
||||
if (latestVersionInRepository != null && VersionComparatorUtil.compare(latestVersionInRepository, KotlinPluginUtil.getPluginVersion()) > 0) {
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
notifyPluginUpdateAvailable(latestVersionInRepository!!, descriptorToInstall, hostToInstallFrom)
|
||||
}
|
||||
ApplicationManager.getApplication().invokeLater({
|
||||
callback(PluginUpdateStatus.Update(latestVersionInRepository!!, descriptorToInstall, hostToInstallFrom))
|
||||
}, ModalityState.any())
|
||||
}
|
||||
else if (reportNoUpdates) {
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
val notification = notificationGroup.createNotification(
|
||||
"Kotlin",
|
||||
"You have the latest version of the Kotlin plugin",
|
||||
NotificationType.INFORMATION, null)
|
||||
notification.notify(null)
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication().invokeLater({
|
||||
callback(PluginUpdateStatus.LatestVersionInstalled)
|
||||
}, ModalityState.any())
|
||||
}
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication().invokeLater { queueUpdateCheck() }
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
if (callback(PluginUpdateStatus.CheckFailed("Plugin not found in repository"))) queueUpdateCheck(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e: Exception) {
|
||||
LOG.info("Kotlin plugin update check failed", e)
|
||||
checkQueued = false
|
||||
ApplicationManager.getApplication().invokeLater { queueUpdateCheck() }
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
if (callback(PluginUpdateStatus.CheckFailed(e.message ?: "Unknown error"))) queueUpdateCheck(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,43 +173,44 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
||||
updateDelay = INITIAL_UPDATE_DELAY
|
||||
}
|
||||
|
||||
private fun notifyPluginUpdateAvailable(newVersion: String,
|
||||
descriptorToInstall: IdeaPluginDescriptor?,
|
||||
hostToInstallFrom: String?) {
|
||||
private fun notifyPluginUpdateAvailable(update: PluginUpdateStatus.Update) {
|
||||
val notification = notificationGroup.createNotification(
|
||||
"Kotlin",
|
||||
"A new version $newVersion of the Kotlin plugin is available. <b><a href=\"#\">Install</a></b>",
|
||||
"A new version ${update.newVersion} of the Kotlin plugin is available. <b><a href=\"#\">Install</a></b>",
|
||||
NotificationType.INFORMATION) { notification, event ->
|
||||
val descriptor = descriptorToInstall ?: PluginManager.getPlugin(PluginId.getId("org.jetbrains.kotlin"))
|
||||
if (descriptor != null) {
|
||||
notification.expire()
|
||||
|
||||
val pluginDownloader = PluginDownloader.createDownloader(descriptor, hostToInstallFrom, null)
|
||||
ProgressManager.getInstance().run(object : Task.Backgroundable(null, "Downloading plugins", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
if (pluginDownloader.prepareToInstall(indicator)) {
|
||||
val pluginDescriptor = pluginDownloader.getDescriptor()
|
||||
if (pluginDescriptor != null) {
|
||||
pluginDownloader.install()
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
notification.expire()
|
||||
PluginManagerMain.notifyPluginsUpdated(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
notifyPluginUpdateAvailable(newVersion, descriptorToInstall, hostToInstallFrom)
|
||||
}
|
||||
})
|
||||
notification.expire()
|
||||
installPluginUpdate(update) {
|
||||
notifyPluginUpdateAvailable(update)
|
||||
}
|
||||
}
|
||||
|
||||
notification.notify(null)
|
||||
}
|
||||
|
||||
fun installPluginUpdate(update: PluginUpdateStatus.Update,
|
||||
cancelCallback: () -> Unit = {}) {
|
||||
val descriptor = update.descriptorToInstall ?: PluginManager.getPlugin(PluginId.getId("org.jetbrains.kotlin")) ?: return
|
||||
val pluginDownloader = PluginDownloader.createDownloader(descriptor, update.hostToInstallFrom, null)
|
||||
ProgressManager.getInstance().run(object : Task.Backgroundable(null, "Downloading plugins", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
if (pluginDownloader.prepareToInstall(indicator)) {
|
||||
val pluginDescriptor = pluginDownloader.getDescriptor()
|
||||
if (pluginDescriptor != null) {
|
||||
pluginDownloader.install()
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
PluginManagerMain.notifyPluginsUpdated(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
cancelCallback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.updateSettings.impl.UpdateSettings
|
||||
import org.jetbrains.kotlin.idea.KotlinPluginUpdater
|
||||
import org.jetbrains.kotlin.idea.PluginUpdateStatus
|
||||
import javax.swing.JComponent
|
||||
|
||||
class ConfigurePluginUpdatesAction : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
ConfigurePluginUpdatesDialog(project).show()
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigurePluginUpdatesDialog(project: Project) : DialogWrapper(project, false) {
|
||||
private val form = ConfigurePluginUpdatesForm()
|
||||
private val initialSelectedChannel: Int
|
||||
private var update: PluginUpdateStatus.Update? = null
|
||||
|
||||
init {
|
||||
title = "Configure Kotlin Plugin Updates"
|
||||
form.updateCheckProgressIcon.suspend()
|
||||
form.updateCheckProgressIcon.setPaintPassiveIcon(false)
|
||||
|
||||
form.checkForUpdatesNowButton.addActionListener {
|
||||
saveSettings()
|
||||
form.updateCheckProgressIcon.resume()
|
||||
resetUpdateStatus()
|
||||
KotlinPluginUpdater.getInstance().queueUpdateCheck() { pluginUpdateStatus ->
|
||||
form.updateCheckProgressIcon.suspend()
|
||||
when (pluginUpdateStatus) {
|
||||
PluginUpdateStatus.LatestVersionInstalled ->
|
||||
form.updateStatusLabel.text = "You have the latest version of the plugin installed."
|
||||
|
||||
is PluginUpdateStatus.Update -> {
|
||||
update = pluginUpdateStatus
|
||||
form.installButton.isVisible = true
|
||||
form.updateStatusLabel.text = "A new version ${pluginUpdateStatus.newVersion} is available"
|
||||
}
|
||||
|
||||
is PluginUpdateStatus.CheckFailed ->
|
||||
form.updateStatusLabel.text = "Update check failed: ${pluginUpdateStatus.message}"
|
||||
}
|
||||
|
||||
false // do not auto-retry update check
|
||||
}
|
||||
}
|
||||
|
||||
form.installButton.isVisible = false
|
||||
form.installButton.addActionListener {
|
||||
update?.let {
|
||||
close(OK_EXIT_CODE)
|
||||
KotlinPluginUpdater.getInstance().installPluginUpdate(it)
|
||||
}
|
||||
}
|
||||
|
||||
form.channelCombo.addActionListener {
|
||||
resetUpdateStatus()
|
||||
}
|
||||
|
||||
initialSelectedChannel = if (hasEAPChannel()) 1 else 0
|
||||
form.channelCombo.selectedIndex = initialSelectedChannel
|
||||
init()
|
||||
}
|
||||
|
||||
private fun resetUpdateStatus() {
|
||||
form.updateStatusLabel.text = " "
|
||||
form.installButton.isVisible = false
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent = form.mainPanel
|
||||
|
||||
private fun saveSettings() {
|
||||
saveSelectedChannel(form.channelCombo.selectedIndex)
|
||||
}
|
||||
|
||||
private fun saveSelectedChannel(channel: Int) {
|
||||
val hosts = UpdateSettings.getInstance().storedPluginHosts
|
||||
when (channel) {
|
||||
0 -> hosts.remove(EAP_UPDATE_HOST)
|
||||
1 -> if (EAP_UPDATE_HOST !in hosts) hosts.add(EAP_UPDATE_HOST)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doOKAction() {
|
||||
saveSettings()
|
||||
super.doOKAction()
|
||||
}
|
||||
|
||||
override fun doCancelAction() {
|
||||
saveSelectedChannel(initialSelectedChannel)
|
||||
super.doCancelAction()
|
||||
}
|
||||
|
||||
private fun hasEAPChannel() = EAP_UPDATE_HOST in UpdateSettings.getInstance().pluginHosts
|
||||
|
||||
companion object {
|
||||
val EAP_UPDATE_HOST = "https://plugins.jetbrains.com/plugins/eap/6954"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.actions.ConfigurePluginUpdatesForm">
|
||||
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="4" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="8d277" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Update channel:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="42b13">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="ca1a9" class="javax.swing.JComboBox" binding="channelCombo">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<model>
|
||||
<item value="Stable"/>
|
||||
<item value="Early Access Preview"/>
|
||||
</model>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="b171" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="0">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="c7546" class="javax.swing.JButton" binding="checkForUpdatesNowButton" default-binding="true">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<text value="Check for updates now"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="3b585" class="com.intellij.util.ui.AsyncProcessIcon" binding="updateCheckProgressIcon" custom-create="true">
|
||||
<constraints/>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="2505b" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="4ec56" class="javax.swing.JLabel" binding="updateStatusLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=" "/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="65662" class="javax.swing.JButton" binding="installButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Install"/>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+15
-8
@@ -14,14 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.actions
|
||||
package org.jetbrains.kotlin.idea.actions;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import org.jetbrains.kotlin.idea.KotlinPluginUpdater
|
||||
import com.intellij.util.ui.AsyncProcessIcon;
|
||||
|
||||
class CheckPluginUpdatesAction : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
KotlinPluginUpdater.getInstance().queueUpdateCheck(true)
|
||||
import javax.swing.*;
|
||||
|
||||
public class ConfigurePluginUpdatesForm {
|
||||
public JComboBox channelCombo;
|
||||
public JButton checkForUpdatesNowButton;
|
||||
public JPanel mainPanel;
|
||||
public AsyncProcessIcon updateCheckProgressIcon;
|
||||
public JLabel updateStatusLabel;
|
||||
public JButton installButton;
|
||||
|
||||
private void createUIComponents() {
|
||||
updateCheckProgressIcon = new AsyncProcessIcon("Plugin update check progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user