diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdater.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdater.kt index c727d145c069..b3b6b0a99b07 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdater.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdater.kt @@ -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("com.intellij.jdkUpdateCheckContributor") +private val EP_NAME: ExtensionPointName = + 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 } -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().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() - -@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().queue(object: UnknownSdkTrackerTask { - override fun createCollector(): UnknownSdkCollector? { - if (!isEnabled()) return null - return object : UnknownSdkCollector(project) { - override fun getContributors(): List { - return super.getContributors() + EP_NAME.extensionList.map { - object : UnknownSdkContributor { - override fun contributeUnknownSdks(project: Project) = listOf() - override fun contributeKnownSdks(project: Project): List = 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> { continuation -> + project.service().queue(object: UnknownSdkTrackerTask { + override fun createCollector(): UnknownSdkCollector { + return object : UnknownSdkCollector(project) { + override fun getContributors(): List { + return super.getContributors() + EP_NAME.extensionList.map { + object : UnknownSdkContributor { + override fun contributeUnknownSdks(project: Project) = listOf() + override fun contributeKnownSdks(project: Project): List = 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, 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) \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdaterActions.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdaterActions.kt index b213daa8da26..7f3812b5eff3 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdaterActions.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkUpdaterActions.kt @@ -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 = pendingActionsCopy } -class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider { - override fun getUpdateActions(context: DataContext): List = service().getActions() +internal class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider { + override fun getUpdateActions(context: DataContext): List = + service().getActions() }