diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0ec9644..769a345d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Each release usually includes various fixes and improvements. The most noteworthy of these, as well as any features and breaking changes, are listed here. +## 4.0.1 +* Updated Lavaplayer to 2.10 +* Updated OSHI to 6.4.8 +* Fix/user data missing field exception in protocol +* Fix plugin manager not deleting old plugin version +* Fix not being able to seek when player is paused +* Removed illegal reflection notice + ## 4.0.0 * Lavalink now requires Java 17 or higher to run * **Removal of all websocket messages sent by the client. Everything is now done via [REST](../api/rest.md)** diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt index 6ae103fe7..8252b9a47 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt @@ -123,7 +123,6 @@ object Launcher { } val parent = launchPluginBootstrap() - log.info("You can safely ignore the big red warning about illegal reflection. See https://github.com/lavalink-devs/Lavalink/issues/295") launchMain(parent, args) } diff --git a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt index 6f168a1b6..d34d61628 100644 --- a/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt +++ b/LavalinkServer/src/main/java/lavalink/server/bootstrap/PluginManager.kt @@ -10,15 +10,16 @@ import java.io.InputStream import java.net.URL import java.net.URLClassLoader import java.nio.channels.Channels -import java.nio.file.Files import java.util.* import java.util.jar.JarFile -import java.util.regex.Pattern - @SpringBootApplication class PluginManager(val config: PluginsConfig) { + companion object { + private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java) + } + final val pluginManifests: MutableList = mutableListOf() var classLoader: ClassLoader = PluginManager::class.java.classLoader @@ -35,14 +36,13 @@ class PluginManager(val config: PluginsConfig) { val directory = File(config.pluginsDir) directory.mkdir() - data class PluginJar(val name: String, val version: String, val file: File) + data class PluginJar(val manifest: PluginManifest, val file: File) - val pattern = Pattern.compile("(.+)-(.+)\\.jar$") - val pluginJars = directory.listFiles()!!.mapNotNull { f -> - val matcher = pattern.matcher(f.name) - if (!matcher.find()) return@mapNotNull null - PluginJar(matcher.group(1), matcher.group(2), f) - } + val pluginJars = directory.listFiles()!!.filter { it.extension == "jar" }.map { + JarFile(it).use {jar -> + loadPluginManifests(jar).map { manifest -> PluginJar(manifest, it) } + } + }.flatten() data class Declaration(val group: String, val name: String, val version: String, val repository: String) @@ -55,21 +55,22 @@ class PluginManager(val config: PluginsConfig) { ?: if (declaration.snapshot) config.defaultPluginSnapshotRepository else config.defaultPluginRepository repository = if (repository.endsWith("/")) repository else "$repository/" Declaration(fragments[0], fragments[1], fragments[2], repository) - } + }.distinctBy { "${it.group}:${it.name}" } declarations.forEach declarationLoop@{ declaration -> - pluginJars.forEach { jar -> - if (declaration.name == jar.name) { - if (declaration.version == jar.version) { - // We already have this jar so don't redownload it - return@declarationLoop - } - - // Delete jar of different versions - if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}") - log.info("Deleted ${jar.file.path}") + var hasVersion = false + pluginJars.forEach pluginLoop@{ jar -> + if (declaration.version == jar.manifest.version && !hasVersion) { + hasVersion = true + // We already have this jar so don't redownload it + return@pluginLoop } + + // Delete jar of different versions + if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}") + log.info("Deleted ${jar.file.path}") } + if (hasVersion) return@declarationLoop val url = declaration.run { "$repository${group.replace(".", "/")}/$name/$version/$name-$version.jar" } val file = File(directory, declaration.run { "$name-$version.jar" }) @@ -87,7 +88,11 @@ class PluginManager(val config: PluginsConfig) { private fun readClasspathManifests(): List { return PathMatchingResourcePatternResolver() .getResources("classpath*:lavalink-plugins/*.properties") - .map { r -> parsePluginManifest(r.inputStream) } + .map map@{ r -> + val manifest = parsePluginManifest(r.inputStream) + log.info("Found plugin '${manifest.name}' version ${manifest.version}") + return@map manifest + } } private fun loadJars(): List { @@ -95,8 +100,7 @@ class PluginManager(val config: PluginsConfig) { if (!directory.isDirectory) return emptyList() val jarsToLoad = mutableListOf() - Files.list(File(config.pluginsDir).toPath()).forEach { path -> - val file = path.toFile() + directory.listFiles()?.forEach { file -> if (!file.isFile) return@forEach if (file.extension != "jar") return@forEach jarsToLoad.add(file) @@ -111,7 +115,6 @@ class PluginManager(val config: PluginsConfig) { classLoader = cl val manifests = mutableListOf() - jarsToLoad.forEach { file -> try { manifests.addAll(loadJar(file, cl)) @@ -124,31 +127,43 @@ class PluginManager(val config: PluginsConfig) { return manifests } - private fun loadJar(file: File, cl: URLClassLoader): MutableList { + private fun loadJar(file: File, cl: URLClassLoader): List { var classCount = 0 val jar = JarFile(file) + var manifests: List + + jar.use { + manifests = loadPluginManifests(jar) + if (manifests.isEmpty()) { + throw RuntimeException("No plugin manifest found in ${file.path}") + } + val allowedPaths = manifests.map { it.path.replace(".", "/") } + + jar.entries().asIterator().forEach { entry -> + if (entry.isDirectory) return@forEach + if (!entry.name.endsWith(".class")) return@forEach + if (!allowedPaths.any { entry.name.startsWith(it) }) return@forEach + cl.loadClass(entry.name.dropLast(6).replace("/", ".")) + classCount++ + } + } + + log.info("Loaded ${file.name} ($classCount classes)") + return manifests + } + + private fun loadPluginManifests(jar: JarFile): List { val manifests = mutableListOf() jar.entries().asIterator().forEach { entry -> if (entry.isDirectory) return@forEach if (!entry.name.startsWith("lavalink-plugins/")) return@forEach if (!entry.name.endsWith(".properties")) return@forEach - manifests.add(parsePluginManifest(jar.getInputStream(entry))) - } - if (manifests.isEmpty()) { - throw RuntimeException("No plugin manifest found in ${file.path}") + val manifest = parsePluginManifest(jar.getInputStream(entry)) + log.info("Found plugin '${manifest.name}' version ${manifest.version}") + manifests.add(manifest) } - val allowedPaths = manifests.map { it.path.replace(".", "/") } - - jar.entries().asIterator().forEach { entry -> - if (entry.isDirectory) return@forEach - if (!entry.name.endsWith(".class")) return@forEach - if (!allowedPaths.any { entry.name.startsWith(it) }) return@forEach - cl.loadClass(entry.name.dropLast(6).replace("/", ".")) - classCount++ - } - log.info("Loaded ${file.name} ($classCount classes)") return manifests } @@ -160,11 +175,6 @@ class PluginManager(val config: PluginsConfig) { val name = props.getProperty("name") ?: throw RuntimeException("Manifest is missing 'name'") val path = props.getProperty("path") ?: throw RuntimeException("Manifest is missing 'path'") val version = props.getProperty("version") ?: throw RuntimeException("Manifest is missing 'version'") - log.info("Found plugin '$name' version $version") return PluginManifest(name, path, version) } - - companion object { - private val log: Logger = LoggerFactory.getLogger(PluginManager::class.java) - } } \ No newline at end of file diff --git a/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt b/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt index fbf73b34e..16c57eb61 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.kt @@ -2,18 +2,32 @@ package lavalink.server.config import lavalink.server.io.HandshakeInterceptorImpl import lavalink.server.io.SocketServer +import org.slf4j.LoggerFactory import org.springframework.context.annotation.Configuration +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController import org.springframework.web.socket.config.annotation.EnableWebSocket import org.springframework.web.socket.config.annotation.WebSocketConfigurer import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry @Configuration @EnableWebSocket +@RestController class WebsocketConfig( private val server: SocketServer, private val handshakeInterceptor: HandshakeInterceptorImpl, ) : WebSocketConfigurer { + + companion object { + private val log = LoggerFactory.getLogger(WebsocketConfig::class.java) + } + override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { registry.addHandler(server, "/v4/websocket").addInterceptors(handshakeInterceptor) } + + @GetMapping("/", "/v3/websocket") + fun oldWebsocket() { + log.warn("This is the old Lavalink websocket endpoint. Please use /v4/websocket instead. If you are using a client library, please update it to a Lavalink v4 compatible version or use Lavalink v3 instead.") + } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt b/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt index 16633e405..4623851ff 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/StatsCollector.kt @@ -49,6 +49,10 @@ class StatsCollector(val socketServer: SocketServer) { private val processRecentCpuUsage: Double get() { val p = os.getProcess(os.processId) + if (p == null) { + log.warn("Failed to get process stats. Process was null.") + return 0.0 + } val output: Double = if (cpuTime != 0.0) { val uptimeDiff = p.upTime - uptime diff --git a/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt index 6a837dbf5..4f4831078 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt @@ -150,7 +150,7 @@ class PlayerRestHandler( // we handle position differently for playing new tracks playerUpdate.position.takeIfPresent { encodedTrack is Omissible.Omitted && identifier is Omissible.Omitted } ?.let { - if (player.isPlaying) { + if (player.track != null) { player.seekTo(it) SocketServer.sendPlayerUpdate(context, player) } diff --git a/build.gradle.kts b/build.gradle.kts index 98e606842..cd938efea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,7 +54,7 @@ subprojects { val snapshots = "https://maven.lavalink.dev/snapshots" val releases = "https://maven.lavalink.dev/releases" - maven(if ((version as String).endsWith("-SNAPSHOT")) releases else snapshots) { + maven(if ((version as String).endsWith("-SNAPSHOT")) snapshots else releases) { credentials { password = findProperty("MAVEN_PASSWORD") as String? username = findProperty("MAVEN_USERNAME") as String? diff --git a/docs/changelog/v4.md b/docs/changelog/v4.md index e7db702f5..ad762f496 100644 --- a/docs/changelog/v4.md +++ b/docs/changelog/v4.md @@ -1,3 +1,11 @@ +## 4.0.1 +* Updated Lavaplayer to 2.10 +* Updated OSHI to 6.4.8 +* Fix/user data missing field exception in protocol +* Fix plugin manager not deleting old plugin version +* Fix not being able to seek when player is paused +* Removed illegal reflection notice + ## v4.0.0 * Lavalink now requires Java 17 or higher to run * **Removal of all websocket messages sent by the client. Everything is now done via [REST](../api/rest.md)** diff --git a/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/loadResult.kt b/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/loadResult.kt index f1b902d1f..c5c348c3c 100644 --- a/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/loadResult.kt +++ b/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/loadResult.kt @@ -133,7 +133,7 @@ data class PlaylistInfo( @Serializable data class Playlist( val info: PlaylistInfo, - val pluginInfo: JsonObject, + val pluginInfo: JsonObject = JsonObject(emptyMap()), val tracks: List ) : LoadResult.Data { diff --git a/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/omissible.kt b/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/omissible.kt index a9fbb1aaf..6da7cd5b7 100644 --- a/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/omissible.kt +++ b/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/omissible.kt @@ -73,7 +73,7 @@ class OmissableSerializer(private val childSerializer: KSerializer) : KSer } @OptIn(ExperimentalContracts::class) -fun Omissible.isPresent(): Boolean { +fun Omissible.isPresent(): Boolean { contract { returns(true) implies (this@isPresent is Omissible.Present) } @@ -81,7 +81,7 @@ fun Omissible.isPresent(): Boolean { } @OptIn(ExperimentalContracts::class) -fun Omissible.isOmitted(): Boolean { +fun Omissible.isOmitted(): Boolean { contract { returns(true) implies (this@isOmitted is Omissible.Omitted) } diff --git a/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/player.kt b/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/player.kt index b9ddf2733..19c7a7917 100644 --- a/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/player.kt +++ b/protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/player.kt @@ -31,8 +31,8 @@ data class Player( data class Track( val encoded: String, val info: TrackInfo, - val pluginInfo: JsonObject, - val userData: JsonObject + val pluginInfo: JsonObject = JsonObject(emptyMap()), + val userData: JsonObject = JsonObject(emptyMap()) ) : LoadResult.Data { /** diff --git a/settings.gradle.kts b/settings.gradle.kts index 459f9affe..861cb22c7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,7 +36,7 @@ fun VersionCatalogBuilder.spring() { } fun VersionCatalogBuilder.voice() { - version("lavaplayer", "2.0.4") + version("lavaplayer", "2.1.0") library("lavaplayer", "dev.arbjerg", "lavaplayer").versionRef("lavaplayer") library("lavaplayer-ip-rotator", "dev.arbjerg", "lavaplayer-ext-youtube-rotator").versionRef("lavaplayer") @@ -76,7 +76,7 @@ fun VersionCatalogBuilder.common() { library("logback", "ch.qos.logback", "logback-classic").version("1.4.7") library("sentry-logback", "io.sentry", "sentry-logback").version("6.22.0") - library("oshi", "com.github.oshi", "oshi-core").version("6.4.3") + library("oshi", "com.github.oshi", "oshi-core").version("6.4.8") } fun VersionCatalogBuilder.other() {