From 0e94e6253c7cb94983a71840e25bf4068db9de06 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 31 Jul 2018 17:29:05 -0700 Subject: [PATCH] [platform] simplifies updater classes API (IDEA-CR-35410) --- .../impl/CheckForUpdateResult.java | 59 ------------------- .../impl/CheckForUpdateResult.kt | 30 ++++++++++ .../updateSettings/impl/UpdateChecker.kt | 13 ++-- .../openapi/updateSettings/impl/UpdateInfo.kt | 46 --------------- .../updateSettings/impl/UpdateInfoDialog.java | 37 ++++-------- .../updateSettings/impl/UpdateInstaller.kt | 15 ----- .../updateSettings/impl/UpdateStrategy.kt | 54 ++++++++++++++++- .../ide/updates/UpdateStrategyTest.kt | 14 ++--- 8 files changed, 106 insertions(+), 162 deletions(-) delete mode 100644 platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.kt diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.java deleted file mode 100644 index 92dfd04e25bc..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2000-2018 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 org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CheckForUpdateResult { - private final UpdateStrategy.State myState; - private final BuildInfo myNewBuild; - private final UpdateChannel myUpdatedChannel; - private final PatchInfo myPatch; - private final ChainInfo myPatchChain; - private final Exception myError; - - CheckForUpdateResult(@Nullable BuildInfo newBuild, - @Nullable UpdateChannel updatedChannel, - @Nullable PatchInfo patch, - @Nullable ChainInfo chain) { - myState = UpdateStrategy.State.LOADED; - myNewBuild = newBuild; - myUpdatedChannel = updatedChannel; - myPatch = patch; - myPatchChain = chain; - myError = null; - } - - CheckForUpdateResult(@NotNull UpdateStrategy.State state, @Nullable Exception e) { - myState = state; - myNewBuild = null; - myUpdatedChannel = null; - myPatch = null; - myPatchChain = null; - myError = e; - } - - public @NotNull UpdateStrategy.State getState() { - return myState; - } - - public @Nullable BuildInfo getNewBuild() { - return myNewBuild; - } - - public @Nullable UpdateChannel getUpdatedChannel() { - return myUpdatedChannel; - } - - public @Nullable PatchInfo getPatch() { - return myPatch; - } - - public @Nullable ChainInfo getPatchChain() { - return myPatchChain; - } - - public @Nullable Exception getError() { - return myError; - } -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.kt new file mode 100644 index 000000000000..ac7104b42977 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/CheckForUpdateResult.kt @@ -0,0 +1,30 @@ +// Copyright 2000-2018 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.util.BuildNumber + +class UpdateChain internal constructor(val chain: List, val size: String?) + +class CheckForUpdateResult { + val state: UpdateStrategy.State + val newBuild: BuildInfo? + val updatedChannel: UpdateChannel? + val patches: UpdateChain? + val error: Exception? + + internal constructor(newBuild: BuildInfo?, updatedChannel: UpdateChannel?, patches: UpdateChain?) { + this.state = UpdateStrategy.State.LOADED + this.newBuild = newBuild + this.updatedChannel = updatedChannel + this.patches = patches + this.error = null + } + + internal constructor(state: UpdateStrategy.State, error: Exception?) { + this.state = state + this.newBuild = null + this.updatedChannel = null + this.patches = null + this.error = error + } +} \ No newline at end of file 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 7317cbebc7c9..70de60824579 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 @@ -390,10 +390,9 @@ object UpdateChecker { if (updatedChannel != null && newBuild != null) { val runnable = { - val patch = checkForUpdateResult.patch - val chain = checkForUpdateResult.patchChain + val patches = checkForUpdateResult.patches val forceHttps = updateSettings.canUseSecureConnection() - UpdateInfoDialog(updatedChannel, newBuild, patch, chain, enableLink, forceHttps, updatedPlugins, incompatiblePlugins).show() + UpdateInfoDialog(updatedChannel, newBuild, patches, enableLink, forceHttps, updatedPlugins, incompatiblePlugins).show() } ourShownNotifications.remove(NotificationUniqueType.PLATFORM)?.forEach { it.expire() } @@ -549,12 +548,12 @@ object UpdateChecker { val channel: UpdateChannel? val newBuild: BuildInfo? - val patch: PatchInfo? + val patches: UpdateChain? if (forceUpdate) { val node = loadElement(updateInfoText).getChild("product")?.getChild("channel") ?: throw IllegalArgumentException("//channel missing") channel = UpdateChannel(node) newBuild = channel.builds.firstOrNull() ?: throw IllegalArgumentException("//build missing") - patch = newBuild.patches.firstOrNull() + patches = newBuild.patches.firstOrNull()?.let { UpdateChain(listOf(it.fromBuild, newBuild.number), it.size) } } else { val updateInfo = UpdatesInfo(loadElement(updateInfoText)) @@ -562,12 +561,12 @@ object UpdateChecker { val checkForUpdateResult = strategy.checkForUpdates() channel = checkForUpdateResult.updatedChannel newBuild = checkForUpdateResult.newBuild - patch = checkForUpdateResult.patch + patches = checkForUpdateResult.patches } if (channel != null && newBuild != null) { val patchFile = if (patchFilePath != null) File(FileUtil.toSystemDependentName(patchFilePath)) else null - UpdateInfoDialog(channel, newBuild, patch, patchFile).show() + UpdateInfoDialog(channel, newBuild, patches, patchFile).show() } else { NoUpdatesDialog(true).show() 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 3f6634770833..cfe80b54af88 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 @@ -6,9 +6,6 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.BuildNumber import com.intellij.openapi.util.BuildRange import com.intellij.openapi.util.SystemInfo -import com.intellij.util.containers.MultiMap -import com.intellij.util.graph.InboundSemiGraph -import com.intellij.util.graph.impl.ShortestPathFinder import org.jdom.Element import org.jdom.JDOMException import java.text.ParseException @@ -25,45 +22,6 @@ class Product internal constructor(node: Element) { val codes: Set = node.getChildren("code").map { it.value.trim() }.toSet() val channels: List = node.getChildren("channel").map(::UpdateChannel) - fun patchChain(from: BuildNumber, to: BuildNumber): ChainInfo? { - val upgrades = MultiMap() - val sizes = mutableMapOf, Int>() - - channels.forEach { channel -> - channel.builds.forEach { build -> - val toBuild = build.number.withoutProductCode() - build.patches.forEach { patch -> - if (patch.isAvailable) { - val fromBuild = patch.fromBuild.withoutProductCode() - upgrades.putValue(toBuild, fromBuild) - if (patch.size != null) { - val maxSize = Regex("\\d+").findAll(patch.size).map { it.value.toIntOrNull() }.filterNotNull().max() - if (maxSize != null) sizes += (fromBuild to toBuild) to maxSize - } - } - } - } - } - - val graph = object : InboundSemiGraph { - override fun getNodes() = upgrades.keySet() + upgrades.values() - override fun getIn(n: BuildNumber) = upgrades[n].iterator() - } - val path = ShortestPathFinder(graph).findPath(from.withoutProductCode(), to.withoutProductCode()) - if (path == null || path.size <= 2) return null - - var total = 0 - for (i in 1 until path.size) { - val size = sizes[path[i - 1] to path[i]] - if (size == null) { - total = -1 - break - } - total += size - } - return ChainInfo(path, total) - } - override fun toString(): String = codes.firstOrNull() ?: "-" } @@ -114,8 +72,6 @@ class BuildInfo internal constructor(node: Element) { val downloadUrl: String? get() = buttons.find(ButtonInfo::isDownload)?.url - fun patch(from: BuildNumber) = patches.find { it.isAvailable && it.fromBuild.compareTo(from) == 0 } - override fun toString(): String = "${number}/${version}" } @@ -137,8 +93,6 @@ class PatchInfo internal constructor(node: Element) { val isAvailable: Boolean = node.getAttributeValue("exclusions")?.splitToSequence(",")?.none { it.trim() == OS_SUFFIX } ?: true } -class ChainInfo internal constructor(val chain: List, val size: Int) - private fun Element.getMandatoryAttributeValue(attribute: String) = getAttributeValue(attribute) ?: throw JDOMException("${name}@${attribute} missing") diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java index 330fc05daa8c..3ec4f0ec0e1c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java @@ -58,16 +58,14 @@ class UpdateInfoDialog extends AbstractUpdateDialog { private final boolean myForceHttps; private final Collection myUpdatedPlugins; private final BuildInfo myNewBuild; - private final PatchInfo myPatch; - private final ChainInfo myChain; + private final UpdateChain myPatches; private final boolean myWriteProtected; private final Pair myLicenseInfo; private final File myTestPatch; UpdateInfoDialog(@NotNull UpdateChannel channel, @NotNull BuildInfo newBuild, - @Nullable PatchInfo patch, - @Nullable ChainInfo chain, + @Nullable UpdateChain patches, boolean enableLink, boolean forceHttps, @Nullable Collection updatedPlugins, @@ -77,9 +75,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog { myForceHttps = forceHttps; myUpdatedPlugins = updatedPlugins; myNewBuild = newBuild; - myPatch = patch; - myChain = chain; - myWriteProtected = myPatch != null && !SystemInfo.isWindows && !Files.isWritable(Paths.get(PathManager.getHomePath())); + myPatches = patches; + myWriteProtected = myPatches != null && !SystemInfo.isWindows && !Files.isWritable(Paths.get(PathManager.getHomePath())); getCancelAction().putValue(DEFAULT_ACTION, Boolean.TRUE); myLicenseInfo = initLicensingInfo(myUpdatedChannel, myNewBuild); myTestPatch = null; @@ -91,7 +88,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog { } FUSApplicationUsageTrigger.getInstance().trigger(IdeUpdateUsageTriggerCollector.class, "dialog.shown"); - if (myPatch == null && myChain == null) { + if (myPatches == null) { FUSApplicationUsageTrigger.getInstance().trigger(IdeUpdateUsageTriggerCollector.class, "dialog.shown.no.patch"); } else if (!ApplicationManager.getApplication().isRestartCapable()) { @@ -99,14 +96,13 @@ class UpdateInfoDialog extends AbstractUpdateDialog { } } - UpdateInfoDialog(UpdateChannel channel, BuildInfo newBuild, PatchInfo patch, @Nullable File patchFile) { + UpdateInfoDialog(UpdateChannel channel, BuildInfo newBuild, UpdateChain patches, @Nullable File patchFile) { super(true); myUpdatedChannel = channel; myForceHttps = true; myUpdatedPlugins = null; myNewBuild = newBuild; - myPatch = patch; - myChain = null; + myPatches = patches; myWriteProtected = false; myLicenseInfo = null; myTestPatch = patchFile; @@ -153,7 +149,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog { protected Action[] createActions() { List actions = ContainerUtil.newArrayList(); - if (myPatch != null || myChain != null || myTestPatch != null) { + if (myPatches != null || myTestPatch != null) { boolean canRestart = ApplicationManager.getApplication().isRestartCapable(); actions.add(new AbstractAction(IdeBundle.message(canRestart ? "updates.download.and.restart.button" : "updates.apply.manually.button")) { { @@ -170,7 +166,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog { List buttons = myNewBuild.getButtons(); for (ButtonInfo info : buttons) { - if (!info.isDownload() || myPatch == null && myChain == null) { + if (!info.isDownload() || myPatches == null) { actions.add(new ButtonAction(info)); } } @@ -205,12 +201,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog { public void run(@NotNull ProgressIndicator indicator) { String[] command; try { - if (myPatch != null) { - File file = UpdateInstaller.downloadPatchFile(myPatch, myNewBuild.getNumber(), myForceHttps, indicator); - command = UpdateInstaller.preparePatchCommand(file, indicator); - } - else if (myChain != null) { - List files = UpdateInstaller.downloadPatchChain(myChain.getChain(), myForceHttps, indicator); + if (myPatches != null) { + List files = UpdateInstaller.downloadPatchChain(myPatches.getChain(), myForceHttps, indicator); command = UpdateInstaller.preparePatchCommand(files, indicator); } else { @@ -343,11 +335,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog { myCurrentVersion.setText(formatVersion(appInfo.getFullVersion(), appInfo.getBuild())); myNewVersion.setText(formatVersion(myNewBuild.getVersion(), myNewBuild.getNumber())); - if (myPatch != null && !StringUtil.isEmptyOrSpaces(myPatch.getSize())) { - myPatchInfo.setText(myPatch.getSize() + " MB"); - } - else if (myChain != null && myChain.getSize() > 0) { - myPatchInfo.setText(myChain.getSize() + " MB"); + if (myPatches != null && !StringUtil.isEmptyOrSpaces(myPatches.getSize())) { + myPatchInfo.setText(myPatches.getSize() + " MB"); } else { myPatchLabel.setVisible(false); diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInstaller.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInstaller.kt index 1ea336d9fbad..090cb6157e67 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInstaller.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInstaller.kt @@ -27,21 +27,6 @@ object UpdateInstaller { private val patchesUrl: URL get() = URL(System.getProperty("idea.patches.url") ?: ApplicationInfoEx.getInstanceEx().updateUrls.patchesUrl) - @JvmStatic - @Throws(IOException::class) - fun downloadPatchFile(patch: PatchInfo, toBuild: BuildNumber, forceHttps: Boolean, indicator: ProgressIndicator): File { - indicator.text = IdeBundle.message("update.downloading.patch.progress") - val product = ApplicationInfo.getInstance().build.productCode - val from = patch.fromBuild.withoutProductCode().asString() - val to = toBuild.withoutProductCode().asString() - val jdk = if (System.getProperty("idea.java.redist", "").lastIndexOf("NoJavaDistribution") >= 0) "-no-jdk" else "" - val patchName = "${product}-${from}-${to}-patch${jdk}-${PatchInfo.OS_SUFFIX}.jar" - val url = URL(patchesUrl, patchName).toString() - val patchFile = File(getTempDir(), "patch.jar") - HttpRequests.request(url).gzip(false).forceHttps(forceHttps).saveToFile(patchFile, indicator) - return patchFile - } - @JvmStatic @Throws(IOException::class) fun downloadPatchChain(chain: List, forceHttps: Boolean, indicator: ProgressIndicator): List { 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 fba4440bb7c5..d323059545d9 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 @@ -3,8 +3,13 @@ package com.intellij.openapi.updateSettings.impl import com.intellij.openapi.updateSettings.UpdateStrategyCustomization import com.intellij.openapi.util.BuildNumber +import com.intellij.util.containers.MultiMap +import com.intellij.util.graph.InboundSemiGraph +import com.intellij.util.graph.impl.ShortestPathFinder import java.util.* +private val NUMBER = Regex("\\d+") + class UpdateStrategy(private val currentBuild: BuildNumber, private val updates: UpdatesInfo, private val settings: UserUpdateSettings) { enum class State { LOADED, CONNECTION_ERROR, NOTHING_LOADED @@ -30,9 +35,8 @@ class UpdateStrategy(private val currentBuild: BuildNumber, private val updates: val newBuild = result?.first val updatedChannel = result?.second - val patch = newBuild?.patch(currentBuild) - val chain = if (newBuild != null && patch == null) product.patchChain(currentBuild, newBuild.number) else null - return CheckForUpdateResult(newBuild, updatedChannel, patch, chain) + val patches = if (newBuild != null) patches(newBuild, product, currentBuild) else null + return CheckForUpdateResult(newBuild, updatedChannel, patches) } private fun isApplicable(candidate: BuildInfo, ignoredBuilds: Set) = @@ -45,6 +49,50 @@ class UpdateStrategy(private val currentBuild: BuildNumber, private val updates: else if (n2.baselineVersion == lineage && n1.baselineVersion != lineage) -1 else n1.compareTo(n2) + private fun patches(newBuild: BuildInfo, product: Product, from: BuildNumber): UpdateChain? { + val single = newBuild.patches.find { it.isAvailable && it.fromBuild.compareTo(from) == 0 } + if (single != null) { + return UpdateChain(listOf(from, newBuild.number), single.size) + } + + val upgrades = MultiMap() + val sizes = mutableMapOf, Int>() + + product.channels.forEach { channel -> + channel.builds.forEach { build -> + val toBuild = build.number.withoutProductCode() + build.patches.forEach { patch -> + if (patch.isAvailable) { + val fromBuild = patch.fromBuild.withoutProductCode() + upgrades.putValue(toBuild, fromBuild) + if (patch.size != null) { + val maxSize = NUMBER.findAll(patch.size).map { it.value.toIntOrNull() }.filterNotNull().max() + if (maxSize != null) sizes += (fromBuild to toBuild) to maxSize + } + } + } + } + } + + val graph = object : InboundSemiGraph { + override fun getNodes() = upgrades.keySet() + upgrades.values() + override fun getIn(n: BuildNumber) = upgrades[n].iterator() + } + val path = ShortestPathFinder(graph).findPath(from.withoutProductCode(), newBuild.number.withoutProductCode()) + if (path == null || path.size <= 2) return null + + var total = 0 + for (i in 1 until path.size) { + val size = sizes[path[i - 1] to path[i]] + if (size == null) { + total = -1 + break + } + total += size + } + return UpdateChain(path, if (total > 0) total.toString() else null) + } + // @Deprecated("use {@link #UpdateStrategy(BuildNumber, UpdatesInfo, UserUpdateSettings)}") constructor(@Suppress("UNUSED_PARAMETER") majorVersion: Int, 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 35e920cb70f6..4349579f4118 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateStrategyTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateStrategyTest.kt @@ -37,8 +37,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() { """ - assertNotNull(check("IU-145.596", ChannelStatus.RELEASE, channels).patch) - assertNull(check("IU-145.258", ChannelStatus.RELEASE, channels).patch) + assertNotNull(check("IU-145.596", ChannelStatus.RELEASE, channels).patches) + assertNull(check("IU-145.258", ChannelStatus.RELEASE, channels).patches) } @Test fun `order of builds does not matter`() { @@ -227,9 +227,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() { """) assertBuild("182.3684.40", result.newBuild) - assertThat(result.patch).isNull() - assertThat(result.patchChain?.chain).isEqualTo(listOf("182.3569.1", "182.3684.2", "182.3684.40").map(BuildNumber::fromString)) - assertThat(result.patchChain?.size).isEqualTo(10) + assertThat(result.patches?.chain).isEqualTo(listOf("182.3569.1", "182.3684.2", "182.3684.40").map(BuildNumber::fromString)) + assertThat(result.patches?.size).isEqualTo("10") } @Test fun `building patch chain across channels`() { @@ -250,9 +249,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() { """) assertBuild("182.3911.2", result.newBuild) - assertThat(result.patch).isNull() - assertThat(result.patchChain?.chain).isEqualTo(listOf("182.3684.40", "182.3684.41", "182.3911.2").map(BuildNumber::fromString)) - assertThat(result.patchChain?.size).isEqualTo(-1) + assertThat(result.patches?.chain).isEqualTo(listOf("182.3684.40", "182.3684.41", "182.3911.2").map(BuildNumber::fromString)) + assertThat(result.patches?.size).isNull() } private fun check(currentBuild: String,