⁠
kyle wood: Standardize HTTP requests through Jetbrains HttpRequests
The McdevHttp util allows us to serve mocked results for tests in the
future.
The McdevHttp util allows us to serve mocked results for tests in the
future.
- /*
- * Minecraft Development for IntelliJ
- *
- * https://mcdev.io/
- *
- * Copyright (C) 2026 minecraft-dev
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published
- * by the Free Software Foundation, version 3.0 only.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
- package com.demonwav.mcdev.update
-
- import com.demonwav.mcdev.util.McdevReflection
- import com.demonwav.mcdev.util.forEachNotNull
- import com.demonwav.mcdev.util.invokeLater
- import com.intellij.ide.plugins.IdeaPluginDescriptor
- import com.intellij.ide.plugins.PluginManagerCore
- import com.intellij.ide.plugins.PluginManagerMain
- import com.intellij.ide.plugins.PluginNode
- import com.intellij.ide.plugins.RepositoryHelper
- import com.intellij.openapi.application.ApplicationInfo
- import com.intellij.openapi.application.ApplicationManager
- import com.intellij.openapi.application.ModalityState
- import com.intellij.openapi.progress.ProgressIndicator
- import com.intellij.openapi.progress.ProgressManager
- import com.intellij.openapi.progress.Task
- import com.intellij.openapi.updateSettings.impl.PluginDownloader
- import com.intellij.openapi.util.JDOMUtil
- import com.intellij.openapi.util.SystemInfo
- import com.intellij.util.io.HttpRequests
- import java.io.IOException
- import java.net.URLEncoder
- import org.jdom.Element
- import org.jdom.JDOMException
-
- object PluginUpdater {
- fun runUpdateCheck(callback: (PluginUpdateStatus) -> Boolean) {
- ApplicationManager.getApplication().executeOnPooledThread { updateCheck(callback) }
- }
-
- // Custom update check to enable downgrading the plugin switching from nightly -> stable
- private fun updateCheck(callback: (PluginUpdateStatus) -> Boolean) {
- var updateStatus: PluginUpdateStatus
- try {
- updateStatus = checkUpdatesInMainRepo()
-
- RepositoryHelper.getPluginHosts()
- .forEachNotNull { updateStatus = updateStatus.mergeWith(checkUpdatesInCustomRepo(it)) }
-
- val finalUpdate = updateStatus
- ApplicationManager.getApplication().invokeLater({ callback(finalUpdate) }, ModalityState.any())
- } catch (_: Exception) {
- PluginUpdateStatus.CheckFailed("Minecraft Development plugin update check failed")
- }
- }
-
- private fun checkUpdatesInMainRepo(): PluginUpdateStatus {
- val buildNumber = ApplicationInfo.getInstance().build.asString()
- val currentVersion = PluginUtil.pluginVersion
- val os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, Charsets.UTF_8)
- val url =
- "https://plugins.jetbrains.com/plugins/list?pluginId=8327&build=" +
- "$buildNumber&pluginVersion=$currentVersion&os=$os"
-
- val responseDoc = HttpRequests.request(url).connect<Element> { request ->
- try {
- return@connect JDOMUtil.load(request.inputStream)
- } catch (e: JDOMException) {
- e.printStackTrace()
- }
-
- null
- } ?: return PluginUpdateStatus.CheckFailed("Unexpected plugin repository response")
-
- if (responseDoc.name != "plugin-repository") {
- return PluginUpdateStatus.CheckFailed("Unexpected plugin repository response")
- }
- if (responseDoc.children.isEmpty()) {
- return PluginUpdateStatus.LatestVersionInstalled
- }
-
- val newVersion = responseDoc.getChild("category")?.getChild("idea-plugin")?.getChild("version")?.text
- ?: return PluginUpdateStatus.CheckFailed("Couldn't find plugin version in repository response")
-
- val plugin = PluginManagerCore.getPlugin(PluginUtil.PLUGIN_ID)!!
- val pluginNode = PluginNode(PluginUtil.PLUGIN_ID)
- pluginNode.version = newVersion
- pluginNode.name = plugin.name
- pluginNode.description = plugin.description
-
- if (pluginNode.version == PluginUtil.pluginVersion) {
- return PluginUpdateStatus.LatestVersionInstalled
- }
-
- return PluginUpdateStatus.Update(pluginNode, null)
- }
-
- private fun checkUpdatesInCustomRepo(host: String): PluginUpdateStatus {
- val plugins = try {
- RepositoryHelper.loadPluginModels(host, null, null)
- .map { it.getDescriptor() }
- } catch (_: IOException) {
- return PluginUpdateStatus.CheckFailed("Checking custom plugin repository $host failed")
- }
-
- val minecraftPlugin = plugins.firstOrNull { plugin -> plugin.pluginId == PluginUtil.PLUGIN_ID }
- ?: return PluginUpdateStatus.LatestVersionInstalled
-
- return updateIfNotLatest(minecraftPlugin, host)
- }
-
- private fun updateIfNotLatest(plugin: IdeaPluginDescriptor, host: String): PluginUpdateStatus {
- if (plugin.version == PluginUtil.pluginVersion) {
- return PluginUpdateStatus.LatestVersionInstalled
- }
- return PluginUpdateStatus.Update(plugin, host)
- }
-
- fun installPluginUpdate(update: PluginUpdateStatus.Update) {
- val plugin = update.pluginDescriptor
- val downloader = PluginDownloader.createDownloader(plugin, update.hostToInstallFrom, null)
- ProgressManager.getInstance().run(
- object : Task.Backgroundable(null, "Downloading plugin", true) {
- override fun run(indicator: ProgressIndicator) {
- try {
- val status = downloader.prepareToInstall(indicator)
- // If the download failed, quit
- // But otherwise force the install
- if (!status && McdevReflection.FIELD_PLUGIN_DOWNLOADER_MY_FILE.get(downloader) == null) {
- return
- }
-
- downloader.install()
-
- invokeLater {
- PluginManagerMain.notifyPluginsUpdated(null)
- }
- } catch (e: IOException) {
- e.printStackTrace()
- }
- }
- },
- )
- }
- }
- /*
- * Minecraft Development for IntelliJ
- *
- * https://mcdev.io/
- *
- * Copyright (C) 2026 minecraft-dev
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published
- * by the Free Software Foundation, version 3.0 only.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
- package com.demonwav.mcdev.update
-
- import com.demonwav.mcdev.util.McdevHttp
- import com.demonwav.mcdev.util.McdevReflection
- import com.demonwav.mcdev.util.forEachNotNull
- import com.demonwav.mcdev.util.invokeLater
- import com.intellij.ide.plugins.IdeaPluginDescriptor
- import com.intellij.ide.plugins.PluginManagerCore
- import com.intellij.ide.plugins.PluginManagerMain
- import com.intellij.ide.plugins.PluginNode
- import com.intellij.ide.plugins.RepositoryHelper
- import com.intellij.openapi.application.ApplicationInfo
- import com.intellij.openapi.application.ApplicationManager
- import com.intellij.openapi.application.ModalityState
- import com.intellij.openapi.progress.ProgressIndicator
- import com.intellij.openapi.progress.ProgressManager
- import com.intellij.openapi.progress.Task
- import com.intellij.openapi.updateSettings.impl.PluginDownloader
- import com.intellij.openapi.util.JDOMUtil
- import com.intellij.openapi.util.SystemInfo
- import java.io.IOException
- import java.net.URLEncoder
- import org.jdom.Element
- import org.jdom.JDOMException
-
- object PluginUpdater {
- fun runUpdateCheck(callback: (PluginUpdateStatus) -> Boolean) {
- ApplicationManager.getApplication().executeOnPooledThread { updateCheck(callback) }
- }
-
- // Custom update check to enable downgrading the plugin switching from nightly -> stable
- private fun updateCheck(callback: (PluginUpdateStatus) -> Boolean) {
- var updateStatus: PluginUpdateStatus
- try {
- updateStatus = checkUpdatesInMainRepo()
-
- RepositoryHelper.getPluginHosts()
- .forEachNotNull { updateStatus = updateStatus.mergeWith(checkUpdatesInCustomRepo(it)) }
-
- val finalUpdate = updateStatus
- ApplicationManager.getApplication().invokeLater({ callback(finalUpdate) }, ModalityState.any())
- } catch (_: Exception) {
- PluginUpdateStatus.CheckFailed("Minecraft Development plugin update check failed")
- }
- }
-
- private fun checkUpdatesInMainRepo(): PluginUpdateStatus {
- val buildNumber = ApplicationInfo.getInstance().build.asString()
- val currentVersion = PluginUtil.pluginVersion
- val os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, Charsets.UTF_8)
- val url =
- "https://plugins.jetbrains.com/plugins/list?pluginId=8327&build=" +
- "$buildNumber&pluginVersion=$currentVersion&os=$os"
-
- val responseDoc = McdevHttp.request(url).connect<Element> { request ->
- try {
- return@connect JDOMUtil.load(request.inputStream)
- } catch (e: JDOMException) {
- e.printStackTrace()
- }
-
- null
- } ?: return PluginUpdateStatus.CheckFailed("Unexpected plugin repository response")
-
- if (responseDoc.name != "plugin-repository") {
- return PluginUpdateStatus.CheckFailed("Unexpected plugin repository response")
- }
- if (responseDoc.children.isEmpty()) {
- return PluginUpdateStatus.LatestVersionInstalled
- }
-
- val newVersion = responseDoc.getChild("category")?.getChild("idea-plugin")?.getChild("version")?.text
- ?: return PluginUpdateStatus.CheckFailed("Couldn't find plugin version in repository response")
-
- val plugin = PluginManagerCore.getPlugin(PluginUtil.PLUGIN_ID)!!
- val pluginNode = PluginNode(PluginUtil.PLUGIN_ID)
- pluginNode.version = newVersion
- pluginNode.name = plugin.name
- pluginNode.description = plugin.description
-
- if (pluginNode.version == PluginUtil.pluginVersion) {
- return PluginUpdateStatus.LatestVersionInstalled
- }
-
- return PluginUpdateStatus.Update(pluginNode, null)
- }
-
- private fun checkUpdatesInCustomRepo(host: String): PluginUpdateStatus {
- val plugins = try {
- RepositoryHelper.loadPluginModels(host, null, null)
- .map { it.getDescriptor() }
- } catch (_: IOException) {
- return PluginUpdateStatus.CheckFailed("Checking custom plugin repository $host failed")
- }
-
- val minecraftPlugin = plugins.firstOrNull { plugin -> plugin.pluginId == PluginUtil.PLUGIN_ID }
- ?: return PluginUpdateStatus.LatestVersionInstalled
-
- return updateIfNotLatest(minecraftPlugin, host)
- }
-
- private fun updateIfNotLatest(plugin: IdeaPluginDescriptor, host: String): PluginUpdateStatus {
- if (plugin.version == PluginUtil.pluginVersion) {
- return PluginUpdateStatus.LatestVersionInstalled
- }
- return PluginUpdateStatus.Update(plugin, host)
- }
-
- fun installPluginUpdate(update: PluginUpdateStatus.Update) {
- val plugin = update.pluginDescriptor
- val downloader = PluginDownloader.createDownloader(plugin, update.hostToInstallFrom, null)
- ProgressManager.getInstance().run(
- object : Task.Backgroundable(null, "Downloading plugin", true) {
- override fun run(indicator: ProgressIndicator) {
- try {
- val status = downloader.prepareToInstall(indicator)
- // If the download failed, quit
- // But otherwise force the install
- if (!status && McdevReflection.FIELD_PLUGIN_DOWNLOADER_MY_FILE.get(downloader) == null) {
- return
- }
-
- downloader.install()
-
- invokeLater {
- PluginManagerMain.notifyPluginsUpdated(null)
- }
- } catch (e: IOException) {
- e.printStackTrace()
- }
- }
- },
- )
- }
- }