Merge remote-tracking branch 'origin/master' into prendota/plugin-manager-new-protocol
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / updateSettings / impl / UpdateChecker.kt
index d9298a306012821ff81d3306746ada2e5c1cff91..3a8241438be0ebc6e46745fc9ae9b2a86b4d3aa3 100644 (file)
@@ -4,6 +4,7 @@ package com.intellij.openapi.updateSettings.impl
 import com.intellij.ide.IdeBundle
 import com.intellij.ide.externalComponents.ExternalComponentManager
 import com.intellij.ide.plugins.*
+import com.intellij.ide.plugins.marketplace.MarketplaceRequests
 import com.intellij.ide.util.PropertiesComponent
 import com.intellij.notification.*
 import com.intellij.notification.impl.NotificationsConfigurationImpl
@@ -114,6 +115,7 @@ object UpdateChecker {
       override fun run(indicator: ProgressIndicator) = doUpdateAndShowResult(getProject(), !fromSettings,
                                                                              fromSettings || WelcomeFrame.getInstance() != null, true,
                                                                              settings, indicator, null)
+
       override fun isConditionalModal(): Boolean = fromSettings
       override fun shouldStartInBackground(): Boolean = !fromSettings
     })
@@ -123,8 +125,7 @@ object UpdateChecker {
    * An immediate check for plugin updates for use from a command line (read "Toolbox").
    */
   @JvmStatic
-  fun getPluginUpdates(): Collection<PluginDownloader>? =
-    checkPluginsUpdate(EmptyProgressIndicator(), null, ApplicationInfo.getInstance().build)
+  fun getPluginUpdates(): Collection<PluginDownloader>? = checkPluginsUpdate(EmptyProgressIndicator()).availableUpdates
 
   private fun doUpdateAndShowResult(project: Project?,
                                     showSettingsLink: Boolean,
@@ -151,13 +152,11 @@ object UpdateChecker {
     indicator?.text = IdeBundle.message("updates.checking.plugins")
 
     val buildNumber: BuildNumber? = result.newBuild?.apiVersion
-    val incompatiblePlugins: MutableCollection<IdeaPluginDescriptor>? = if (buildNumber != null) HashSet() else null
 
-    val updatedPlugins: Collection<PluginDownloader>?
+    val checkPluginsUpdateResult: CheckPluginsUpdateResult
     val externalUpdates: Collection<ExternalUpdate>?
     try {
-      updatedPlugins = checkPluginsUpdate(indicator, incompatiblePlugins,
-                                          buildNumber)?.filter { downloader -> !PluginUpdateDialog.isIgnored(downloader.descriptor) }
+      checkPluginsUpdateResult = checkPluginsUpdate(indicator, buildNumber)
       externalUpdates = checkExternalUpdates(showDialog, updateSettings, indicator)
     }
     catch (e: IOException) {
@@ -171,7 +170,7 @@ object UpdateChecker {
     UpdateSettings.getInstance().saveLastCheckedInfo()
 
     ApplicationManager.getApplication().invokeLater {
-      showUpdateResult(project, result, updatedPlugins, incompatiblePlugins, externalUpdates, showSettingsLink, showDialog, showEmptyNotification)
+      showUpdateResult(project, result, checkPluginsUpdateResult, externalUpdates, showSettingsLink, showDialog, showEmptyNotification)
       callback?.setDone()
     }
   }
@@ -210,31 +209,51 @@ object UpdateChecker {
     return HttpRequests.request(updateUrl).connect { UpdatesInfo(JDOMUtil.load(it.reader)) }
   }
 
+  private data class CheckPluginsUpdateResult(
+    val availableUpdates: Collection<PluginDownloader>?,
+    val customRepositoryPlugins: Collection<IdeaPluginDescriptor>,
+    val incompatiblePlugins: Collection<IdeaPluginDescriptor>?
+  )
+
+  private val EMPTY_CHECK_UPDATE_RESULT = CheckPluginsUpdateResult(null, emptyList(), null)
+
+  /**
+   * Checks for plugin updates for current build if `build` is not provided.
+   *
+   * Checks for plugin updates for provided `build` and calculates plugins that don't have updates and would be incompatible with provided build.
+   */
   /**
    * If [buildNumber] is null, returns new versions of plugins compatible with the current IDE version. If not null, returns
    * new versions of plugins compatible with the specified build.
    */
-  private fun checkPluginsUpdate(indicator: ProgressIndicator?,
-                                 incompatiblePlugins: MutableCollection<IdeaPluginDescriptor>?,
-                                 buildNumber: BuildNumber?): Collection<PluginDownloader>? {
+  private fun checkPluginsUpdate(
+    indicator: ProgressIndicator?,
+    newBuildNumber: BuildNumber? = null
+  ): CheckPluginsUpdateResult {
     val updateable = collectUpdateablePlugins()
-    if (updateable.isEmpty()) return null
+    if (updateable.isEmpty()) return EMPTY_CHECK_UPDATE_RESULT
 
     val toUpdate = mutableMapOf<PluginId, PluginDownloader>()
 
+    val latestCustomPluginsAsMap = HashMap<PluginId, IdeaPluginDescriptor>()
     val state = InstalledPluginsState.getInstance()
     for (host in RepositoryHelper.getPluginHosts()) {
       try {
         if (host == null && ApplicationInfoEx.getInstanceEx().usesJetBrainsPluginRepository()) {
-          validateCompatibleUpdatesForCurrentPlugins(updateable, toUpdate, buildNumber, state, incompatiblePlugins, indicator)
+          validateCompatibleUpdatesForCurrentPlugins(updateable, toUpdate, newBuildNumber, state, indicator)
         }
         else {
-          val list = RepositoryHelper.loadPlugins(host, buildNumber, indicator)
+          val list = RepositoryHelper.loadPlugins(host, newBuildNumber, indicator)
           for (descriptor in list) {
             val id = descriptor.pluginId
             if (updateable.containsKey(id)) {
               updateable.remove(id)
-              buildDownloaderAndPrepareToInstall(state, descriptor, buildNumber, toUpdate, incompatiblePlugins, indicator, host)
+              buildDownloaderAndPrepareToInstall(state, descriptor, newBuildNumber, toUpdate, indicator, host)
+            }
+            //collect latest plugins from custom repos
+            val storedDescriptor = latestCustomPluginsAsMap[id]
+            if (storedDescriptor == null || StringUtil.compareVersionNumbers(descriptor.version, storedDescriptor.version) > 0) {
+              latestCustomPluginsAsMap[id] = descriptor
             }
           }
         }
@@ -245,13 +264,43 @@ object UpdateChecker {
       }
     }
 
-    if (incompatiblePlugins != null && buildNumber != null) {
+    val incompatiblePlugins: MutableCollection<IdeaPluginDescriptor>? = getIncompatiblePlugins(newBuildNumber, updateable, toUpdate)
+
+    //TODO: check if code in this `if` duplicates `getIncompatiblePlugins`
+    if (incompatiblePlugins != null && newBuildNumber != null) {
       updateable.values.filterNotNull().filterTo(incompatiblePlugins) {
-        it.isEnabled && !PluginManagerCore.isCompatible(it, buildNumber)
+        it.isEnabled && !PluginManagerCore.isCompatible(it, newBuildNumber)
+      }
+    }
+
+    return CheckPluginsUpdateResult(if (toUpdate.isEmpty()) null else toUpdate.values, latestCustomPluginsAsMap.values, incompatiblePlugins)
+  }
+
+  private fun getIncompatiblePlugins(
+    newBuildNumber: BuildNumber?,
+    updateable: MutableMap<PluginId, IdeaPluginDescriptor?>,
+    toUpdate: MutableMap<PluginId, PluginDownloader>
+  ): MutableCollection<IdeaPluginDescriptor>? {
+    if (newBuildNumber == null) return null
+    val incompatiblePlugins: MutableCollection<IdeaPluginDescriptor> =  HashSet()
+    for ((pluginId, installedPlugin) in updateable) {
+      if (!PluginManagerCore.isDisabled(pluginId)) {
+        // collect plugins that were not updated and would be incompatible with the new version
+        if (installedPlugin != null && installedPlugin.isEnabled &&
+            !toUpdate.containsKey(installedPlugin.pluginId) &&
+            !PluginManagerCore.isCompatible(installedPlugin, newBuildNumber)) {
+          incompatiblePlugins += installedPlugin
+        }
       }
     }
+    return incompatiblePlugins
+  }
 
-    return if (toUpdate.isEmpty()) null else toUpdate.values
+  @JvmStatic
+  fun updateDescriptorsForInstalledPlugins(state: InstalledPluginsState) {
+    val updateable = collectUpdateablePlugins()
+    if (updateable.isEmpty()) return
+    validateCompatibleUpdatesForCurrentPlugins(updateable, mutableMapOf(), null, state, null)
   }
 
   /**
@@ -263,24 +312,23 @@ object UpdateChecker {
     toUpdate: MutableMap<PluginId, PluginDownloader>,
     buildNumber: BuildNumber?,
     state: InstalledPluginsState,
-    incompatiblePlugins: MutableCollection<IdeaPluginDescriptor>?,
     indicator: ProgressIndicator?
   ) {
-    val marketplacePluginIds = PluginsMetaLoader.getMarketplacePlugins(indicator)
+    val marketplacePluginIds = MarketplaceRequests.getMarketplacePlugins(indicator)
     val idsToUpdate = updateable.map { it.key.idString }.filter { it in marketplacePluginIds }
-    val updates = PluginsMetaLoader.getLastCompatiblePluginUpdate(idsToUpdate, buildNumber)
+    val updates = MarketplaceRequests.getLastCompatiblePluginUpdate(idsToUpdate, buildNumber)
     for ((id, descriptor) in updateable) {
       val lastUpdate = updates.find { it.pluginId == id.idString } ?: continue
       val isOutdated = descriptor == null || VersionComparatorUtil.compare(lastUpdate.version, descriptor.version) > 0
       if (isOutdated) {
         val newDescriptor = try {
-          PluginsMetaLoader.loadPluginDescriptor(id.idString, lastUpdate, indicator)
+          MarketplaceRequests.loadPluginDescriptor(id.idString, lastUpdate, indicator)
         }
         catch (e: HttpRequests.HttpStatusException) {
           if (e.statusCode == HttpURLConnection.HTTP_NOT_FOUND) continue
           else throw e
         }
-        buildDownloaderAndPrepareToInstall(state, newDescriptor, buildNumber, toUpdate, incompatiblePlugins, indicator, null)
+        buildDownloaderAndPrepareToInstall(state, newDescriptor, buildNumber, toUpdate, indicator, null)
       }
     }
     toUpdate.keys.forEach { updateable.remove(it) }
@@ -291,13 +339,12 @@ object UpdateChecker {
     descriptor: IdeaPluginDescriptor,
     buildNumber: BuildNumber?,
     toUpdate: MutableMap<PluginId, PluginDownloader>,
-    incompatiblePlugins: MutableCollection<IdeaPluginDescriptor>?,
     indicator: ProgressIndicator?,
     host: String?
   ) {
     val downloader = PluginDownloader.createDownloader(descriptor, host, buildNumber)
     state.onDescriptorDownload(descriptor)
-    checkAndPrepareToInstall(downloader, state, toUpdate, incompatiblePlugins, indicator)
+    checkAndPrepareToInstall(downloader, state, toUpdate, indicator)
   }
 
   /**
@@ -366,6 +413,8 @@ object UpdateChecker {
     return result
   }
 
+  @ApiStatus.ScheduledForRemoval(inVersion = "2021.1")
+  @Deprecated("Use `checkAndPrepareToInstall` without `incompatiblePlugins` parameter", level = DeprecationLevel.ERROR)
   @Throws(IOException::class)
   @JvmStatic
   fun checkAndPrepareToInstall(
@@ -374,6 +423,27 @@ object UpdateChecker {
     toUpdate: MutableMap<PluginId, PluginDownloader>,
     incompatiblePlugins: MutableCollection<IdeaPluginDescriptor>?,
     indicator: ProgressIndicator?
+  ) {
+    checkAndPrepareToInstall(downloader, state, toUpdate, indicator)
+
+    val pluginId = downloader.id
+    if (PluginManagerCore.isDisabled(pluginId)) return
+    val installedPlugin = PluginManagerCore.getPlugin(pluginId)
+    // collect plugins that were not updated and would be incompatible with the new version
+    if (incompatiblePlugins != null && installedPlugin != null && installedPlugin.isEnabled &&
+        !toUpdate.containsKey(installedPlugin.pluginId) &&
+        !PluginManagerCore.isCompatible(installedPlugin, downloader.buildNumber)) {
+      incompatiblePlugins += installedPlugin
+    }
+  }
+
+  @Throws(IOException::class)
+  @JvmStatic
+  fun checkAndPrepareToInstall(
+    downloader: PluginDownloader,
+    state: InstalledPluginsState,
+    toUpdate: MutableMap<PluginId, PluginDownloader>,
+    indicator: ProgressIndicator?
   ) {
     @Suppress("NAME_SHADOWING")
     var downloader = downloader
@@ -405,13 +475,6 @@ object UpdateChecker {
         toUpdate[pluginId] = downloader
       }
     }
-
-    // collect plugins that were not updated and would be incompatible with the new version
-    if (incompatiblePlugins != null && installedPlugin != null && installedPlugin.isEnabled &&
-        !toUpdate.containsKey(installedPlugin.pluginId) &&
-        !PluginManagerCore.isCompatible(installedPlugin, downloader.buildNumber)) {
-      incompatiblePlugins += installedPlugin
-    }
   }
 
   private fun showErrorMessage(showDialog: Boolean, message: String) {
@@ -423,8 +486,7 @@ object UpdateChecker {
 
   private fun showUpdateResult(project: Project?,
                                checkForUpdateResult: CheckForUpdateResult,
-                               updatedPlugins: Collection<PluginDownloader>?,
-                               incompatiblePlugins: Collection<IdeaPluginDescriptor>?,
+                               checkPluginsUpdateResult: CheckPluginsUpdateResult,
                                externalUpdates: Collection<ExternalUpdate>?,
                                showSettingsLink: Boolean,
                                showDialog: Boolean,
@@ -432,10 +494,13 @@ object UpdateChecker {
     val updatedChannel = checkForUpdateResult.updatedChannel
     val newBuild = checkForUpdateResult.newBuild
 
+    val updatedPlugins =
+      checkPluginsUpdateResult.availableUpdates?.filter { downloader -> !PluginUpdateDialog.isIgnored(downloader.descriptor) }
+
     if (updatedChannel != null && newBuild != null) {
       val runnable = {
         UpdateInfoDialog(updatedChannel, newBuild, checkForUpdateResult.patches, showSettingsLink, updatedPlugins,
-                         incompatiblePlugins).show()
+                         checkPluginsUpdateResult.incompatiblePlugins).show()
       }
 
       ourShownNotifications.remove(NotificationUniqueType.PLATFORM)?.forEach { it.expire() }
@@ -457,13 +522,13 @@ object UpdateChecker {
 
     var updateFound = false
 
-    if (updatedPlugins != null && !updatedPlugins.isEmpty()) {
+    if (updatedPlugins != null && updatedPlugins.isNotEmpty()) {
       updateFound = true
 
       ourShownNotifications.remove(NotificationUniqueType.PLUGINS)?.forEach { it.expire() }
 
       if (showDialog || !canEnableNotifications()) {
-        PluginUpdateDialog(updatedPlugins).show()
+        PluginUpdateDialog(updatedPlugins, checkPluginsUpdateResult.customRepositoryPlugins).show()
       }
       else {
         val runnable = { PluginManagerConfigurable.showPluginConfigurable(project, updatedPlugins) }
@@ -478,7 +543,7 @@ object UpdateChecker {
           showNotification(project, title, message, runnable, { notification ->
             notification.actions[0].templatePresentation.text = IdeBundle.message("plugin.settings.title")
             notification.actions.add(0, object : NotificationAction(
-                IdeBundle.message(if (updatedPlugins.size == 1) "plugins.configurable.update.button" else "plugin.manager.update.all")) {
+              IdeBundle.message(if (updatedPlugins.size == 1) "plugins.configurable.update.button" else "plugin.manager.update.all")) {
               override fun actionPerformed(e: AnActionEvent, notification: Notification) {
                 notification.expire()
                 PluginUpdateDialog.runUpdateAll(updatedPlugins, e.getData(PlatformDataKeys.CONTEXT_COMPONENT) as JComponent?)
@@ -524,7 +589,7 @@ object UpdateChecker {
         ourShownNotifications.remove(NotificationUniqueType.PLUGINS)?.forEach { it.expire() }
 
         val title = IdeBundle.message("updates.no.updates.notification")
-        showNotification(project, title, "", {}, { notification -> notification.actions.clear()}, NotificationUniqueType.PLUGINS)
+        showNotification(project, title, "", {}, { notification -> notification.actions.clear() }, NotificationUniqueType.PLUGINS)
       }
     }
   }