Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release 4.0.1 #1005

Merged
merged 12 commits into from
Feb 5, 2024
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**
Expand Down
1 change: 0 additions & 1 deletion LavalinkServer/src/main/java/lavalink/server/Launcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PluginManifest> = mutableListOf()
var classLoader: ClassLoader = PluginManager::class.java.classLoader

Expand All @@ -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)

Expand All @@ -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" })
Expand All @@ -87,16 +88,19 @@ class PluginManager(val config: PluginsConfig) {
private fun readClasspathManifests(): List<PluginManifest> {
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<PluginManifest> {
val directory = File(config.pluginsDir)
if (!directory.isDirectory) return emptyList()
val jarsToLoad = mutableListOf<File>()

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)
Expand All @@ -111,7 +115,6 @@ class PluginManager(val config: PluginsConfig) {
classLoader = cl

val manifests = mutableListOf<PluginManifest>()

jarsToLoad.forEach { file ->
try {
manifests.addAll(loadJar(file, cl))
Expand All @@ -124,31 +127,43 @@ class PluginManager(val config: PluginsConfig) {
return manifests
}

private fun loadJar(file: File, cl: URLClassLoader): MutableList<PluginManifest> {
private fun loadJar(file: File, cl: URLClassLoader): List<PluginManifest> {
var classCount = 0
val jar = JarFile(file)
var manifests: List<PluginManifest>

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<PluginManifest> {
val manifests = mutableListOf<PluginManifest>()

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
}

Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog/v4.md
Original file line number Diff line number Diff line change
@@ -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)**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Track>
) : LoadResult.Data {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ class OmissableSerializer<T>(private val childSerializer: KSerializer<T>) : KSer
}

@OptIn(ExperimentalContracts::class)
fun <T : Any> Omissible<T>.isPresent(): Boolean {
fun <T : Any?> Omissible<T>.isPresent(): Boolean {
contract {
returns(true) implies (this@isPresent is Omissible.Present<T>)
}
return this is Omissible.Present
}

@OptIn(ExperimentalContracts::class)
fun <T : Any> Omissible<T>.isOmitted(): Boolean {
fun <T : Any?> Omissible<T>.isOmitted(): Boolean {
contract {
returns(true) implies (this@isOmitted is Omissible.Omitted<T>)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

/**
Expand Down
4 changes: 2 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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() {
Expand Down
Loading