mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
jdk-updates: IDEA-261052
GitOrigin-RevId: 0e61362a53a83fbcc877b4de251511347f9484f5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
11fa6f32ef
commit
c83f86faf7
-72
@@ -1,72 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.projectRoots.impl.jdkDownloader
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.components.stateStore
|
||||
import com.intellij.openapi.projectRoots.JavaSdk
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
import org.junit.Assert
|
||||
|
||||
class JdkUpdateStoreTest : LightPlatformTestCase() {
|
||||
|
||||
private fun newSdk(sdkName: String, version: String): Sdk {
|
||||
val oldSdk = ProjectJdkTable.getInstance().createSdk(sdkName, JavaSdk.getInstance())
|
||||
oldSdk.sdkModificator.apply {
|
||||
homePath = createTempDir("mock-old-home").toString()
|
||||
versionString = version
|
||||
}.commitChanges()
|
||||
|
||||
if (oldSdk is Disposable) {
|
||||
Disposer.register(testRootDisposable, oldSdk)
|
||||
}
|
||||
|
||||
return oldSdk
|
||||
}
|
||||
|
||||
fun testState() {
|
||||
val sdkA = newSdk("a", "1.2.3")
|
||||
val sdkB = newSdk("b", "3.2.1")
|
||||
Assert.assertTrue(service.isAllowed(sdkA, mockZipNew))
|
||||
Assert.assertTrue(service.isAllowed(sdkB, mockZipNew))
|
||||
|
||||
service.blockVersion(sdkA, mockZipNew)
|
||||
|
||||
Assert.assertFalse(service.isAllowed(sdkA, mockZipNew))
|
||||
Assert.assertTrue(service.isAllowed(sdkA, mockZipNew.copy(jdkVersion = "1234.53.5")))
|
||||
Assert.assertTrue(service.isAllowed(sdkB, mockZipNew))
|
||||
|
||||
reloadState()
|
||||
|
||||
Assert.assertFalse(service.isAllowed(sdkA, mockZipNew))
|
||||
Assert.assertTrue(service.isAllowed(sdkA, mockZipNew.copy(jdkVersion = "1234.53.5")))
|
||||
Assert.assertTrue(service.isAllowed(sdkB, mockZipNew))
|
||||
}
|
||||
|
||||
private val service get() = service<JdkUpdaterState>()
|
||||
private val newState get() = JdkUpdaterStateData()
|
||||
|
||||
private fun resetState() {
|
||||
service.loadState(newState)
|
||||
}
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
resetState()
|
||||
}
|
||||
|
||||
private fun reloadState() {
|
||||
//just to check if the state can be saved
|
||||
val store = ApplicationManager.getApplication().stateStore
|
||||
val p = service
|
||||
store.saveComponent(p)
|
||||
//wipe the state
|
||||
p.loadState(newState)
|
||||
//load it (hopefully)
|
||||
store.reloadState(service::class.java)
|
||||
}
|
||||
}
|
||||
+78
-85
@@ -3,23 +3,26 @@ package com.intellij.openapi.projectRoots.impl.jdkDownloader
|
||||
|
||||
import com.intellij.application.subscribe
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.notification.Notifications
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.projectRoots.JavaSdk
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkUpdateNotification.InstallUpdateNotification
|
||||
import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkUpdateNotification.RejectUpdateNotification
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.junit.Assert
|
||||
import java.awt.event.KeyEvent
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import javax.swing.JPanel
|
||||
|
||||
class JdkUpdateTest : LightPlatformTestCase() {
|
||||
private val myNotifications = Collections.synchronizedList(ArrayList<Notification>())
|
||||
@@ -45,24 +48,22 @@ class JdkUpdateTest : LightPlatformTestCase() {
|
||||
}
|
||||
})
|
||||
|
||||
doEventsWhile(1)
|
||||
listOurNotifications().forEach { it.expire() }
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
listOurActions().forEach { it.reachTerminalState() }
|
||||
}
|
||||
|
||||
private fun listOurActions(): List<JdkUpdateNotification> {
|
||||
doEventsWhile(1)
|
||||
return service<JdkUpdaterNotifications>().getActions().map { it.jdkUpdateNotification }.filter { it.isUpdateActionVisible }
|
||||
}
|
||||
|
||||
private fun listOurNotifications() = myNotifications
|
||||
.filter { !it.isExpired }
|
||||
.filter { it.groupId == "JDK Update" || it.groupId == "JDK Update Error" }
|
||||
|
||||
private fun listOurExpiredNotifications() = myNotifications
|
||||
.filter { it.isExpired }
|
||||
.filter { it.groupId == "JDK Update" || it.groupId == "JDK Update Error" }
|
||||
|
||||
private fun expectSingleSuggestUpdateNotification() : Notification {
|
||||
return listOurNotifications().single { it.groupId == "JDK Update" }
|
||||
}
|
||||
|
||||
private fun newNotification(sdkName: String, oldVersion: JdkItem = mockZipOld, newVersion: JdkItem = mockZipNew): JdkUpdateNotification {
|
||||
val oldSdk = ProjectJdkTable.getInstance().createSdk(sdkName, JavaSdk.getInstance())
|
||||
private fun newNotification(sdkName: String, oldVersion: JdkItem = mockZipOld, newVersion: JdkItem = mockZipNew): JdkUpdateNotification? {
|
||||
val oldSdk = ProjectJdkTable.getInstance().findJdk(sdkName) ?: ProjectJdkTable.getInstance().createSdk(sdkName, JavaSdk.getInstance())
|
||||
oldSdk.sdkModificator.apply {
|
||||
homePath = createTempDir("mock-old-home").toString()
|
||||
versionString = oldVersion.versionString
|
||||
@@ -72,31 +73,30 @@ class JdkUpdateTest : LightPlatformTestCase() {
|
||||
Disposer.register(testRootDisposable, oldSdk)
|
||||
}
|
||||
|
||||
return JdkUpdateNotification(oldSdk, oldVersion, newVersion) {}
|
||||
val notification = service<JdkUpdaterNotifications>().showNotification(oldSdk, oldVersion, newVersion)
|
||||
doEventsWhile(5)
|
||||
return notification
|
||||
}
|
||||
|
||||
fun `test the same popup is not shown twice`() {
|
||||
val notification = newNotification("old-sdk")
|
||||
|
||||
notification.showNotificationIfAbsent()
|
||||
notification.showNotificationIfAbsent()
|
||||
notification.showNotificationIfAbsent()
|
||||
newNotification("old-sdk")
|
||||
|
||||
val actions = listOurActions()
|
||||
val notifications = listOurNotifications()
|
||||
val expired = listOurExpiredNotifications()
|
||||
Assert.assertEquals("$notifications", 1, notifications.size)
|
||||
Assert.assertEquals("$expired", 0, expired.size)
|
||||
Assert.assertEquals("$notifications", 0, notifications.size)
|
||||
Assert.assertEquals("$actions", 1, actions.size)
|
||||
}
|
||||
|
||||
fun `test jdk update`() {
|
||||
val update = newNotification("old-sdk2")
|
||||
update.showNotificationIfAbsent()
|
||||
val update = newNotification("old-sdk2")!!
|
||||
Assert.assertEquals(setOf(update), listOurActions().toSet())
|
||||
|
||||
val n = expectSingleSuggestUpdateNotification()
|
||||
n.fireAction<InstallUpdateNotification>()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
update.fireAction()
|
||||
doEventsWhile(5)
|
||||
|
||||
val ourActions = listOurActions()
|
||||
val ourNotifications = listOurNotifications()
|
||||
Assert.assertTrue("$ourActions", ourActions.isEmpty())
|
||||
Assert.assertTrue("$ourNotifications", ourNotifications.isEmpty())
|
||||
Assert.assertEquals(update.newItem.versionString, update.jdk.versionString)
|
||||
Assert.assertTrue(update.isTerminated())
|
||||
@@ -108,89 +108,59 @@ class JdkUpdateTest : LightPlatformTestCase() {
|
||||
}
|
||||
|
||||
fun `test jdk update failed`() {
|
||||
val update = newNotification("old-sdk2", newVersion = mockZipNewBroken)
|
||||
update.showNotificationIfAbsent()
|
||||
val update = newNotification("old-sdk2", newVersion = mockZipNewBroken)!!
|
||||
Assert.assertEquals(setOf(update), listOurActions().toSet())
|
||||
|
||||
val n = expectSingleSuggestUpdateNotification()
|
||||
n.fireAction<InstallUpdateNotification>()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
update.fireAction()
|
||||
doEventsWhile(2)
|
||||
|
||||
val ourActions = listOurActions()
|
||||
val ourNotifications = listOurNotifications()
|
||||
Assert.assertEquals(setOf(update), listOurActions().toSet())
|
||||
Assert.assertEquals("$ourNotifications", 1, ourNotifications.size)
|
||||
Assert.assertEquals("$ourNotifications", 1, ourNotifications.filter { it.type == NotificationType.ERROR }.size)
|
||||
Assert.assertEquals(update.oldItem.versionString, update.jdk.versionString)
|
||||
Assert.assertTrue(!update.isTerminated())
|
||||
|
||||
update.showNotificationIfAbsent()
|
||||
Assert.assertEquals("$ourNotifications", ourNotifications, listOurNotifications())
|
||||
}
|
||||
|
||||
fun `test reject is not lost`() {
|
||||
val update = newNotification("old-sdk2")
|
||||
update.showNotificationIfAbsent()
|
||||
|
||||
val n = expectSingleSuggestUpdateNotification()
|
||||
n.fireAction<RejectUpdateNotification>()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val ourNotifications = listOurNotifications()
|
||||
Assert.assertTrue("$ourNotifications", ourNotifications.isEmpty())
|
||||
Assert.assertEquals(update.oldItem.versionString, update.jdk.versionString)
|
||||
Assert.assertTrue(update.isTerminated())
|
||||
Assert.assertFalse(service<JdkUpdaterState>().isAllowed(update.jdk, update.newItem))
|
||||
}
|
||||
|
||||
private inline fun <reified T: NotificationAction> Notification.fireAction() {
|
||||
val action = actions.filterIsInstance<T>().single()
|
||||
Notification.fire(this, action)
|
||||
}
|
||||
|
||||
|
||||
fun `test merge notifications correctly`() {
|
||||
val old1 = newNotification("old-1")
|
||||
val old1 = newNotification("old-1")!!
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
|
||||
val notifications = listOurNotifications()
|
||||
val expired = listOurExpiredNotifications()
|
||||
Assert.assertEquals("$notifications", 1, notifications.size)
|
||||
Assert.assertEquals("$expired", 0, expired.size)
|
||||
val actions = listOurActions()
|
||||
Assert.assertEquals("$actions", 1, actions.size)
|
||||
}
|
||||
|
||||
fun `test merge notifications correctly 2`() {
|
||||
val old1 = newNotification("old-1")
|
||||
val old2 = newNotification("old-2")
|
||||
val old1 = newNotification("old-1")!!
|
||||
val old2 = newNotification("old-2")!!
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
service<JdkUpdaterNotifications>().showNotification(old2.jdk, old2.oldItem, old2.newItem)
|
||||
service<JdkUpdaterNotifications>().showNotification(old2.jdk, old2.oldItem, old2.newItem)
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
|
||||
val notifications = listOurNotifications()
|
||||
val expired = listOurExpiredNotifications()
|
||||
Assert.assertEquals("$notifications", 2, notifications.size)
|
||||
Assert.assertEquals("$expired", 0, expired.size)
|
||||
val actions = listOurActions()
|
||||
Assert.assertEquals("$actions", 2, actions.size)
|
||||
}
|
||||
|
||||
fun `test replace notifications correctly 2`() {
|
||||
val old1 = newNotification("old-1")
|
||||
val old1 = newNotification("old-1")!!
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem.copy(jdkVersion = "17.0.777"))
|
||||
|
||||
val notifications = listOurNotifications()
|
||||
val expired = listOurExpiredNotifications()
|
||||
Assert.assertEquals("$notifications", 1, notifications.size)
|
||||
Assert.assertTrue("$notifications", notifications.single().content.contains("17.0.777"))
|
||||
Assert.assertEquals("$expired", 1, expired.size)
|
||||
Assert.assertFalse("$notifications", expired.single().content.contains("17.0.777"))
|
||||
val actions = listOurActions()
|
||||
Assert.assertEquals("$actions", 1, actions.size)
|
||||
Assert.assertTrue("$actions", actions.single().newItem.jdkVersion == "17.0.777")
|
||||
}
|
||||
|
||||
fun `test replace notifications correctly 3`() {
|
||||
val old1 = newNotification("old-1")
|
||||
val old1 = newNotification("old-1")!!
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
|
||||
Assert.assertEquals(1, listOurNotifications().size)
|
||||
Assert.assertEquals(0, listOurExpiredNotifications().size)
|
||||
doEventsWhile(5)
|
||||
Assert.assertEquals(1, listOurActions().size)
|
||||
|
||||
runWriteAction {
|
||||
old1.jdk.sdkModificator.also { it.versionString = "new JDK version" }.commitChanges()
|
||||
@@ -198,16 +168,14 @@ class JdkUpdateTest : LightPlatformTestCase() {
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
|
||||
//notification must expire and update
|
||||
Assert.assertEquals(1, listOurNotifications().size)
|
||||
Assert.assertEquals(1, listOurExpiredNotifications().size)
|
||||
Assert.assertEquals(1, listOurActions().size)
|
||||
}
|
||||
|
||||
fun `test replace notifications correctly 4`() {
|
||||
val old1 = newNotification("old-1")
|
||||
val old1 = newNotification("old-1")!!
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
|
||||
Assert.assertEquals(1, listOurNotifications().size)
|
||||
Assert.assertEquals(0, listOurExpiredNotifications().size)
|
||||
Assert.assertEquals(1, listOurActions().size)
|
||||
|
||||
runWriteAction {
|
||||
old1.jdk.sdkModificator.also { it.homePath = it.homePath + "-123" }.commitChanges()
|
||||
@@ -215,8 +183,7 @@ class JdkUpdateTest : LightPlatformTestCase() {
|
||||
service<JdkUpdaterNotifications>().showNotification(old1.jdk, old1.oldItem, old1.newItem)
|
||||
|
||||
//notification must expire and update
|
||||
Assert.assertEquals(1, listOurNotifications().size)
|
||||
Assert.assertEquals(1, listOurExpiredNotifications().size)
|
||||
Assert.assertEquals(1, listOurActions().size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,3 +237,29 @@ private fun jdkItemForTest(url: String,
|
||||
sharedIndexAliases = listOf(),
|
||||
saveToFile = {}
|
||||
)
|
||||
|
||||
|
||||
private fun doEventsWhile(iterations: Int = Int.MAX_VALUE / 2,
|
||||
condition: () -> Boolean = { true }) {
|
||||
repeat(iterations) {
|
||||
if (!condition()) return
|
||||
|
||||
ApplicationManager.getApplication().invokeAndWait {
|
||||
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
}
|
||||
|
||||
if (!condition()) return
|
||||
Thread.sleep(30)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun runAction(theAction: AnAction) {
|
||||
ApplicationManager.getApplication().invokeAndWait {
|
||||
val event = KeyEvent(JPanel(), 1, 0, 0, 0, ' ')
|
||||
ActionManager.getInstance().tryToExecute(theAction, event, null, null, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun JdkUpdateNotification.fireAction() = runAction(updateAction)
|
||||
|
||||
+39
-48
@@ -8,18 +8,16 @@ import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.ProjectBundle
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.SdkType
|
||||
import com.intellij.openapi.util.NlsContexts.NotificationContent
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.util.io.systemIndependentPath
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@@ -50,7 +48,6 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
|
||||
private val jdkVersion = jdk.versionString
|
||||
private val jdkHome = jdk.homePath
|
||||
private val openProjectsSnapshot = ProjectManager.getInstance().openProjects.map { it.locationHash }.toSortedSet()
|
||||
|
||||
private var myIsTerminated = false
|
||||
private var myIsUpdateRunning = false
|
||||
@@ -58,7 +55,9 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
/**
|
||||
* Can be either suggestion or error notification
|
||||
*/
|
||||
private var myPendingNotification : Notification? = null
|
||||
private var myRetryNotification : Notification? = null
|
||||
|
||||
val persistentId = "${jdk.name}-${oldItem.fullPresentationText}-${newItem.fullPresentationText}-${jdk.homePath}"
|
||||
|
||||
private fun Notification.bindNextNotificationAndShow() {
|
||||
bindNextNotification(this)
|
||||
@@ -67,17 +66,17 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
|
||||
private fun bindNextNotification(notification: Notification) {
|
||||
lock.withLock {
|
||||
myPendingNotification?.expire()
|
||||
myRetryNotification?.expire()
|
||||
|
||||
notification.whenExpired {
|
||||
lock.withLock {
|
||||
if (myPendingNotification === notification) {
|
||||
myPendingNotification = null
|
||||
if (myRetryNotification === notification) {
|
||||
myRetryNotification = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myPendingNotification = notification
|
||||
myRetryNotification = notification
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +87,8 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
//the pending notification is the same as before
|
||||
if (other != null && isSameNotification(other)) return false
|
||||
|
||||
myPendingNotification?.expire()
|
||||
myPendingNotification = null
|
||||
myRetryNotification?.expire()
|
||||
myRetryNotification = null
|
||||
reachTerminalState()
|
||||
|
||||
return true
|
||||
@@ -99,15 +98,13 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
if (this.jdkVersion != other.jdkVersion) return false
|
||||
if (this.jdkHome != other.jdkHome) return false
|
||||
if (this.newItem != other.newItem) return false
|
||||
//a new open project may also have a update notification, that is not shown there
|
||||
if (!this.openProjectsSnapshot.containsAll(other.openProjectsSnapshot)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* The state-machine reached it's end
|
||||
*/
|
||||
private fun reachTerminalState(): Unit = lock.withLock {
|
||||
fun reachTerminalState(): Unit = lock.withLock {
|
||||
if (myIsTerminated) return
|
||||
myIsTerminated = true
|
||||
whenComplete(this)
|
||||
@@ -115,44 +112,32 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
|
||||
fun isTerminated() = lock.withLock { myIsTerminated }
|
||||
|
||||
private fun updateJdkAction(@NotificationContent message: String) = InstallUpdateNotification(message)
|
||||
|
||||
inner class InstallUpdateNotification(@NotificationContent message: String) : NotificationAction(message) {
|
||||
inner class InstallUpdateNotification : NotificationAction(ProjectBundle.message("notification.link.jdk.update.retry")) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
lock.withLock {
|
||||
if (myIsUpdateRunning) return
|
||||
myIsUpdateRunning = true
|
||||
}
|
||||
updateJdk(e.project)
|
||||
performUpdateAction(e)
|
||||
notification.expire()
|
||||
}
|
||||
}
|
||||
|
||||
private fun rejectJdkAction() = RejectUpdateNotification()
|
||||
val updateAction = JdkUpdateSuggestionAction()
|
||||
|
||||
inner class RejectUpdateNotification : NotificationAction(ProjectBundle.message("notification.link.jdk.update.skip")) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
service<JdkUpdaterState>().blockVersion(jdk, newItem)
|
||||
notification.expire()
|
||||
reachTerminalState()
|
||||
val isUpdateActionVisible get() = !myIsUpdateRunning && !myIsTerminated
|
||||
|
||||
inner class JdkUpdateSuggestionAction : DumbAwareAction() {
|
||||
val jdkUpdateNotification = this@JdkUpdateNotification
|
||||
|
||||
init {
|
||||
templatePresentation.text = ProjectBundle.message("action.title.jdk.update.found", jdk.name, newItem.fullPresentationText, oldItem.versionPresentationText)
|
||||
templatePresentation.description = ProjectBundle.message("action.description.jdk.update.found", jdk.name, newItem.fullPresentationText, oldItem.versionPresentationText)
|
||||
}
|
||||
}
|
||||
|
||||
fun showNotificationIfAbsent() : Unit = lock.withLock {
|
||||
if (myPendingNotification != null || myIsUpdateRunning || myIsTerminated) return
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = isUpdateActionVisible
|
||||
}
|
||||
|
||||
val title = ProjectBundle.message("notification.title.jdk.update.found")
|
||||
val message = ProjectBundle.message("notification.text.jdk.update.found",
|
||||
jdk.name,
|
||||
newItem.fullPresentationText,
|
||||
oldItem.versionPresentationText)
|
||||
|
||||
NotificationGroupManager.getInstance().getNotificationGroup("JDK Update")
|
||||
.createNotification(title, message, NotificationType.INFORMATION)
|
||||
.setImportant(true)
|
||||
.addAction(updateJdkAction(ProjectBundle.message("notification.link.jdk.update.apply")))
|
||||
.addAction(rejectJdkAction())
|
||||
.bindNextNotificationAndShow()
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
performUpdateAction(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUpdateErrorNotification(feedItem: JdkItem) : Unit = lock.withLock {
|
||||
@@ -160,12 +145,18 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
.createNotification(type = NotificationType.ERROR)
|
||||
.setTitle(ProjectBundle.message("progress.title.updating.jdk.0.to.1", jdk.name, feedItem.fullPresentationText))
|
||||
.setContent(ProjectBundle.message("progress.title.updating.jdk.failed", feedItem.fullPresentationText))
|
||||
.addAction(updateJdkAction(ProjectBundle.message("notification.link.jdk.update.retry")))
|
||||
.addAction(rejectJdkAction())
|
||||
.addAction(InstallUpdateNotification())
|
||||
.bindNextNotificationAndShow()
|
||||
}
|
||||
|
||||
private fun updateJdk(project: Project?) {
|
||||
private fun performUpdateAction(e: AnActionEvent) {
|
||||
myRetryNotification?.expire()
|
||||
|
||||
lock.withLock {
|
||||
if (myIsUpdateRunning) return
|
||||
myIsUpdateRunning = true
|
||||
}
|
||||
|
||||
val title = ProjectBundle.message("progress.title.updating.jdk.0.to.1", jdk.name, newItem.fullPresentationText)
|
||||
ProgressManager.getInstance().run(
|
||||
object : Task.Backgroundable(null /*progress should be global*/, title, true, ALWAYS_BACKGROUND) {
|
||||
@@ -174,7 +165,7 @@ class JdkUpdateNotification(val jdk: Sdk,
|
||||
val installer = JdkInstaller.getInstance()
|
||||
|
||||
val request = installer.prepareJdkInstallation(newItem, installer.defaultInstallDir(newItem))
|
||||
installer.installJdk(request, indicator, project)
|
||||
installer.installJdk(request, indicator, e.project)
|
||||
|
||||
//make sure VFS sees the files and sets up the JDK correctly
|
||||
indicator.text = ProjectBundle.message("progress.text.updating.jdk.setting.up")
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.projectRoots.impl.jdkDownloader
|
||||
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.xmlb.annotations.OptionTag
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class JdkUpdaterStateData : BaseState() {
|
||||
@get:OptionTag
|
||||
val dndVersions by stringSet()
|
||||
}
|
||||
|
||||
@State(name = "jdk-update-state", storages = [Storage(StoragePathMacros.CACHE_FILE)], allowLoadInTests = true)
|
||||
@Service
|
||||
class JdkUpdaterState : SimplePersistentStateComponent<JdkUpdaterStateData>(JdkUpdaterStateData()) {
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
override fun loadState(state: JdkUpdaterStateData) = lock.withLock {
|
||||
super.loadState(state)
|
||||
}
|
||||
|
||||
private fun key(forJdk: Sdk, feedItem: JdkItem) = "for(${forJdk.name})-${feedItem.fullPresentationText}"
|
||||
|
||||
fun isAllowed(forJdk: Sdk, feedItem: JdkItem) = lock.withLock {
|
||||
key(forJdk, feedItem) !in state.dndVersions
|
||||
}
|
||||
|
||||
fun blockVersion(forJdk: Sdk, feedItem: JdkItem) = lock.withLock {
|
||||
state.dndVersions += key(forJdk, feedItem)
|
||||
state.intIncrementModificationCount()
|
||||
}
|
||||
}
|
||||
-36
@@ -29,8 +29,6 @@ import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* This extension point is used to collect
|
||||
@@ -180,8 +178,6 @@ internal class JdkUpdatesCollector(
|
||||
it.suggestedSdkName == actualItem.suggestedSdkName && it.arch == actualItem.arch && it.os == actualItem.os
|
||||
} ?: continue
|
||||
|
||||
if (!service<JdkUpdaterState>().isAllowed(jdk, feedItem)) continue
|
||||
|
||||
//internal versions are not considered here (JBRs?)
|
||||
if (VersionComparatorUtil.compare(feedItem.jdkVersion, actualItem.jdkVersion) <= 0) continue
|
||||
|
||||
@@ -195,35 +191,3 @@ internal class JdkUpdatesCollector(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Service //Application service
|
||||
class JdkUpdaterNotifications : Disposable {
|
||||
private val lock = ReentrantLock()
|
||||
private val pendingNotifications = HashMap<Sdk, JdkUpdateNotification>()
|
||||
|
||||
override fun dispose() : Unit = lock.withLock {
|
||||
pendingNotifications.clear()
|
||||
}
|
||||
|
||||
fun showNotification(jdk: Sdk, actualItem: JdkItem, newItem: JdkItem) : Unit = lock.withLock {
|
||||
val newNotification = JdkUpdateNotification(
|
||||
jdk = jdk,
|
||||
oldItem = actualItem,
|
||||
newItem = newItem,
|
||||
whenComplete = {
|
||||
lock.withLock {
|
||||
pendingNotifications.remove(jdk, it)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val currentNotification = pendingNotifications[jdk]
|
||||
if (currentNotification != null && !currentNotification.tryReplaceWithNewerNotification(newNotification)) return
|
||||
pendingNotifications[jdk] = newNotification
|
||||
newNotification
|
||||
}.showNotificationIfAbsent()
|
||||
|
||||
fun hideNotification(jdk: Sdk) = lock.withLock {
|
||||
pendingNotifications[jdk]?.tryReplaceWithNewerNotification()
|
||||
}
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.projectRoots.impl.jdkDownloader
|
||||
|
||||
import com.intellij.ide.actions.SettingsEntryPointAction
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue
|
||||
import com.intellij.util.ui.update.Update
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
|
||||
class JdkSettingsActionRegistryState: BaseState() {
|
||||
val knownActions by list<String>()
|
||||
}
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@State(name = "jdk-update-state", storages = [Storage(StoragePathMacros.CACHE_FILE)], allowLoadInTests = true)
|
||||
class JdkUpdaterNotifications : SimplePersistentStateComponent<JdkSettingsActionRegistryState>(JdkSettingsActionRegistryState()), Disposable {
|
||||
private val lock = ReentrantLock()
|
||||
private val pendingNotifications = HashMap<Sdk, JdkUpdateNotification>()
|
||||
private var pendingActionsCopy = listOf<JdkUpdateNotification.JdkUpdateSuggestionAction>()
|
||||
|
||||
private val alarm = MergingUpdateQueue("jdk-update-actions", 500, true, null, this, null, Alarm.ThreadToUse.POOLED_THREAD).usePassThroughInUnitTestMode()
|
||||
override fun dispose() = Unit
|
||||
|
||||
private fun scheduleUpdate() {
|
||||
alarm.queue(object: Update(this) {
|
||||
override fun run() = lock.withLock {
|
||||
//we would not like it to overflow
|
||||
if (state.knownActions.size > 300) {
|
||||
val tail = state.knownActions.toList().takeLast(30).toHashSet()
|
||||
state.knownActions.clear()
|
||||
state.knownActions.addAll(tail)
|
||||
state.intIncrementModificationCount()
|
||||
}
|
||||
|
||||
val ids = pendingNotifications.values.map { it.persistentId }.toSortedSet()
|
||||
val iconState = if (!state.knownActions.containsAll(ids)) {
|
||||
state.knownActions.addAll(ids)
|
||||
state.intIncrementModificationCount()
|
||||
|
||||
SettingsEntryPointAction.IconState.ApplicationUpdate
|
||||
} else {
|
||||
SettingsEntryPointAction.IconState.Current
|
||||
}
|
||||
|
||||
invokeLater {
|
||||
pendingActionsCopy = pendingNotifications.values.sortedBy { it.persistentId }.map { it.updateAction }
|
||||
SettingsEntryPointAction.updateState(iconState)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun hideNotification(jdk: Sdk) {
|
||||
lock.withLock {
|
||||
pendingNotifications[jdk]?.tryReplaceWithNewerNotification()
|
||||
}
|
||||
scheduleUpdate()
|
||||
}
|
||||
|
||||
fun showNotification(jdk: Sdk, actualItem: JdkItem, newItem: JdkItem): JdkUpdateNotification? {
|
||||
val newNotification = lock.withLock {
|
||||
val newNotification = JdkUpdateNotification(
|
||||
jdk = jdk,
|
||||
oldItem = actualItem,
|
||||
newItem = newItem,
|
||||
whenComplete = {
|
||||
lock.withLock {
|
||||
pendingNotifications.remove(jdk, it)
|
||||
}
|
||||
scheduleUpdate()
|
||||
}
|
||||
)
|
||||
|
||||
val currentNotification = pendingNotifications[jdk]
|
||||
if (currentNotification != null && !currentNotification.tryReplaceWithNewerNotification(newNotification)) return null
|
||||
pendingNotifications[jdk] = newNotification
|
||||
newNotification
|
||||
}
|
||||
|
||||
scheduleUpdate()
|
||||
return newNotification
|
||||
}
|
||||
|
||||
fun getActions() : List<JdkUpdateNotification.JdkUpdateSuggestionAction> = pendingActionsCopy
|
||||
}
|
||||
|
||||
class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider {
|
||||
override fun getUpdateActions(context: DataContext) = service<JdkUpdaterNotifications>().getActions()
|
||||
}
|
||||
@@ -269,10 +269,8 @@ progress.title.updating.jdk.0.to.1=Updating JDK "{0}" to {1}
|
||||
progress.title.updating.jdk.failed=Failed to download and install JDK {0}
|
||||
progress.text.updating.jdk.setting.up=Configuring JDK
|
||||
progress.title.checking.for.jdk.updates=Checking for JDK updates
|
||||
notification.title.jdk.update.found=New JDK version is available
|
||||
notification.text.jdk.update.found={1}<br/>Current version is {2} (JDK "{0}")
|
||||
notification.link.jdk.update.apply=Download
|
||||
notification.link.jdk.update.skip=Skip this update
|
||||
action.title.jdk.update.found=Update JDK "{0}" to {1}
|
||||
action.description.jdk.update.found=JDK update to {1} is available for JDK \'{0}\'. Current version is {2}
|
||||
notification.link.jdk.update.retry=Retry
|
||||
dialog.text.resolving.sdks.item={0} "{1}"
|
||||
unknown.sdk.with.no.name=<unknown>
|
||||
|
||||
@@ -886,7 +886,6 @@
|
||||
configurationSchemaKey="java"
|
||||
/>
|
||||
|
||||
<notificationGroup id="JDK Update" displayType="BALLOON"/>
|
||||
<notificationGroup id="JDK Update Error" displayType="STICKY_BALLOON" />
|
||||
|
||||
<notificationGroup id="Runtime Chooser" displayType="BALLOON" bundle="messages.LangBundle" key="notification.title.choose.ide.runtime" />
|
||||
@@ -894,6 +893,7 @@
|
||||
<registryKey key="runtime.chooser.pretend.major" description="Makes Runtime Chooser show JBRs from previous releases" defaultValue="" />
|
||||
|
||||
<backgroundPostStartupActivity implementation="com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkUpdaterStartup" />
|
||||
<settingsEntryPointActionProvider implementation="com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkSettingsActionRegistryActionProvider"/>
|
||||
|
||||
<registryKey key="jdk.auto.setup" defaultValue="true" description="Attempt to use local or downloadable SDK to configure project"/>
|
||||
<registryKey key="jdk.updater" defaultValue="true" description="Check for JDK updates and suggest actions in notifications"/>
|
||||
|
||||
Reference in New Issue
Block a user