[platform] simplifies updater classes API (IDEA-CR-35410)

This commit is contained in:
Roman Shevchenko
2018-07-31 17:29:57 -07:00
parent 30037b638a
commit 0e94e6253c
8 changed files with 106 additions and 162 deletions
@@ -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;
}
}
@@ -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<BuildNumber>, 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
}
}
@@ -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()
@@ -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<String> = node.getChildren("code").map { it.value.trim() }.toSet()
val channels: List<UpdateChannel> = node.getChildren("channel").map(::UpdateChannel)
fun patchChain(from: BuildNumber, to: BuildNumber): ChainInfo? {
val upgrades = MultiMap<BuildNumber, BuildNumber>()
val sizes = mutableMapOf<Pair<BuildNumber, BuildNumber>, 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<BuildNumber> {
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<BuildNumber>, val size: Int)
private fun Element.getMandatoryAttributeValue(attribute: String) =
getAttributeValue(attribute) ?: throw JDOMException("${name}@${attribute} missing")
@@ -58,16 +58,14 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
private final boolean myForceHttps;
private final Collection<PluginDownloader> myUpdatedPlugins;
private final BuildInfo myNewBuild;
private final PatchInfo myPatch;
private final ChainInfo myChain;
private final UpdateChain myPatches;
private final boolean myWriteProtected;
private final Pair<String, Color> 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<PluginDownloader> 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<Action> 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<ButtonInfo> 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<File> files = UpdateInstaller.downloadPatchChain(myChain.getChain(), myForceHttps, indicator);
if (myPatches != null) {
List<File> 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);
@@ -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<BuildNumber>, forceHttps: Boolean, indicator: ProgressIndicator): List<File> {
@@ -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<String>) =
@@ -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<BuildNumber, BuildNumber>()
val sizes = mutableMapOf<Pair<BuildNumber, BuildNumber>, 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<BuildNumber> {
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)
}
//<editor-fold desc="Deprecated stuff.">
@Deprecated("use {@link #UpdateStrategy(BuildNumber, UpdatesInfo, UserUpdateSettings)}")
constructor(@Suppress("UNUSED_PARAMETER") majorVersion: Int,
@@ -37,8 +37,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() {
<patch from="145.258" exclusions="win,mac,unix"/>
</build>
</channel>"""
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() {
</build>
</channel>""")
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() {
</build>
</channel>""")
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,