mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] IDEA-337233 Delay Checking for JDK updates
GitOrigin-RevId: 21edb1ec5a0cea2fb3f9ea049330985b697a0c27
This commit is contained in:
committed by
intellij-monorepo-bot
parent
605e5da097
commit
7c0ce1b768
+63
-115
@@ -2,158 +2,103 @@
|
||||
package com.intellij.openapi.projectRoots.impl.jdkDownloader
|
||||
|
||||
import com.intellij.execution.wsl.WslDistributionManager
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.extensions.ExtensionNotApplicableException
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.progress.coroutineToIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectBundle
|
||||
import com.intellij.openapi.projectRoots.JavaSdkType
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.*
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
import com.intellij.openapi.roots.ModuleRootListener
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.roots.ui.configuration.UnknownSdk
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* This extension point is used to collect
|
||||
* additional [Sdk] instances to check for a possible
|
||||
* JDK update
|
||||
* Used to collect additional [Sdk] instances to check for a possible JDK update.
|
||||
*/
|
||||
private val EP_NAME = ExtensionPointName<JdkUpdateCheckContributor>("com.intellij.jdkUpdateCheckContributor")
|
||||
private val EP_NAME: ExtensionPointName<JdkUpdateCheckContributor> =
|
||||
ExtensionPointName("com.intellij.jdkUpdateCheckContributor")
|
||||
|
||||
interface JdkUpdateCheckContributor {
|
||||
/**
|
||||
* Executed from any thread (possibly without read-lock) to
|
||||
* collect SDKs, which should be considered for
|
||||
* JDK Update check
|
||||
* Executed from any thread (possibly without read-lock) to collect SDKs, which should be considered for JDK Update check.
|
||||
*/
|
||||
fun contributeJdks(project: Project): List<Sdk>
|
||||
}
|
||||
|
||||
private fun isEnabled(project: Project) = !project.isDefault &&
|
||||
Registry.`is`("jdk.updater") &&
|
||||
!ApplicationManager.getApplication().isUnitTestMode &&
|
||||
!ApplicationManager.getApplication().isHeadlessEnvironment
|
||||
|
||||
private class JdkUpdaterStartup : ProjectActivity {
|
||||
override suspend fun execute(project: Project) {
|
||||
if (isEnabled(project)) {
|
||||
project.service<JdkUpdatesCollector>().updateNotifications()
|
||||
}
|
||||
}
|
||||
private fun isEnabled(project: Project): Boolean {
|
||||
return !project.isDefault &&
|
||||
!project.isDisposed &&
|
||||
Registry.`is`("jdk.updater") &&
|
||||
!ApplicationManager.getApplication().isUnitTestMode &&
|
||||
!ApplicationManager.getApplication().isHeadlessEnvironment
|
||||
}
|
||||
|
||||
private val LOG = logger<JdkUpdatesCollector>()
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
private class JdkUpdatesCollectorQueue : UnknownSdkCollectorQueue(7_000)
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class JdkUpdatesCollector(private val project: Project) : Disposable {
|
||||
override fun dispose(): Unit = Unit
|
||||
|
||||
private class JdkUpdaterStartup : ProjectActivity {
|
||||
init {
|
||||
schedule()
|
||||
}
|
||||
|
||||
private fun isEnabled() = isEnabled(project)
|
||||
|
||||
|
||||
private fun schedule() {
|
||||
if (!isEnabled()) return
|
||||
|
||||
val future = AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay(
|
||||
Runnable {
|
||||
if (project.isDisposed) return@Runnable
|
||||
|
||||
try {
|
||||
updateNotifications()
|
||||
}
|
||||
catch (t: Throwable) {
|
||||
if (t is ControlFlowException) return@Runnable
|
||||
LOG.warn("Failed to complete JDK Update check. ${t.message}", t)
|
||||
}
|
||||
},
|
||||
12,
|
||||
12,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
Disposer.register(this, Disposable { future.cancel(false) })
|
||||
|
||||
val myLastKnownModificationId = AtomicLong(-100_500)
|
||||
|
||||
project.messageBus
|
||||
.connect(this)
|
||||
.subscribe(
|
||||
ModuleRootListener.TOPIC, object : ModuleRootListener {
|
||||
override fun rootsChanged(event: ModuleRootEvent) {
|
||||
if (event.isCausedByFileTypesChange) return
|
||||
|
||||
//an optimization - we do not scan for JDKs if there we no ProjectRootManager modifications change
|
||||
//this avoids VirtualFilePointers invalidation
|
||||
val newCounterValue = ProjectRootManager.getInstance(project).modificationCount
|
||||
if (myLastKnownModificationId.getAndSet(newCounterValue) == newCounterValue) return
|
||||
|
||||
updateNotifications()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun updateNotifications() {
|
||||
if (!isEnabled()) {
|
||||
return
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
throw ExtensionNotApplicableException.create()
|
||||
}
|
||||
}
|
||||
|
||||
project.service<JdkUpdatesCollectorQueue>().queue(object: UnknownSdkTrackerTask {
|
||||
override fun createCollector(): UnknownSdkCollector? {
|
||||
if (!isEnabled()) return null
|
||||
return object : UnknownSdkCollector(project) {
|
||||
override fun getContributors(): List<UnknownSdkContributor> {
|
||||
return super.getContributors() + EP_NAME.extensionList.map {
|
||||
object : UnknownSdkContributor {
|
||||
override fun contributeUnknownSdks(project: Project) = listOf<UnknownSdk>()
|
||||
override fun contributeKnownSdks(project: Project): List<Sdk> = it.contributeJdks(project)
|
||||
override suspend fun execute(project: Project) {
|
||||
delay(60.seconds) // no hurry!
|
||||
|
||||
if (isEnabled(project)) {
|
||||
updateNotifications(project)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateNotifications(project: Project) {
|
||||
val knownSdks = suspendCancellableCoroutine<Collection<Sdk>> { continuation ->
|
||||
project.service<JdkUpdatesCollectorQueue>().queue(object: UnknownSdkTrackerTask {
|
||||
override fun createCollector(): UnknownSdkCollector {
|
||||
return object : UnknownSdkCollector(project) {
|
||||
override fun getContributors(): List<UnknownSdkContributor> {
|
||||
return super.getContributors() + EP_NAME.extensionList.map {
|
||||
object : UnknownSdkContributor {
|
||||
override fun contributeUnknownSdks(project: Project) = listOf<UnknownSdk>()
|
||||
override fun contributeKnownSdks(project: Project): List<Sdk> = it.contributeJdks(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLookupCompleted(snapshot: UnknownSdkSnapshot) {
|
||||
if (!isEnabled()) return
|
||||
|
||||
//this callback happens in the GUI thread!
|
||||
val knownSdks = snapshot
|
||||
.knownSdks
|
||||
.filter { it.sdkType is JavaSdkType && it.sdkType !is DependentSdkType }
|
||||
|
||||
if (knownSdks.isEmpty()) return
|
||||
|
||||
ProgressManager.getInstance().run(
|
||||
object : Task.Backgroundable(project, ProjectBundle.message("progress.title.checking.for.jdk.updates"), true, ALWAYS_BACKGROUND) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
updateWithSnapshot(knownSdks.distinct().sortedBy { it.name }, indicator)
|
||||
}
|
||||
override fun onLookupCompleted(snapshot: UnknownSdkSnapshot) {
|
||||
if (!isEnabled(project)) {
|
||||
continuation.resumeWith(Result.success(emptyList()))
|
||||
return
|
||||
}
|
||||
)
|
||||
|
||||
// this callback happens in the GUI thread!
|
||||
val knownSdks = snapshot.knownSdks
|
||||
.filter { it.sdkType is JavaSdkType && it.sdkType !is DependentSdkType }
|
||||
|
||||
continuation.resumeWith(Result.success(knownSdks))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (knownSdks.isEmpty()) return
|
||||
|
||||
withBackgroundProgress(project, ProjectBundle.message("progress.title.checking.for.jdk.updates")) {
|
||||
coroutineToIndicator {
|
||||
updateWithSnapshot(knownSdks.distinct().sortedBy { it.name }, ProgressManager.getInstance().progressIndicator)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWithSnapshot(knownSdks: List<Sdk>, indicator: ProgressIndicator) {
|
||||
@@ -191,9 +136,12 @@ internal class JdkUpdatesCollector(private val project: Project) : Disposable {
|
||||
noUpdatesFor -= jdk
|
||||
}
|
||||
|
||||
//handle the case, when a JDK is no longer requiring an update
|
||||
// handle the case, when a JDK is no longer requiring an update
|
||||
for (jdk in noUpdatesFor) {
|
||||
notifications.hideNotification(jdk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
private class JdkUpdatesCollectorQueue : UnknownSdkCollectorQueue(7_000)
|
||||
+9
-7
@@ -4,8 +4,10 @@ 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.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue
|
||||
@@ -13,7 +15,6 @@ import com.intellij.util.ui.update.Update
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
class JdkUpdaterNotifications : Disposable {
|
||||
private val lock = ReentrantLock()
|
||||
@@ -30,9 +31,9 @@ class JdkUpdaterNotifications : Disposable {
|
||||
pendingNotifications.values.sortedBy { it.persistentId }.map { it.updateAction }
|
||||
}
|
||||
|
||||
invokeLater {
|
||||
ApplicationManager.getApplication().invokeLater({
|
||||
SettingsEntryPointAction.updateState()
|
||||
}
|
||||
}, ModalityState.nonModal())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -72,6 +73,7 @@ class JdkUpdaterNotifications : Disposable {
|
||||
fun getActions() : List<JdkUpdateNotification.JdkUpdateSuggestionAction> = pendingActionsCopy
|
||||
}
|
||||
|
||||
class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider {
|
||||
override fun getUpdateActions(context: DataContext): List<JdkUpdateNotification.JdkUpdateSuggestionAction> = service<JdkUpdaterNotifications>().getActions()
|
||||
internal class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider {
|
||||
override fun getUpdateActions(context: DataContext): List<JdkUpdateNotification.JdkUpdateSuggestionAction> =
|
||||
service<JdkUpdaterNotifications>().getActions()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user