Skip to content

Commit

Permalink
Add plugin auto updating mechanism with maven-metadata & dialog warni…
Browse files Browse the repository at this point in the history
…ng when another instance is running
  • Loading branch information
serivesmejia committed Dec 8, 2024
1 parent 24c1ac9 commit 36a9f32
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 14 deletions.
10 changes: 10 additions & 0 deletions EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

package com.github.serivesmejia.eocvsim

import com.formdev.flatlaf.intellijthemes.FlatArcDarkIJTheme
import com.formdev.flatlaf.intellijthemes.FlatArcDarkIJTheme.setup
import com.github.serivesmejia.eocvsim.EOCVSim.Parameters
import com.github.serivesmejia.eocvsim.config.Config
import com.github.serivesmejia.eocvsim.config.ConfigManager
Expand Down Expand Up @@ -59,6 +61,7 @@ import org.openftc.easyopencv.TimestampedPipelineHandler
import java.awt.Dimension
import java.io.File
import java.lang.Thread.sleep
import javax.swing.JOptionPane
import javax.swing.SwingUtilities
import javax.swing.filechooser.FileFilter
import javax.swing.filechooser.FileNameExtensionFilter
Expand Down Expand Up @@ -262,6 +265,13 @@ class EOCVSim(val params: Parameters = Parameters()) {
"Couldn't finally claim lock file in \"${EOCVSimFolder.absolutePath}\"! " + "Is the folder opened by another EOCV-Sim instance?"
)

JOptionPane.showMessageDialog(
null,
"Another instance of EOCV-Sim is already running in this machine, please close it and try again.",
"Error",
JOptionPane.ERROR_MESSAGE
)

logger.error("Unable to continue with the execution, the sim will exit now.")
exitProcess(-1)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class PluginManager(val eocvSim: EOCVSim) {
}

val repositoryManager by lazy {
PluginRepositoryManager(appender, haltLock, haltCondition)
PluginRepositoryManager(appender, eocvSim, haltLock, haltCondition)
}

private val _pluginFiles = mutableListOf<File>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlin.math.log

