Skip to content

Commit c4351be

Browse files
authored
fix plugin manager not deleting old plugin version (#991)
* read plugin version & name from manifest instead of file name in plugin manager * close JarFile correctly
1 parent 6560834 commit c4351be

File tree

1 file changed

+55
-45
lines changed

1 file changed

+55
-45
lines changed

LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ import java.io.InputStream
1010
import java.net.URL
1111
import java.net.URLClassLoader
1212
import java.nio.channels.Channels
13-
import java.nio.file.Files
1413
import java.util.*
1514
import java.util.jar.JarFile
16-
import java.util.regex.Pattern
17-
1815

1916
@SpringBootApplication
2017
class PluginManager(val config: PluginsConfig) {
2118

19+
companion object {
20+
private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java)
21+
}
22+
2223
final val pluginManifests: MutableList<PluginManifest> = mutableListOf()
2324
var classLoader: ClassLoader = PluginManager::class.java.classLoader
2425

@@ -35,14 +36,13 @@ class PluginManager(val config: PluginsConfig) {
3536
val directory = File(config.pluginsDir)
3637
directory.mkdir()
3738

38-
data class PluginJar(val name: String, val version: String, val file: File)
39+
data class PluginJar(val manifest: PluginManifest, val file: File)
3940

40-
val pattern = Pattern.compile("(.+)-(.+)\\.jar$")
41-
val pluginJars = directory.listFiles()!!.mapNotNull { f ->
42-
val matcher = pattern.matcher(f.name)
43-
if (!matcher.find()) return@mapNotNull null
44-
PluginJar(matcher.group(1), matcher.group(2), f)
45-
}
41+
val pluginJars = directory.listFiles()!!.filter { it.extension == "jar" }.map {
42+
JarFile(it).use {jar ->
43+
loadPluginManifests(jar).map { manifest -> PluginJar(manifest, it) }
44+
}
45+
}.flatten()
4646

4747
data class Declaration(val group: String, val name: String, val version: String, val repository: String)
4848

@@ -55,21 +55,22 @@ class PluginManager(val config: PluginsConfig) {
5555
?: if (declaration.snapshot) config.defaultPluginSnapshotRepository else config.defaultPluginRepository
5656
repository = if (repository.endsWith("/")) repository else "$repository/"
5757
Declaration(fragments[0], fragments[1], fragments[2], repository)
58-
}
58+
}.distinctBy { "${it.group}:${it.name}" }
5959

6060
declarations.forEach declarationLoop@{ declaration ->
61-
pluginJars.forEach { jar ->
62-
if (declaration.name == jar.name) {
63-
if (declaration.version == jar.version) {
64-
// We already have this jar so don't redownload it
65-
return@declarationLoop
66-
}
67-
68-
// Delete jar of different versions
69-
if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}")
70-
log.info("Deleted ${jar.file.path}")
61+
var hasVersion = false
62+
pluginJars.forEach pluginLoop@{ jar ->
63+
if (declaration.version == jar.manifest.version && !hasVersion) {
64+
hasVersion = true
65+
// We already have this jar so don't redownload it
66+
return@pluginLoop
7167
}
68+
69+
// Delete jar of different versions
70+
if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}")
71+
log.info("Deleted ${jar.file.path}")
7272
}
73+
if (hasVersion) return@declarationLoop
7374

7475
val url = declaration.run { "$repository${group.replace(".", "/")}/$name/$version/$name-$version.jar" }
7576
val file = File(directory, declaration.run { "$name-$version.jar" })
@@ -87,16 +88,19 @@ class PluginManager(val config: PluginsConfig) {
8788
private fun readClasspathManifests(): List<PluginManifest> {
8889
return PathMatchingResourcePatternResolver()
8990
.getResources("classpath*:lavalink-plugins/*.properties")
90-
.map { r -> parsePluginManifest(r.inputStream) }
91+
.map map@{ r ->
92+
val manifest = parsePluginManifest(r.inputStream)
93+
log.info("Found plugin '${manifest.name}' version ${manifest.version}")
94+
return@map manifest
95+
}
9196
}
9297

9398
private fun loadJars(): List<PluginManifest> {
9499
val directory = File(config.pluginsDir)
95100
if (!directory.isDirectory) return emptyList()
96101
val jarsToLoad = mutableListOf<File>()
97102

98-
Files.list(File(config.pluginsDir).toPath()).forEach { path ->
99-
val file = path.toFile()
103+
directory.listFiles()?.forEach { file ->
100104
if (!file.isFile) return@forEach
101105
if (file.extension != "jar") return@forEach
102106
jarsToLoad.add(file)
@@ -111,7 +115,6 @@ class PluginManager(val config: PluginsConfig) {
111115
classLoader = cl
112116

113117
val manifests = mutableListOf<PluginManifest>()
114-
115118
jarsToLoad.forEach { file ->
116119
try {
117120
manifests.addAll(loadJar(file, cl))
@@ -124,31 +127,43 @@ class PluginManager(val config: PluginsConfig) {
124127
return manifests
125128
}
126129

127-
private fun loadJar(file: File, cl: URLClassLoader): MutableList<PluginManifest> {
130+
private fun loadJar(file: File, cl: URLClassLoader): List<PluginManifest> {
128131
var classCount = 0
129132
val jar = JarFile(file)
133+
var manifests: List<PluginManifest>
134+
135+
jar.use {
136+
manifests = loadPluginManifests(jar)
137+
if (manifests.isEmpty()) {
138+
throw RuntimeException("No plugin manifest found in ${file.path}")
139+
}
140+
val allowedPaths = manifests.map { it.path.replace(".", "/") }
141+
142+
jar.entries().asIterator().forEach { entry ->
143+
if (entry.isDirectory) return@forEach
144+
if (!entry.name.endsWith(".class")) return@forEach
145+
if (!allowedPaths.any { entry.name.startsWith(it) }) return@forEach
146+
cl.loadClass(entry.name.dropLast(6).replace("/", "."))
147+
classCount++
148+
}
149+
}
150+
151+
log.info("Loaded ${file.name} ($classCount classes)")
152+
return manifests
153+
}
154+
155+
private fun loadPluginManifests(jar: JarFile): List<PluginManifest> {
130156
val manifests = mutableListOf<PluginManifest>()
131157

132158
jar.entries().asIterator().forEach { entry ->
133159
if (entry.isDirectory) return@forEach
134160
if (!entry.name.startsWith("lavalink-plugins/")) return@forEach
135161
if (!entry.name.endsWith(".properties")) return@forEach
136-
manifests.add(parsePluginManifest(jar.getInputStream(entry)))
137-
}
138162

139-
if (manifests.isEmpty()) {
140-
throw RuntimeException("No plugin manifest found in ${file.path}")
163+
val manifest = parsePluginManifest(jar.getInputStream(entry))
164+
log.info("Found plugin '${manifest.name}' version ${manifest.version}")
165+
manifests.add(manifest)
141166
}
142-
val allowedPaths = manifests.map { it.path.replace(".", "/") }
143-
144-
jar.entries().asIterator().forEach { entry ->
145-
if (entry.isDirectory) return@forEach
146-
if (!entry.name.endsWith(".class")) return@forEach
147-
if (!allowedPaths.any { entry.name.startsWith(it) }) return@forEach
148-
cl.loadClass(entry.name.dropLast(6).replace("/", "."))
149-
classCount++
150-
}
151-
log.info("Loaded ${file.name} ($classCount classes)")
152167
return manifests
153168
}
154169

@@ -160,11 +175,6 @@ class PluginManager(val config: PluginsConfig) {
160175
val name = props.getProperty("name") ?: throw RuntimeException("Manifest is missing 'name'")
161176
val path = props.getProperty("path") ?: throw RuntimeException("Manifest is missing 'path'")
162177
val version = props.getProperty("version") ?: throw RuntimeException("Manifest is missing 'version'")
163-
log.info("Found plugin '$name' version $version")
164178
return PluginManifest(name, path, version)
165179
}
166-
167-
companion object {
168-
private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java)
169-
}
170180
}

0 commit comments

Comments
 (0)