[platform] skipping irrelevant IDE update data

GitOrigin-RevId: 91c4baaeb3a1b12bd854579e748d1f21a861d64e
This commit is contained in:
Roman Shevchenko
2021-04-06 20:41:26 +00:00
committed by intellij-monorepo-bot
parent 1e8e201eb6
commit d7be3e5675
5 changed files with 75 additions and 86 deletions
@@ -208,11 +208,10 @@ object UpdateChecker {
@JvmStatic
@Throws(IOException::class, JDOMException::class)
fun loadProductData(indicator: ProgressIndicator?): Product? {
fun loadProductData(indicator: ProgressIndicator?): Product? =
productDataLock.withLock {
SoftReference.dereference(productDataCache)?.let {
return it.getOrThrow()
}
val cached = SoftReference.dereference(productDataCache)
if (cached != null) return@withLock cached.getOrThrow()
val result = runCatching {
var url = Urls.newFromEncoded(updateUrl)
@@ -220,15 +219,13 @@ object UpdateChecker {
url = UpdateRequestParameters.amendUpdateRequest(url)
}
LOG.debug { "loading ${url}" }
val updates = HttpRequests.request(url).connect { UpdatesInfo(JDOMUtil.load(it.getReader(indicator))) }
updates.get(ApplicationInfo.getInstance().build.productCode)
parseUpdateData(HttpRequests.request(url).connect { JDOMUtil.load(it.getReader(indicator)) })
}
productDataCache = SoftReference(result)
AppExecutorUtil.getAppScheduledExecutorService().schedule(this::clearProductDataCache, PRODUCT_DATA_TTL_MS, TimeUnit.MILLISECONDS)
return result.getOrThrow()
return@withLock result.getOrThrow()
}
}
private fun clearProductDataCache() {
if (productDataLock.tryLock(1, TimeUnit.MILLISECONDS)) { // longer means loading now, no much sense in clearing
@@ -666,8 +663,7 @@ object UpdateChecker {
if (!ourHasFailedPlugins) {
val app = ApplicationManager.getApplication()
if (app != null && !app.isDisposed && UpdateSettings.getInstance().isPluginsCheckNeeded) {
val pluginDescriptor = PluginManagerCore.getPlugin(
PluginUtil.getInstance().findPluginId(event.throwable))
val pluginDescriptor = PluginManagerCore.getPlugin(PluginUtil.getInstance().findPluginId(event.throwable))
if (pluginDescriptor != null && !pluginDescriptor.isBundled) {
ourHasFailedPlugins = true
updateAndShowResult()
@@ -678,7 +674,7 @@ object UpdateChecker {
/** A helper method for manually testing platform updates (see [com.intellij.internal.ShowUpdateInfoDialogAction]). */
@ApiStatus.Internal
fun testPlatformUpdate(project: Project?, updateInfoText: String, patchFilePath: String?, forceUpdate: Boolean) {
fun testPlatformUpdate(project: Project?, updateDataText: String, patchFilePath: String?, forceUpdate: Boolean) {
if (!ApplicationManager.getApplication().isInternal) {
throw IllegalStateException()
}
@@ -686,15 +682,16 @@ object UpdateChecker {
val channel: UpdateChannel?
val newBuild: BuildInfo?
val patches: UpdateChain?
val currentBuild = ApplicationInfo.getInstance().build
if (forceUpdate) {
val node = JDOMUtil.load(updateInfoText).getChild("product")?.getChild("channel") ?: throw IllegalArgumentException("//channel missing")
channel = UpdateChannel(node)
val node = JDOMUtil.load(updateDataText).getChild("product")?.getChild("channel") ?: throw IllegalArgumentException("//channel missing")
channel = UpdateChannel(node, currentBuild.productCode)
newBuild = channel.builds.firstOrNull() ?: throw IllegalArgumentException("//build missing")
patches = newBuild.patches.firstOrNull()?.let { UpdateChain(listOf(it.fromBuild, newBuild.number), it.size) }
}
else {
val updateInfo = UpdatesInfo(JDOMUtil.load(updateInfoText))
val strategy = UpdateStrategy(ApplicationInfo.getInstance().build, updateInfo, UpdateSettings.getInstance())
val product = parseUpdateData(JDOMUtil.load(updateDataText), currentBuild.productCode)
val strategy = UpdateStrategy(currentBuild, product, UpdateSettings.getInstance())
val checkForUpdateResult = strategy.checkForUpdates()
channel = checkForUpdateResult.updatedChannel
newBuild = checkForUpdateResult.newBuild
@@ -1,32 +1,33 @@
// 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.
// 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.
@file:JvmName("UpdateData")
package com.intellij.openapi.updateSettings.impl
import com.intellij.openapi.application.impl.ApplicationInfoImpl
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.BuildNumber
import com.intellij.openapi.util.BuildRange
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.SystemInfo
import org.jdom.Element
import org.jdom.JDOMException
import org.jetbrains.annotations.ApiStatus
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
class UpdatesInfo(node: Element) {
private val products = node.getChildren("product").map(::Product)
operator fun get(code: String): Product? = products.find { code in it.codes }
}
fun parseUpdateData(node: Element, productCode: String = ApplicationInfo.getInstance().build.productCode): Product? =
node.getChildren("product")
.find { it.getChildren("code").any { code -> code.value.trim() == productCode } }
?.let { Product(it, productCode) }
class Product internal constructor(node: Element) {
class Product internal constructor(node: Element, private val productCode: String) {
@NlsSafe val name: String = node.getMandatoryAttributeValue("name")
val codes: Set<String> = node.getChildren("code").map { it.value.trim() }.toSet()
val channels: List<UpdateChannel> = node.getChildren("channel").map(::UpdateChannel)
val channels: List<UpdateChannel> = node.getChildren("channel").map { UpdateChannel(it, productCode) }
override fun toString(): String = codes.firstOrNull() ?: "-"
override fun toString(): String = productCode
}
class UpdateChannel internal constructor(node: Element) {
class UpdateChannel internal constructor(node: Element, productCode: String) {
enum class Licensing { EAP, RELEASE; }
val id: String = node.getMandatoryAttributeValue("id")
@@ -34,13 +35,13 @@ class UpdateChannel internal constructor(node: Element) {
val licensing: Licensing = if (node.getAttributeValue("licensing") == "eap") Licensing.EAP else Licensing.RELEASE
val evalDays: Int = node.getAttributeValue("evalDays")?.toInt() ?: 30
val url: String? = node.getAttributeValue("url")
val builds: List<BuildInfo> = node.getChildren("build").map(::BuildInfo)
val builds: List<BuildInfo> = node.getChildren("build").map { BuildInfo(it, productCode) }
override fun toString(): String = id
}
class BuildInfo internal constructor(node: Element) {
val number: BuildNumber = parseBuildNumber(node.getMandatoryAttributeValue("fullNumber", "number"))
class BuildInfo internal constructor(node: Element, productCode: String) {
val number: BuildNumber = parseBuildNumber(node.getMandatoryAttributeValue("fullNumber", "number"), productCode)
val apiVersion: BuildNumber = node.getAttributeValue("apiVersion")?.let { BuildNumber.fromStringWithProductCode(it, number.productCode) } ?: number
val version: String = node.getAttributeValue("version") ?: ""
@NlsSafe val message: String = node.getChild("message")?.value ?: ""
@@ -48,15 +49,11 @@ class BuildInfo internal constructor(node: Element) {
val releaseDate: Date? = parseDate(node.getAttributeValue("releaseDate"))
val target: BuildRange? = BuildRange.fromStrings(node.getAttributeValue("targetSince"), node.getAttributeValue("targetUntil"))
val patches: List<PatchInfo> = node.getChildren("patch").map(::PatchInfo)
val downloadUrl: String? = node.getChildren("button").find { it.getAttributeValue("download") != null }?.getMandatoryAttributeValue("url")
private val buttons: List<ButtonInfo> = node.getChildren("button").map(::ButtonInfo)
private fun parseBuildNumber(value: String): BuildNumber {
var buildNumber = BuildNumber.fromString(value)!!
if (buildNumber.productCode.isEmpty()) {
buildNumber = BuildNumber(ApplicationInfoImpl.getShadowInstance().build.productCode, *buildNumber.components)
}
return buildNumber
private fun parseBuildNumber(value: String, productCode: String): BuildNumber {
val buildNumber = BuildNumber.fromString(value)!!
return if (buildNumber.productCode.isNotEmpty()) buildNumber else BuildNumber(productCode, *buildNumber.components)
}
private fun parseDate(value: String?): Date? =
@@ -65,20 +62,11 @@ class BuildInfo internal constructor(node: Element) {
SimpleDateFormat("yyyyMMdd", Locale.US).parse(value) // same as the 'majorReleaseDate' in ApplicationInfo.xml
}
catch (e: ParseException) {
Logger.getInstance(BuildInfo::class.java).info("invalid build release date: $value")
logger<BuildInfo>().info("invalid build release date: ${value}")
null
}
val downloadUrl: String?
get() = buttons.find(ButtonInfo::isDownload)?.url
override fun toString(): String = "${number}/${version}"
private class ButtonInfo constructor(node: Element) {
// "name" is no longer used
val url: String = node.getMandatoryAttributeValue("url")
val isDownload: Boolean = node.getAttributeValue("download") != null
}
}
class PatchInfo internal constructor(node: Element) {
@@ -96,3 +84,11 @@ private fun Element.getMandatoryAttributeValue(attribute: String) =
private fun Element.getMandatoryAttributeValue(attribute: String, fallback: String) =
getAttributeValue(attribute) ?: getMandatoryAttributeValue(fallback)
//<editor-fold desc="Deprecated stuff.">
@Deprecated("Please use `parseUpdateData` instead")
@ApiStatus.ScheduledForRemoval(inVersion = "2022.2")
class UpdatesInfo(node: Element) {
val product: Product? = parseUpdateData(node)
}
//</editor-fold>
@@ -1,4 +1,4 @@
// 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.
// 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.updateSettings.impl
import com.intellij.openapi.updateSettings.UpdateStrategyCustomization
@@ -6,13 +6,17 @@ import com.intellij.openapi.util.BuildNumber
import com.intellij.util.containers.MultiMap
import com.intellij.util.graph.GraphAlgorithms
import com.intellij.util.graph.InboundSemiGraph
import org.jetbrains.annotations.ApiStatus
import java.util.*
private val NUMBER = Regex("\\d+")
class UpdateStrategy(private val currentBuild: BuildNumber, private val product: Product?, private val settings: UpdateSettings) {
@Deprecated("Please use `UpdateStrategy(BuildNumber, Product, UpdateSettings)` instead")
@ApiStatus.ScheduledForRemoval(inVersion = "2022.2")
@Suppress("DEPRECATION")
constructor(currentBuild: BuildNumber, updates: UpdatesInfo, settings: UpdateSettings) :
this(currentBuild, updates[currentBuild.productCode], settings)
this(currentBuild, updates.product, settings)
private val customization = UpdateStrategyCustomization.getInstance()
@@ -1,11 +1,8 @@
// 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.
// 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.ide.updates
import com.intellij.openapi.updateSettings.impl.ChannelStatus
import com.intellij.openapi.updateSettings.impl.UpdateChannel
import com.intellij.openapi.updateSettings.impl.UpdatesInfo
import com.intellij.openapi.updateSettings.impl.*
import com.intellij.openapi.util.JDOMUtil
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assume.assumeTrue
@@ -14,11 +11,10 @@ import java.io.IOException
import java.net.URL
import java.text.SimpleDateFormat
class UpdateInfoParsingTest : BareTestFixtureTestCase() {
class UpdateInfoParsingTest {
@Test fun liveJetBrainsUpdateFile() {
try {
val info = load(URL("https://www.jetbrains.com/updates/updates.xml").readText())
assertNotNull(info["IC"])
assertNotNull(load(URL("https://www.jetbrains.com/updates/updates.xml").readText(), "IC"))
}
catch (e: IOException) {
assumeTrue(e.toString(), false)
@@ -27,8 +23,7 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() {
@Test fun liveAndroidUpdateFile() {
try {
val info = load(URL("https://dl.google.com/android/studio/patches/updates.xml").readText())
assertNotNull(info["AI"])
assertNotNull(load(URL("https://dl.google.com/android/studio/patches/updates.xml").readText(), "AI"))
}
catch (e: IOException) {
assumeTrue(e.toString(), false)
@@ -36,21 +31,23 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() {
}
@Test fun emptyChannels() {
val info = load("""
val updates = """
<products>
<product name="IntelliJ IDEA">
<code>IU</code>
<code>IC</code>
</product>
</products>""".trimIndent())
val product = info["IU"]!!
assertEquals("IntelliJ IDEA", product.name)
assertEquals(0, product.channels.size)
assertEquals(product, info["IC"])
</products>""".trimIndent()
val ultimate = load(updates, "IU")!!
assertEquals("IntelliJ IDEA", ultimate.name)
assertEquals(0, ultimate.channels.size)
val community = load(updates, "IC")!!
assertEquals("IntelliJ IDEA", community.name)
assertEquals(0, community.channels.size)
}
@Test fun oneProductOnly() {
val info = load("""
val product = load("""
<products>
<product name="IntelliJ IDEA">
<code>IU</code>
@@ -69,9 +66,8 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() {
</build>
</channel>
</product>
</products>""".trimIndent())
</products>""".trimIndent(), "IU")!!
val product = info["IU"]!!
assertEquals("IntelliJ IDEA", product.name)
assertEquals(2, product.channels.size)
@@ -90,7 +86,7 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() {
}
@Test fun targetRanges() {
val info = load("""
val product = load("""
<products>
<product name="IntelliJ IDEA">
<code>IU</code>
@@ -100,12 +96,12 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() {
<build number="2016.1.11" version="2016.1"/>
</channel>
</product>
</products>""".trimIndent())
assertEquals(2, info["IU"]!!.channels[0].builds.count { it.target != null })
</products>""".trimIndent(), "IU")
assertEquals(2, product!!.channels[0].builds.count { it.target != null })
}
@Test fun fullBuildNumbers() {
val info = load("""
val buildInfo = load("""
<products>
<product name="IntelliJ IDEA">
<code>IU</code>
@@ -115,11 +111,10 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() {
</build>
</channel>
</product>
</products>""".trimIndent())
val buildInfo = info["IU"]!!.channels[0].builds[0]
</products>""".trimIndent(), "IU")!!.channels[0].builds[0]
assertEquals("162.100.1", buildInfo.number.asStringWithoutProductCode())
assertEquals("162.99.2", buildInfo.patches[0].fromBuild.asStringWithoutProductCode())
}
private fun load(text: String) = UpdatesInfo(JDOMUtil.load(text))
private fun load(text: String, productCode: String): Product? = parseUpdateData(JDOMUtil.load(text), productCode)
}
@@ -1,4 +1,4 @@
// 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.
// 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.ide.updates
import com.intellij.openapi.updateSettings.impl.*
@@ -272,28 +272,25 @@ class UpdateStrategyTest : BareTestFixtureTestCase() {
assertThat(result.patches?.chain).isEqualTo(listOf("183.3795.13", "183.3975.18", "183.4139.22").map(BuildNumber::fromString))
}
//<editor-fold desc="Helpers.">
private fun check(currentBuild: String,
selectedChannel: ChannelStatus,
testData: String,
ignoredBuilds: List<String> = emptyList()): CheckForUpdateResult {
val updates = UpdatesInfo(JDOMUtil.load("""
val product = parseUpdateData(JDOMUtil.load("""
<products>
<product name="IntelliJ IDEA">
<code>IU</code>
$testData
${testData}
</product>
</products>"""))
</products>"""), "IU")
val settings = UpdateSettings()
settings.selectedChannelStatus = selectedChannel
settings.ignoredBuildNumbers += ignoredBuilds
val result = UpdateStrategy(BuildNumber.fromString(currentBuild)!!, updates, settings).checkForUpdates()
val result = UpdateStrategy(BuildNumber.fromString(currentBuild)!!, product, settings).checkForUpdates()
assertEquals(UpdateStrategy.State.LOADED, result.state)
return result
}
private fun assertBuild(expected: String, build: BuildInfo?) {
private fun assertBuild(expected: String, build: BuildInfo?) =
assertEquals(expected, build?.number?.asStringWithoutProductCode())
}
//</editor-fold>
}
}