diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 47aed21305a..d1975249664 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -84,9 +84,9 @@ - + diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt index e5c468ee3d6..b916941680b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt @@ -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. Install", + "A new version ${update.newVersion} of the Kotlin plugin is available. Install", 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() { } diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesAction.kt new file mode 100644 index 00000000000..5728b29f266 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesAction.kt @@ -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" + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesForm.form b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesForm.form new file mode 100644 index 00000000000..6efab9ec99e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesForm.form @@ -0,0 +1,83 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/CheckPluginUpdatesAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesForm.java similarity index 54% rename from idea/src/org/jetbrains/kotlin/idea/actions/CheckPluginUpdatesAction.kt rename to idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesForm.java index 0591804a853..6c72a2143ac 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/CheckPluginUpdatesAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigurePluginUpdatesForm.java @@ -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"); } -} \ No newline at end of file +}