Skip to content

Commit 3a20917

Browse files
authored
Merge pull request #1007 from lavalink-devs/dev
release 4.0.2
2 parents a527886 + c81107a commit 3a20917

File tree

8 files changed

+89
-95
lines changed

8 files changed

+89
-95
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ gradle.properties
2121
application.yml
2222
LavalinkServer/plugins
2323
.cache/
24-
site/
24+
site/
25+
.DS_Store

CHANGELOG.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
Each release usually includes various fixes and improvements.
44
The most noteworthy of these, as well as any features and breaking changes, are listed here.
55

6+
## v4.0.2
7+
* Fixed issue where all plugins get deleted when already present (introduced in [`v4.0.1`](https://github.com/lavalink-devs/Lavalink/releases/tag/4.0.1))
8+
* Always include plugin info & user data when serializing (introduced in [`v4.0.1`](https://github.com/lavalink-devs/Lavalink/releases/tag/4.0.1))
9+
* Updated oshi to `6.4.11`
10+
611
## 4.0.1
7-
* Updated Lavaplayer to 2.10
8-
* Updated OSHI to 6.4.8
12+
* Updated Lavaplayer to `2.1.0`
13+
* Updated oshi to `6.4.8`
914
* Fix/user data missing field exception in protocol
1015
* Fix plugin manager not deleting old plugin version
1116
* Fix not being able to seek when player is paused
1217
* Removed illegal reflection notice
1318

1419
## 4.0.0
1520
* Lavalink now requires Java 17 or higher to run
16-
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](../api/rest.md)**
21+
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](https://lavalink.dev/api/rest.html)**
1722
* Remove default 4GB max heap allocation from docker image
1823
* Removal of all `/v3` endpoints except `/version`. All other endpoints are now under `/v4`
1924
* Reworked track loading result. For more info see [here](https://lavalink.dev/api/rest.md#track-loading-result)
@@ -59,17 +64,17 @@ The most noteworthy of these, as well as any features and breaking changes, are
5964

6065
## 4.0.0-beta.1
6166
* New Lavalink now requires Java 17 or higher to run
62-
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](IMPLEMENTATION.md#rest-api)**
67+
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](https://lavalink.dev/api/rest.html)**
6368
* Update to [Lavaplayer custom branch](https://github.com/Walkyst/lavaplayer-fork/tree/custom), which includes native support for artwork urls and ISRCs in the track info
6469
* Addition of full `Track` objects in following events: `TrackStartEvent`, `TrackEndEvent`, `TrackExceptionEvent`, `TrackStuckEvent`
6570
* Resuming a session now requires the `Session-Id` header instead of `Resume-Key` header
66-
* Reworked track loading result. For more info see [here](IMPLEMENTATION.md#track-loading-result)
71+
* Reworked track loading result. For more info see [here](https://lavalink.dev/api/rest.html#track-loading-result)
6772
* Update to the [Protocol Module](protocol) to support Kotlin/JS
6873
* Removal of all `/v3` endpoints except `/version`. All other endpoints are now under `/v4`
6974

7075
> **Warning**
7176
> This is a beta release, and as such, may contain bugs. Please report any bugs you find to the [issue tracker](https://github.com/lavalink-devs/Lavalink/issues/new/choose).
72-
> For more info on the changes in this release, see [here](IMPLEMENTATION.md#significant-changes-v370---v400)
77+
> For more info on the changes in this release, see [here](https://lavalink.dev/changelog/index.html#significant-changes)
7378
> If you have any question regarding the changes in this release, please ask in the [support server](https://discord.gg/ZW4s47Ppw4) or [GitHub discussions](https://github.com/lavalink-devs/Lavalink/discussions/categories/q-a)
7479
7580
Contributors:

LavalinkServer/src/main/java/lavalink/server/Launcher.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ object Launcher {
143143
.properties(properties)
144144
.web(WebApplicationType.SERVLET)
145145
.bannerMode(Banner.Mode.OFF)
146-
.resourceLoader(DefaultResourceLoader(pluginManager.classLoader))
146+
.resourceLoader(DefaultResourceLoader(pluginManager::class.java.classLoader))
147147
.listeners(
148148
ApplicationListener { event: Any ->
149149
when (event) {

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

+60-76
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ import java.util.jar.JarFile
1515

1616
@SpringBootApplication
1717
class PluginManager(val config: PluginsConfig) {
18-
1918
companion object {
2019
private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java)
2120
}
2221

2322
final val pluginManifests: MutableList<PluginManifest> = mutableListOf()
24-
var classLoader: ClassLoader = PluginManager::class.java.classLoader
2523

2624
init {
2725
manageDownloads()
@@ -33,53 +31,59 @@ class PluginManager(val config: PluginsConfig) {
3331

3432
private fun manageDownloads() {
3533
if (config.plugins.isEmpty()) return
34+
3635
val directory = File(config.pluginsDir)
3736
directory.mkdir()
3837

39-
data class PluginJar(val manifest: PluginManifest, val file: File)
40-
41-
val pluginJars = directory.listFiles()!!.filter { it.extension == "jar" }.map {
42-
JarFile(it).use {jar ->
43-
loadPluginManifests(jar).map { manifest -> PluginJar(manifest, it) }
38+
val pluginJars = directory.listFiles()?.filter { it.extension == "jar" }
39+
?.flatMap { file ->
40+
JarFile(file).use { jar ->
41+
loadPluginManifests(jar).map { manifest -> PluginJar(manifest, file) }
42+
}
4443
}
45-
}.flatten()
46-
47-
data class Declaration(val group: String, val name: String, val version: String, val repository: String)
44+
?.onEach { log.info("Found plugin '${it.manifest.name}' version ${it.manifest.version}") }
45+
?: return
4846

4947
val declarations = config.plugins.map { declaration ->
5048
if (declaration.dependency == null) throw RuntimeException("Illegal dependency declaration: null")
5149
val fragments = declaration.dependency!!.split(":")
5250
if (fragments.size != 3) throw RuntimeException("Invalid dependency \"${declaration.dependency}\"")
5351

54-
var repository = declaration.repository
55-
?: if (declaration.snapshot) config.defaultPluginSnapshotRepository else config.defaultPluginRepository
56-
repository = if (repository.endsWith("/")) repository else "$repository/"
57-
Declaration(fragments[0], fragments[1], fragments[2], repository)
52+
val repository = declaration.repository
53+
?: config.defaultPluginSnapshotRepository.takeIf { declaration.snapshot }
54+
?: config.defaultPluginRepository
55+
56+
Declaration(fragments[0], fragments[1], fragments[2], "${repository.removeSuffix("/")}/")
5857
}.distinctBy { "${it.group}:${it.name}" }
5958

60-
declarations.forEach declarationLoop@{ declaration ->
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
59+
for (declaration in declarations) {
60+
val jars = pluginJars.filter { it.manifest.name == declaration.name }
61+
var hasCurrentVersion = false
62+
63+
for (jar in jars) {
64+
if (jar.manifest.version == declaration.version) {
65+
hasCurrentVersion = true
66+
// Don't clean up the jar if it's a current version.
67+
continue
6768
}
6869

69-
// Delete jar of different versions
70+
// Delete versions of the plugin that aren't the same as declared version.
7071
if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}")
71-
log.info("Deleted ${jar.file.path}")
72+
log.info("Deleted ${jar.file.path} (new version: ${declaration.version})")
73+
7274
}
73-
if (hasVersion) return@declarationLoop
7475

75-
val url = declaration.run { "$repository${group.replace(".", "/")}/$name/$version/$name-$version.jar" }
76-
val file = File(directory, declaration.run { "$name-$version.jar" })
77-
downloadJar(file, url)
76+
if (!hasCurrentVersion) {
77+
val url = declaration.url
78+
val file = File(directory, declaration.canonicalJarName)
79+
downloadJar(file, url)
80+
}
7881
}
7982
}
8083

8184
private fun downloadJar(output: File, url: String) {
8285
log.info("Downloading $url")
86+
8387
Channels.newChannel(URL(url).openStream()).use {
8488
FileOutputStream(output).channel.transferFrom(it, 0, Long.MAX_VALUE)
8589
}
@@ -88,61 +92,43 @@ class PluginManager(val config: PluginsConfig) {
8892
private fun readClasspathManifests(): List<PluginManifest> {
8993
return PathMatchingResourcePatternResolver()
9094
.getResources("classpath*:lavalink-plugins/*.properties")
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-
}
95+
.map { parsePluginManifest(it.inputStream) }
96+
.onEach { log.info("Found plugin '${it.name}' version ${it.version}") }
9697
}
9798

9899
private fun loadJars(): List<PluginManifest> {
99-
val directory = File(config.pluginsDir)
100-
if (!directory.isDirectory) return emptyList()
101-
val jarsToLoad = mutableListOf<File>()
102-
103-
directory.listFiles()?.forEach { file ->
104-
if (!file.isFile) return@forEach
105-
if (file.extension != "jar") return@forEach
106-
jarsToLoad.add(file)
107-
}
100+
val directory = File(config.pluginsDir).takeIf { it.isDirectory }
101+
?: return emptyList()
108102

109-
if (jarsToLoad.isEmpty()) return emptyList()
103+
val jarsToLoad = directory.listFiles()?.filter { it.isFile && it.extension == "jar" }
104+
?.takeIf { it.isNotEmpty() }
105+
?: return emptyList()
110106

111-
val cl = URLClassLoader.newInstance(
107+
val classLoader = URLClassLoader.newInstance(
112108
jarsToLoad.map { URL("jar:file:${it.absolutePath}!/") }.toTypedArray(),
113109
javaClass.classLoader
114110
)
115-
classLoader = cl
116-
117-
val manifests = mutableListOf<PluginManifest>()
118-
jarsToLoad.forEach { file ->
119-
try {
120-
manifests.addAll(loadJar(file, cl))
121-
} catch (e: Exception) {
122-
throw RuntimeException("Error loading $file", e)
123-
}
124-
}
125111

126-
127-
return manifests
112+
return jarsToLoad.flatMap { loadJar(it, classLoader) }
128113
}
129114

130115
private fun loadJar(file: File, cl: URLClassLoader): List<PluginManifest> {
131-
var classCount = 0
132116
val jar = JarFile(file)
133-
var manifests: List<PluginManifest>
117+
val manifests = loadPluginManifests(jar)
118+
var classCount = 0
134119

135120
jar.use {
136-
manifests = loadPluginManifests(jar)
137121
if (manifests.isEmpty()) {
138122
throw RuntimeException("No plugin manifest found in ${file.path}")
139123
}
140-
val allowedPaths = manifests.map { it.path.replace(".", "/") }
141124

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
125+
val allowedPaths = manifests.map { manifest -> manifest.path.replace(".", "/") }
126+
127+
for (entry in it.entries()) {
128+
if (entry.isDirectory ||
129+
!entry.name.endsWith(".class") ||
130+
allowedPaths.none(entry.name::startsWith)) continue
131+
146132
cl.loadClass(entry.name.dropLast(6).replace("/", "."))
147133
classCount++
148134
}
@@ -153,18 +139,10 @@ class PluginManager(val config: PluginsConfig) {
153139
}
154140

155141
private fun loadPluginManifests(jar: JarFile): List<PluginManifest> {
156-
val manifests = mutableListOf<PluginManifest>()
157-
158-
jar.entries().asIterator().forEach { entry ->
159-
if (entry.isDirectory) return@forEach
160-
if (!entry.name.startsWith("lavalink-plugins/")) return@forEach
161-
if (!entry.name.endsWith(".properties")) return@forEach
162-
163-
val manifest = parsePluginManifest(jar.getInputStream(entry))
164-
log.info("Found plugin '${manifest.name}' version ${manifest.version}")
165-
manifests.add(manifest)
166-
}
167-
return manifests
142+
return jar.entries().asSequence()
143+
.filter { !it.isDirectory && it.name.startsWith("lavalink-plugins/") && it.name.endsWith(".properties") }
144+
.map { parsePluginManifest(jar.getInputStream(it)) }
145+
.toList()
168146
}
169147

170148
private fun parsePluginManifest(stream: InputStream): PluginManifest {
@@ -177,4 +155,10 @@ class PluginManager(val config: PluginsConfig) {
177155
val version = props.getProperty("version") ?: throw RuntimeException("Manifest is missing 'version'")
178156
return PluginManifest(name, path, version)
179157
}
180-
}
158+
159+
private data class PluginJar(val manifest: PluginManifest, val file: File)
160+
private data class Declaration(val group: String, val name: String, val version: String, val repository: String) {
161+
val canonicalJarName = "$name-$version.jar"
162+
val url = "$repository${group.replace(".", "/")}/$name/$version/$name-$version.jar"
163+
}
164+
}

docs/changelog/v4.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
## 4.0.1
2-
* Updated Lavaplayer to 2.10
3-
* Updated OSHI to 6.4.8
1+
## v4.0.2
2+
* Fixed issue where all plugins get deleted when already present (introduced in [`v4.0.1`](https://github.com/lavalink-devs/Lavalink/releases/tag/4.0.1))
3+
* Always include plugin info & user data when serializing (introduced in [`v4.0.1`](https://github.com/lavalink-devs/Lavalink/releases/tag/4.0.1))
4+
* Updated oshi to `6.4.11`
5+
6+
## v4.0.1
7+
* Updated Lavaplayer to `2.1.0`
8+
* Updated oshi to `6.4.8`
49
* Fix/user data missing field exception in protocol
510
* Fix plugin manager not deleting old plugin version
611
* Fix not being able to seek when player is paused

protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/loadResult.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
package dev.arbjerg.lavalink.protocol.v4
44

55
import dev.arbjerg.lavalink.protocol.v4.serialization.asPolymorphicDeserializer
6-
import kotlinx.serialization.DeserializationStrategy
7-
import kotlinx.serialization.KSerializer
8-
import kotlinx.serialization.SerialName
9-
import kotlinx.serialization.Serializable
6+
import kotlinx.serialization.*
107
import kotlinx.serialization.builtins.ListSerializer
118
import kotlinx.serialization.descriptors.SerialDescriptor
129
import kotlinx.serialization.encoding.Decoder
@@ -133,6 +130,7 @@ data class PlaylistInfo(
133130
@Serializable
134131
data class Playlist(
135132
val info: PlaylistInfo,
133+
@EncodeDefault
136134
val pluginInfo: JsonObject = JsonObject(emptyMap()),
137135
val tracks: List<Track>
138136
) : LoadResult.Data {

protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/player.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package dev.arbjerg.lavalink.protocol.v4
22

3-
import kotlinx.serialization.DeserializationStrategy
4-
import kotlinx.serialization.Serializable
3+
import kotlinx.serialization.*
4+
import kotlinx.serialization.json.JsonNames
55
import kotlinx.serialization.json.JsonObject
6-
import kotlinx.serialization.serializer
76
import kotlin.jvm.JvmInline
87

98
inline fun <reified T> JsonObject.deserialize(): T =
@@ -31,7 +30,9 @@ data class Player(
3130
data class Track(
3231
val encoded: String,
3332
val info: TrackInfo,
33+
@EncodeDefault
3434
val pluginInfo: JsonObject = JsonObject(emptyMap()),
35+
@EncodeDefault
3536
val userData: JsonObject = JsonObject(emptyMap())
3637
) : LoadResult.Data {
3738

settings.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fun VersionCatalogBuilder.common() {
7676

7777
library("logback", "ch.qos.logback", "logback-classic").version("1.4.7")
7878
library("sentry-logback", "io.sentry", "sentry-logback").version("6.22.0")
79-
library("oshi", "com.github.oshi", "oshi-core").version("6.4.8")
79+
library("oshi", "com.github.oshi", "oshi-core").version("6.4.11")
8080
}
8181

8282
fun VersionCatalogBuilder.other() {

0 commit comments

Comments
 (0)