object PaperVisionChecker {

val LATEST_PAPERVISION = ParsedVersion(1, 0, 3)
val LATEST_PAPERVISION = ParsedVersion(1, 0, 4)

const val RESET_QUESTION = "o you wish to fix this by resetting back to the default settings? Please note this will wipe your plugins folder!"

Expand Down Expand Up @@ -52,12 +52,6 @@ object PaperVisionChecker {
logger.info("hash_check = ${eocvSim.config.flags["${hash}_check"]}")
logger.info("null_check = ${eocvSim.config.flags["null_check"]}")

if(eocvSim.config.flags["${hash}_check"] == true) {
return
} else {
eocvSim.config.flags["${hash}_check"] = true
}

val parsedVersion = try {
ParsedVersion(paperVisionPlugin!!.pluginVersion).apply {
logger.info("Parsed PaperVision version: $this")
Expand All @@ -67,6 +61,12 @@ object PaperVisionChecker {
null
}

if(eocvSim.config.flags["${hash}_check"] == true && (parsedVersion != null && parsedVersion >= LATEST_PAPERVISION)) {
return
} else {
eocvSim.config.flags["${hash}_check"] = true
}

if(paperVisionPlugin == null) {
SwingUtilities.invokeLater {
val result = JOptionPane.showOptionDialog(
Expand Down Expand Up @@ -102,7 +102,7 @@ object PaperVisionChecker {

eocvSim.config.flags["null_check"] = false
logger.warn("PaperVision plugin loaded from file")
} else if(parsedVersion == null || parsedVersion < LATEST_PAPERVISION) {
}/*else if(parsedVersion == null || parsedVersion < LATEST_PAPERVISION) {
SwingUtilities.invokeLater {
val result = JOptionPane.showOptionDialog(
eocvSim.visualizer.frame,
Expand All @@ -120,7 +120,6 @@ object PaperVisionChecker {
eocvSim.config.flags["null_check"] = false
logger.warn("PaperVision plugin outdated")
}
}*/
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,29 @@

package io.github.deltacv.eocvsim.plugin.repository

import com.github.serivesmejia.eocvsim.EOCVSim
import com.github.serivesmejia.eocvsim.gui.dialog.AppendDelegate
import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput
import com.github.serivesmejia.eocvsim.util.SysUtil
import com.github.serivesmejia.eocvsim.util.extension.hashString
import com.github.serivesmejia.eocvsim.util.extension.plus
import com.github.serivesmejia.eocvsim.util.loggerForThis
import com.moandjiezana.toml.Toml
import io.github.deltacv.common.util.ParsedVersion
import io.github.deltacv.eocvsim.plugin.loader.PluginManager
import org.jboss.shrinkwrap.resolver.api.maven.ConfigurableMavenResolverSystem
import org.jboss.shrinkwrap.resolver.api.maven.Maven
import java.io.File
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.Condition
import java.util.concurrent.TimeUnit
import javax.swing.JOptionPane
import kotlin.collections.iterator
import kotlin.concurrent.withLock

class PluginRepositoryManager(
val appender: AppendDelegate,
val eocvSim: EOCVSim,
val haltLock: ReentrantLock,
val haltCondition: Condition
) {
Expand All @@ -66,6 +70,8 @@ class PluginRepositoryManager(

private lateinit var resolver: ConfigurableMavenResolverSystem

private val repositories = mutableListOf<String>()

private val _resolvedFiles = mutableListOf<File>()
val resolvedFiles get() = _resolvedFiles.toList()

Expand Down Expand Up @@ -106,6 +112,7 @@ class PluginRepositoryManager(
}

resolver.withRemoteRepo(repo.key, repoUrl, "default")
repositories += repoUrl

logger.info("Added repository ${repo.key} with URL $repoUrl")
}
Expand Down Expand Up @@ -139,6 +146,24 @@ class PluginRepositoryManager(
handleResolutionError(pluginDep, ex)
shouldHalt = true
}

val latest = checkForUpdates(pluginDep, repositories.toTypedArray())

if(latest != null) {
appender.appendln(
PluginOutput.SPECIAL_SILENT +
"Plugin \"${plugin.key}\" is outdated. Latest version is ${latest.version}."
)

eocvSim.onMainUpdate.doOnce {
promptUpdateAndRestart(plugin.key, pluginDep, latest)
}
} else {
appender.appendln(
PluginOutput.SPECIAL_SILENT +
"Plugin \"${plugin.key}\" is up to date."
)
}
}

writeCacheFile(newCache, newTransitiveCache)
Expand All @@ -148,6 +173,66 @@ class PluginRepositoryManager(
return files
}

private fun promptUpdateAndRestart(pluginName: String, pluginDep: String, latest: ParsedVersion) {
if (promptUpdate(pluginName, latest)) {
appender.appendln(PluginOutput.SPECIAL_SILENT +"Updating plugin \"$pluginName\" to version ${latest.version}...")

val artifact = parseArtifact(pluginDep)

// Read the current TOML file content
val tomlFile = REPOSITORY_FILE
val tomlString = tomlFile.readText()
val tomlLines = tomlString.lines().toMutableList()

// Locate the `[plugins]` section
val indexOfPlugins = tomlLines.indexOfFirst { it.trim() == "[plugins]" }
if (indexOfPlugins == -1) {
appender.appendln("Failed to find [plugins] section in the TOML file.")
return
}

// Find the line for `pluginDep` under `[plugins]`
val pluginLineIndex = tomlLines
.subList(indexOfPlugins + 1, tomlLines.size) // Only consider lines after `[plugins]`
.indexOfFirst { it.trim().matches(Regex("^$pluginName\\s*=\\s*.*")) } // Find the line
.takeIf { it != -1 } // Check if the plugin was found
?.let { it + indexOfPlugins + 1 } // Adjust the index relative to the full list

if (pluginLineIndex == -1 || pluginLineIndex == null) {
appender.appendln("Failed to find plugin \"$pluginName\" in the TOML file.")
return
}

// Update the version
val oldLine = tomlLines[pluginLineIndex]
tomlLines[pluginLineIndex] = "$pluginName = \"${artifact.groupId}:${artifact.artifactId}:${latest.version}\""

// Write updated content back to the TOML file
tomlFile.writeText(tomlLines.joinToString("\n"))

appender.appendln(PluginOutput.SPECIAL_SILENT +"Successfully updated \"$pluginName\" to version ${latest.version}. Restarting...")
eocvSim.restart()
}
}

private fun promptUpdate(
pluginName: String,
latest: ParsedVersion
): Boolean {
val result = JOptionPane.showOptionDialog(
null,
"Plugin \"$pluginName\" is outdated. Latest version is ${latest.version}. Do you want to update? This will restart EOCV-Sim.",
"Update Plugin",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
arrayOf("Update", "Ignore"),
null
)

return result == JOptionPane.YES_OPTION
}

// Function to handle resolution from cache
private fun resolveFromCache(
pluginDep: String,
Expand Down Expand Up @@ -283,7 +368,6 @@ class PluginRepositoryManager(
val cacheBuilder = StringBuilder()

cacheBuilder.appendLine("# Do not edit this file, it is generated by the application.")

cacheBuilder.appendLine("[plugins]") // add plugins table

for(cached in cache) {
Expand Down Expand Up @@ -314,5 +398,4 @@ class PluginRepositoryManager(
deps.joinToString(File.pathSeparator).replace("\\", "/").trimEnd(File.pathSeparatorChar).hashString
}


class InvalidFileException(msg: String) : RuntimeException(msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2024 Sebastian Erives
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

package io.github.deltacv.eocvsim.plugin.repository

import com.github.serivesmejia.eocvsim.util.loggerOf
import io.github.deltacv.common.util.ParsedVersion
import java.net.URL
import javax.xml.parsers.DocumentBuilderFactory

private val logger by loggerOf("PluginRepositoryUpdateChecker")

data class ParsedArtifact(
val groupId: String,
val artifactId: String,
val version: String
)

fun parseArtifact(coordinates: String): ParsedArtifact {
val components = coordinates.split(":")
if (components.size != 3) {
logger.warn("Invalid artifact ID format. Expected 'group:artifact:version'.")
return ParsedArtifact("", "", "")
}

val (group, artifact, version) = components

return ParsedArtifact(group, artifact, version)
}

fun findArtifactRootUrl(repositoryUrl: String, artifactId: String): String? {
val (group, artifact, _) = parseArtifact(artifactId)
val groupPath = group.replace('.', '/')
val pluginUrl = "$repositoryUrl$groupPath/$artifact/"

return pluginUrl
}

fun findArtifactLatest(repositoryUrl: String, artifactId: String): ParsedVersion? {
val pluginUrl = findArtifactRootUrl(repositoryUrl, artifactId)
if (pluginUrl == null) {
println("Failed to find plugin root URL for $artifactId")
return null
}

val mavenMetadataUrl = "${pluginUrl}maven-metadata.xml"

return try {
// Fetch and parse the maven-metadata.xml
val metadataXml = URL(mavenMetadataUrl).readText()

val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val doc = builder.parse(metadataXml.byteInputStream())

val latest = doc.getElementsByTagName("latest").item(0)?.textContent

return ParsedVersion(latest!!)
} catch (e: Exception) {
logger.warn("Failed to get plugin versions for $artifactId in $repositoryUrl", e)
null
}
}

fun checkForUpdates(
coordinates: String,
repoUrls: Array<String>
): ParsedVersion? {
val (group, artifact, version) = coordinates.split(":")
val artifactId = "$group:$artifact:$version"

val currentVersion = ParsedVersion(version)

for (repoUrl in repoUrls) {
val latestVersion = findArtifactLatest(repoUrl, artifactId) ?: continue

logger.info("Latest version of $group:$artifact is $latestVersion")

if (latestVersion > currentVersion) {
return latestVersion
}
}

return null
}
2 changes: 1 addition & 1 deletion EOCV-Sim/src/main/resources/repository.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ jitpack = "https://jitpack.io/"
# Declare the ID and the Maven coordinates of the plugin (group:artifact:version)
# which will be used to download the plugin from one of repositories specified above.
# Any dependency that is not a valid EOCV-Sim plugin will not be considered after download.
PaperVision = "org.deltacv.PaperVision:EOCVSimPlugin:1.0.3"
PaperVision = "org.deltacv.PaperVision:EOCVSimPlugin:1.0.4"

0 comments on commit 36a9f32

Please sign in to comment.