streamlined plugin update logic
This commit is contained in:
@@ -42,17 +42,35 @@ import com.intellij.util.Alarm
|
|||||||
import com.intellij.util.io.HttpRequests
|
import com.intellij.util.io.HttpRequests
|
||||||
import com.intellij.util.text.VersionComparatorUtil
|
import com.intellij.util.text.VersionComparatorUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
sealed class PluginUpdateStatus {
|
sealed class PluginUpdateStatus {
|
||||||
object LatestVersionInstalled : PluginUpdateStatus()
|
object LatestVersionInstalled : PluginUpdateStatus()
|
||||||
|
|
||||||
class Update(val newVersion: String,
|
class Update(val pluginDescriptor: IdeaPluginDescriptor,
|
||||||
val descriptorToInstall: IdeaPluginDescriptor,
|
|
||||||
val hostToInstallFrom: String?) : PluginUpdateStatus()
|
val hostToInstallFrom: String?) : PluginUpdateStatus()
|
||||||
|
|
||||||
class CheckFailed(val message: String) : PluginUpdateStatus()
|
class CheckFailed(val message: String, val detail: String? = null) : PluginUpdateStatus()
|
||||||
|
|
||||||
|
fun mergeWith(other: PluginUpdateStatus): PluginUpdateStatus {
|
||||||
|
if (other is Update && (this is LatestVersionInstalled ||
|
||||||
|
(this is Update && VersionComparatorUtil.compare(other.pluginDescriptor.version,
|
||||||
|
pluginDescriptor.version) > 0))) {
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromException(message: String, e: Exception): PluginUpdateStatus {
|
||||||
|
val writer = StringWriter()
|
||||||
|
e.printStackTrace(PrintWriter(writer))
|
||||||
|
return CheckFailed(message, writer.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Disposable {
|
class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Disposable {
|
||||||
@@ -69,8 +87,9 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
|||||||
val lastUpdateTime = java.lang.Long.parseLong(propertiesComponent.getValue(PROPERTY_NAME, "0"))
|
val lastUpdateTime = java.lang.Long.parseLong(propertiesComponent.getValue(PROPERTY_NAME, "0"))
|
||||||
if (lastUpdateTime == 0L || System.currentTimeMillis() - lastUpdateTime > TimeUnit.DAYS.toMillis(1)) {
|
if (lastUpdateTime == 0L || System.currentTimeMillis() - lastUpdateTime > TimeUnit.DAYS.toMillis(1)) {
|
||||||
queueUpdateCheck { updateStatus ->
|
queueUpdateCheck { updateStatus ->
|
||||||
if (updateStatus is PluginUpdateStatus.Update) {
|
when (updateStatus) {
|
||||||
notifyPluginUpdateAvailable(updateStatus)
|
is PluginUpdateStatus.Update -> notifyPluginUpdateAvailable(updateStatus)
|
||||||
|
is PluginUpdateStatus.CheckFailed -> LOG.info("Plugin update check failed: ${updateStatus.message}, details: ${updateStatus.detail}")
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -86,61 +105,37 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
fun runUpdateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||||
try {
|
ApplicationManager.getApplication().executeOnPooledThread {
|
||||||
var (mainRepoUpdateSuccess, latestVersionInRepository) = getPluginVersionFromMainRepository()
|
updateCheck(callback)
|
||||||
var descriptorToInstall: IdeaPluginDescriptor? = initPluginDescriptor(latestVersionInRepository)
|
|
||||||
var hostToInstallFrom: String? = null
|
|
||||||
|
|
||||||
for (host in RepositoryHelper.getPluginHosts().filterNotNull()) {
|
|
||||||
val plugins = try {
|
|
||||||
RepositoryHelper.loadPlugins(host, null)
|
|
||||||
}
|
|
||||||
catch(e: Exception) {
|
|
||||||
LOG.info("Checking custom plugin repository $host failed", e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val kotlinPlugin = plugins.find { it.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID }
|
|
||||||
if (kotlinPlugin != null && VersionComparatorUtil.compare(kotlinPlugin.version, latestVersionInRepository) > 0) {
|
|
||||||
latestVersionInRepository = kotlinPlugin.version
|
|
||||||
descriptorToInstall = kotlinPlugin
|
|
||||||
hostToInstallFrom = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkQueued = false
|
|
||||||
|
|
||||||
if (mainRepoUpdateSuccess || latestVersionInRepository != null) {
|
|
||||||
recordSuccessfulUpdateCheck()
|
|
||||||
if (latestVersionInRepository != null && VersionComparatorUtil.compare(latestVersionInRepository, KotlinPluginUtil.getPluginVersion()) > 0) {
|
|
||||||
ApplicationManager.getApplication().invokeLater({
|
|
||||||
callback(PluginUpdateStatus.Update(latestVersionInRepository!!, descriptorToInstall!!, hostToInstallFrom))
|
|
||||||
}, ModalityState.any())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ApplicationManager.getApplication().invokeLater({
|
|
||||||
callback(PluginUpdateStatus.LatestVersionInstalled)
|
|
||||||
}, ModalityState.any())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ApplicationManager.getApplication().invokeLater {
|
|
||||||
if (callback(PluginUpdateStatus.CheckFailed("Plugin not found in repository"))) queueUpdateCheck(callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(e: Exception) {
|
|
||||||
LOG.info("Kotlin plugin update check failed", e)
|
|
||||||
checkQueued = false
|
|
||||||
ApplicationManager.getApplication().invokeLater {
|
|
||||||
if (callback(PluginUpdateStatus.CheckFailed(e.message ?: "Unknown error"))) queueUpdateCheck(callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initPluginDescriptor(newVersion: String?): IdeaPluginDescriptor? {
|
private fun updateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||||
if (newVersion == null) return null
|
var updateStatus: PluginUpdateStatus
|
||||||
|
try {
|
||||||
|
updateStatus = checkUpdatesInMainRepository()
|
||||||
|
|
||||||
|
for (host in RepositoryHelper.getPluginHosts().filterNotNull()) {
|
||||||
|
val customUpdateStatus = checkUpdatesInCustomRepository(host)
|
||||||
|
updateStatus = updateStatus.mergeWith(customUpdateStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e: Exception) {
|
||||||
|
updateStatus = PluginUpdateStatus.fromException("Kotlin plugin update check failed", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkQueued = false
|
||||||
|
|
||||||
|
if (updateStatus !is PluginUpdateStatus.CheckFailed) {
|
||||||
|
recordSuccessfulUpdateCheck()
|
||||||
|
}
|
||||||
|
ApplicationManager.getApplication().invokeLater({
|
||||||
|
callback(updateStatus)
|
||||||
|
}, ModalityState.any())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initPluginDescriptor(newVersion: String): IdeaPluginDescriptor {
|
||||||
val originalPlugin = PluginManager.getPlugin(KotlinPluginUtil.KOTLIN_PLUGIN_ID)!!
|
val originalPlugin = PluginManager.getPlugin(KotlinPluginUtil.KOTLIN_PLUGIN_ID)!!
|
||||||
return PluginNode(KotlinPluginUtil.KOTLIN_PLUGIN_ID).apply {
|
return PluginNode(KotlinPluginUtil.KOTLIN_PLUGIN_ID).apply {
|
||||||
version = newVersion
|
version = newVersion
|
||||||
@@ -149,31 +144,48 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RepositoryCheckResult(val success: Boolean, val newVersion: String?)
|
fun checkUpdatesInMainRepository(): PluginUpdateStatus {
|
||||||
|
|
||||||
fun getPluginVersionFromMainRepository(): RepositoryCheckResult {
|
|
||||||
val buildNumber = ApplicationInfo.getInstance().build.asString()
|
val buildNumber = ApplicationInfo.getInstance().build.asString()
|
||||||
val pluginVersion = KotlinPluginUtil.getPluginVersion()
|
val currentVersion = KotlinPluginUtil.getPluginVersion()
|
||||||
val os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8)
|
val os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8)
|
||||||
val uid = UpdateChecker.getInstallationUID(propertiesComponent)
|
val uid = UpdateChecker.getInstallationUID(propertiesComponent)
|
||||||
val url = "https://plugins.jetbrains.com/plugins/list?pluginId=6954&build=$buildNumber&pluginVersion=$pluginVersion&os=$os&uuid=$uid"
|
val url = "https://plugins.jetbrains.com/plugins/list?pluginId=6954&build=$buildNumber&pluginVersion=$currentVersion&os=$os&uuid=$uid"
|
||||||
val responseDoc = HttpRequests.request(url).connect {
|
val responseDoc = HttpRequests.request(url).connect {
|
||||||
JDOMUtil.load(it.inputStream)
|
JDOMUtil.load(it.inputStream)
|
||||||
}
|
}
|
||||||
if (responseDoc.name != "plugin-repository") {
|
if (responseDoc.name != "plugin-repository") {
|
||||||
LOG.info("Unexpected plugin repository response: ${JDOMUtil.writeElement(responseDoc, "\n")}")
|
return PluginUpdateStatus.CheckFailed("Unexpected plugin repository response", JDOMUtil.writeElement(responseDoc, "\n"))
|
||||||
return RepositoryCheckResult(false, null)
|
|
||||||
}
|
}
|
||||||
if (responseDoc.children.isEmpty()) {
|
if (responseDoc.children.isEmpty()) {
|
||||||
// No plugin version compatible with current IDEA build; don't retry updates
|
// No plugin version compatible with current IDEA build; don't retry updates
|
||||||
return RepositoryCheckResult(true, null)
|
return PluginUpdateStatus.LatestVersionInstalled
|
||||||
}
|
}
|
||||||
val version = responseDoc.getChild("category")?.getChild("idea-plugin")?.getChild("version")?.text
|
val newVersion = responseDoc.getChild("category")?.getChild("idea-plugin")?.getChild("version")?.text
|
||||||
if (version == null) {
|
if (newVersion == null) {
|
||||||
LOG.info("Couldn't find plugin version in repository response: ${JDOMUtil.writeElement(responseDoc, "\n")}")
|
return PluginUpdateStatus.CheckFailed("Couldn't find plugin version in repository response", JDOMUtil.writeElement(responseDoc, "\n"))
|
||||||
return RepositoryCheckResult(false, null)
|
|
||||||
}
|
}
|
||||||
return RepositoryCheckResult(true, version)
|
val pluginDescriptor = initPluginDescriptor(newVersion)
|
||||||
|
return updateIfNotLatest(pluginDescriptor, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkUpdatesInCustomRepository(host: String): PluginUpdateStatus {
|
||||||
|
val plugins = try {
|
||||||
|
RepositoryHelper.loadPlugins(host, null)
|
||||||
|
}
|
||||||
|
catch(e: Exception) {
|
||||||
|
return PluginUpdateStatus.fromException("Checking custom plugin repository $host failed", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinPlugin = plugins.find { it.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID } ?: return PluginUpdateStatus.LatestVersionInstalled
|
||||||
|
return updateIfNotLatest(kotlinPlugin, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateIfNotLatest(kotlinPlugin: IdeaPluginDescriptor, host: String?): PluginUpdateStatus {
|
||||||
|
if (VersionComparatorUtil.compare(kotlinPlugin.version, KotlinPluginUtil.getPluginVersion()) <= 0) {
|
||||||
|
return PluginUpdateStatus.LatestVersionInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
return PluginUpdateStatus.Update(kotlinPlugin, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recordSuccessfulUpdateCheck() {
|
private fun recordSuccessfulUpdateCheck() {
|
||||||
@@ -184,7 +196,7 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
|||||||
private fun notifyPluginUpdateAvailable(update: PluginUpdateStatus.Update) {
|
private fun notifyPluginUpdateAvailable(update: PluginUpdateStatus.Update) {
|
||||||
val notification = notificationGroup.createNotification(
|
val notification = notificationGroup.createNotification(
|
||||||
"Kotlin",
|
"Kotlin",
|
||||||
"A new version ${update.newVersion} of the Kotlin plugin is available. <b><a href=\"#\">Install</a></b>",
|
"A new version ${update.pluginDescriptor.version} of the Kotlin plugin is available. <b><a href=\"#\">Install</a></b>",
|
||||||
NotificationType.INFORMATION) { notification, event ->
|
NotificationType.INFORMATION) { notification, event ->
|
||||||
notification.expire()
|
notification.expire()
|
||||||
installPluginUpdate(update) {
|
installPluginUpdate(update) {
|
||||||
@@ -197,7 +209,7 @@ class KotlinPluginUpdater(val propertiesComponent: PropertiesComponent) : Dispos
|
|||||||
|
|
||||||
fun installPluginUpdate(update: PluginUpdateStatus.Update,
|
fun installPluginUpdate(update: PluginUpdateStatus.Update,
|
||||||
cancelCallback: () -> Unit = {}) {
|
cancelCallback: () -> Unit = {}) {
|
||||||
val descriptor = update.descriptorToInstall
|
val descriptor = update.pluginDescriptor
|
||||||
val pluginDownloader = PluginDownloader.createDownloader(descriptor, update.hostToInstallFrom, null)
|
val pluginDownloader = PluginDownloader.createDownloader(descriptor, update.hostToInstallFrom, null)
|
||||||
ProgressManager.getInstance().run(object : Task.Backgroundable(null, "Downloading plugins", true) {
|
ProgressManager.getInstance().run(object : Task.Backgroundable(null, "Downloading plugins", true) {
|
||||||
override fun run(indicator: ProgressIndicator) {
|
override fun run(indicator: ProgressIndicator) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class ConfigurePluginUpdatesDialog(project: Project) : DialogWrapper(project, fa
|
|||||||
saveSettings()
|
saveSettings()
|
||||||
form.updateCheckProgressIcon.resume()
|
form.updateCheckProgressIcon.resume()
|
||||||
resetUpdateStatus()
|
resetUpdateStatus()
|
||||||
KotlinPluginUpdater.getInstance().queueUpdateCheck() { pluginUpdateStatus ->
|
KotlinPluginUpdater.getInstance().runUpdateCheck{ pluginUpdateStatus ->
|
||||||
form.updateCheckProgressIcon.suspend()
|
form.updateCheckProgressIcon.suspend()
|
||||||
when (pluginUpdateStatus) {
|
when (pluginUpdateStatus) {
|
||||||
PluginUpdateStatus.LatestVersionInstalled ->
|
PluginUpdateStatus.LatestVersionInstalled ->
|
||||||
@@ -55,7 +55,7 @@ class ConfigurePluginUpdatesDialog(project: Project) : DialogWrapper(project, fa
|
|||||||
is PluginUpdateStatus.Update -> {
|
is PluginUpdateStatus.Update -> {
|
||||||
update = pluginUpdateStatus
|
update = pluginUpdateStatus
|
||||||
form.installButton.isVisible = true
|
form.installButton.isVisible = true
|
||||||
form.updateStatusLabel.text = "A new version ${pluginUpdateStatus.newVersion} is available"
|
form.updateStatusLabel.text = "A new version ${pluginUpdateStatus.pluginDescriptor.version} is available"
|
||||||
}
|
}
|
||||||
|
|
||||||
is PluginUpdateStatus.CheckFailed ->
|
is PluginUpdateStatus.CheckFailed ->
|
||||||
|
|||||||
Reference in New Issue
Block a user