diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt index 3c0def9b4b54..50302d8d88af 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt @@ -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 diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfo.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfo.kt index 2243d20df386..763dcf836a48 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfo.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfo.kt @@ -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 = node.getChildren("code").map { it.value.trim() }.toSet() - val channels: List = node.getChildren("channel").map(::UpdateChannel) + val channels: List = 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 = node.getChildren("build").map(::BuildInfo) + val builds: List = 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 = node.getChildren("patch").map(::PatchInfo) + val downloadUrl: String? = node.getChildren("button").find { it.getAttributeValue("download") != null }?.getMandatoryAttributeValue("url") - private val buttons: List = 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().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) + +// +@Deprecated("Please use `parseUpdateData` instead") +@ApiStatus.ScheduledForRemoval(inVersion = "2022.2") +class UpdatesInfo(node: Element) { + val product: Product? = parseUpdateData(node) +} +// diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateStrategy.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateStrategy.kt index 53384f56f7ab..4945a0a316a0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateStrategy.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateStrategy.kt @@ -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() diff --git a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt index 60eb7f56137b..ede8b30e93ac 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt @@ -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 = """ IU IC - """.trimIndent()) - val product = info["IU"]!! - assertEquals("IntelliJ IDEA", product.name) - assertEquals(0, product.channels.size) - assertEquals(product, info["IC"]) + """.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(""" IU @@ -69,9 +66,8 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() { - """.trimIndent()) + """.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(""" IU @@ -100,12 +96,12 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() { - """.trimIndent()) - assertEquals(2, info["IU"]!!.channels[0].builds.count { it.target != null }) + """.trimIndent(), "IU") + assertEquals(2, product!!.channels[0].builds.count { it.target != null }) } @Test fun fullBuildNumbers() { - val info = load(""" + val buildInfo = load(""" IU @@ -115,11 +111,10 @@ class UpdateInfoParsingTest : BareTestFixtureTestCase() { - """.trimIndent()) - val buildInfo = info["IU"]!!.channels[0].builds[0] + """.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) } diff --git a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateStrategyTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateStrategyTest.kt index 809aca8f3f44..8faa1f1e39e4 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateStrategyTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateStrategyTest.kt @@ -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)) } - // private fun check(currentBuild: String, selectedChannel: ChannelStatus, testData: String, ignoredBuilds: List = emptyList()): CheckForUpdateResult { - val updates = UpdatesInfo(JDOMUtil.load(""" + val product = parseUpdateData(JDOMUtil.load(""" IU - $testData + ${testData} - """)) + """), "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()) - } - // -} \ No newline at end of file +}