Skip to content

Commit

Permalink
Merge pull request #1007 from lavalink-devs/dev
Browse files Browse the repository at this point in the history
release 4.0.2
  • Loading branch information
topi314 committed Feb 6, 2024
2 parents a527886 + c81107a commit 3a20917
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 95 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -21,4 +21,5 @@ gradle.properties
application.yml
LavalinkServer/plugins
.cache/
site/
site/
.DS_Store
17 changes: 11 additions & 6 deletions CHANGELOG.md
Expand Up @@ -3,17 +3,22 @@
Each release usually includes various fixes and improvements.
The most noteworthy of these, as well as any features and breaking changes, are listed here.

## v4.0.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))
* Always include plugin info & user data when serializing (introduced in [`v4.0.1`](https://github.com/lavalink-devs/Lavalink/releases/tag/4.0.1))
* Updated oshi to `6.4.11`

## 4.0.1
* Updated Lavaplayer to 2.10
* Updated OSHI to 6.4.8
* Updated Lavaplayer to `2.1.0`
* 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)**
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](https://lavalink.dev/api/rest.html)**
* Remove default 4GB max heap allocation from docker image
* Removal of all `/v3` endpoints except `/version`. All other endpoints are now under `/v4`
* Reworked track loading result. For more info see [here](https://lavalink.dev/api/rest.md#track-loading-result)
Expand Down Expand Up @@ -59,17 +64,17 @@ The most noteworthy of these, as well as any features and breaking changes, are

## 4.0.0-beta.1
* New Lavalink now requires Java 17 or higher to run
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](IMPLEMENTATION.md#rest-api)**
* **Removal of all websocket messages sent by the client. Everything is now done via [REST](https://lavalink.dev/api/rest.html)**
* 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
* Addition of full `Track` objects in following events: `TrackStartEvent`, `TrackEndEvent`, `TrackExceptionEvent`, `TrackStuckEvent`
* Resuming a session now requires the `Session-Id` header instead of `Resume-Key` header
* Reworked track loading result. For more info see [here](IMPLEMENTATION.md#track-loading-result)
* Reworked track loading result. For more info see [here](https://lavalink.dev/api/rest.html#track-loading-result)
* Update to the [Protocol Module](protocol) to support Kotlin/JS
* Removal of all `/v3` endpoints except `/version`. All other endpoints are now under `/v4`

> **Warning**
> 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).
> For more info on the changes in this release, see [here](IMPLEMENTATION.md#significant-changes-v370---v400)
> For more info on the changes in this release, see [here](https://lavalink.dev/changelog/index.html#significant-changes)
> 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)
Contributors:
Expand Down
2 changes: 1 addition & 1 deletion LavalinkServer/src/main/java/lavalink/server/Launcher.kt
Expand Up @@ -143,7 +143,7 @@ object Launcher {
.properties(properties)
.web(WebApplicationType.SERVLET)
.bannerMode(Banner.Mode.OFF)
.resourceLoader(DefaultResourceLoader(pluginManager.classLoader))
.resourceLoader(DefaultResourceLoader(pluginManager::class.java.classLoader))
.listeners(
ApplicationListener { event: Any ->
when (event) {
Expand Down
Expand Up @@ -15,13 +15,11 @@ import java.util.jar.JarFile

@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

init {
manageDownloads()
Expand All @@ -33,53 +31,59 @@ class PluginManager(val config: PluginsConfig) {

private fun manageDownloads() {
if (config.plugins.isEmpty()) return

val directory = File(config.pluginsDir)
directory.mkdir()

data class PluginJar(val manifest: PluginManifest, val file: File)

val pluginJars = directory.listFiles()!!.filter { it.extension == "jar" }.map {
JarFile(it).use {jar ->
loadPluginManifests(jar).map { manifest -> PluginJar(manifest, it) }
val pluginJars = directory.listFiles()?.filter { it.extension == "jar" }
?.flatMap { file ->
JarFile(file).use { jar ->
loadPluginManifests(jar).map { manifest -> PluginJar(manifest, file) }
}
}
}.flatten()

data class Declaration(val group: String, val name: String, val version: String, val repository: String)
?.onEach { log.info("Found plugin '${it.manifest.name}' version ${it.manifest.version}") }
?: return

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

var repository = declaration.repository
?: if (declaration.snapshot) config.defaultPluginSnapshotRepository else config.defaultPluginRepository
repository = if (repository.endsWith("/")) repository else "$repository/"
Declaration(fragments[0], fragments[1], fragments[2], repository)
val repository = declaration.repository
?: config.defaultPluginSnapshotRepository.takeIf { declaration.snapshot }
?: config.defaultPluginRepository

Declaration(fragments[0], fragments[1], fragments[2], "${repository.removeSuffix("/")}/")
}.distinctBy { "${it.group}:${it.name}" }

declarations.forEach declarationLoop@{ declaration ->
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
for (declaration in declarations) {
val jars = pluginJars.filter { it.manifest.name == declaration.name }
var hasCurrentVersion = false

for (jar in jars) {
if (jar.manifest.version == declaration.version) {
hasCurrentVersion = true
// Don't clean up the jar if it's a current version.
continue
}

// Delete jar of different versions
// Delete versions of the plugin that aren't the same as declared version.
if (!jar.file.delete()) throw RuntimeException("Failed to delete ${jar.file.path}")
log.info("Deleted ${jar.file.path}")
log.info("Deleted ${jar.file.path} (new version: ${declaration.version})")

}
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" })
downloadJar(file, url)
if (!hasCurrentVersion) {
val url = declaration.url
val file = File(directory, declaration.canonicalJarName)
downloadJar(file, url)
}
}
}

private fun downloadJar(output: File, url: String) {
log.info("Downloading $url")

Channels.newChannel(URL(url).openStream()).use {
FileOutputStream(output).channel.transferFrom(it, 0, Long.MAX_VALUE)
}
Expand All @@ -88,61 +92,43 @@ class PluginManager(val config: PluginsConfig) {
private fun readClasspathManifests(): List<PluginManifest> {
return PathMatchingResourcePatternResolver()
.getResources("classpath*:lavalink-plugins/*.properties")
.map map@{ r ->
val manifest = parsePluginManifest(r.inputStream)
log.info("Found plugin '${manifest.name}' version ${manifest.version}")
return@map manifest
}
.map { parsePluginManifest(it.inputStream) }
.onEach { log.info("Found plugin '${it.name}' version ${it.version}") }
}

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

directory.listFiles()?.forEach { file ->
if (!file.isFile) return@forEach
if (file.extension != "jar") return@forEach
jarsToLoad.add(file)
}
val directory = File(config.pluginsDir).takeIf { it.isDirectory }
?: return emptyList()

if (jarsToLoad.isEmpty()) return emptyList()
val jarsToLoad = directory.listFiles()?.filter { it.isFile && it.extension == "jar" }
?.takeIf { it.isNotEmpty() }
?: return emptyList()

val cl = URLClassLoader.newInstance(
val classLoader = URLClassLoader.newInstance(
jarsToLoad.map { URL("jar:file:${it.absolutePath}!/") }.toTypedArray(),
javaClass.classLoader
)
classLoader = cl

val manifests = mutableListOf<PluginManifest>()
jarsToLoad.forEach { file ->
try {
manifests.addAll(loadJar(file, cl))
} catch (e: Exception) {
throw RuntimeException("Error loading $file", e)
}
}


return manifests
return jarsToLoad.flatMap { loadJar(it, classLoader) }
}

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

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
val allowedPaths = manifests.map { manifest -> manifest.path.replace(".", "/") }

for (entry in it.entries()) {
if (entry.isDirectory ||
!entry.name.endsWith(".class") ||
allowedPaths.none(entry.name::startsWith)) continue

cl.loadClass(entry.name.dropLast(6).replace("/", "."))
classCount++
}
Expand All @@ -153,18 +139,10 @@ class PluginManager(val config: PluginsConfig) {
}

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

val manifest = parsePluginManifest(jar.getInputStream(entry))
log.info("Found plugin '${manifest.name}' version ${manifest.version}")
manifests.add(manifest)
}
return manifests
return jar.entries().asSequence()
.filter { !it.isDirectory && it.name.startsWith("lavalink-plugins/") && it.name.endsWith(".properties") }
.map { parsePluginManifest(jar.getInputStream(it)) }
.toList()
}

private fun parsePluginManifest(stream: InputStream): PluginManifest {
Expand All @@ -177,4 +155,10 @@ class PluginManager(val config: PluginsConfig) {
val version = props.getProperty("version") ?: throw RuntimeException("Manifest is missing 'version'")
return PluginManifest(name, path, version)
}
}

private data class PluginJar(val manifest: PluginManifest, val file: File)
private data class Declaration(val group: String, val name: String, val version: String, val repository: String) {
val canonicalJarName = "$name-$version.jar"
val url = "$repository${group.replace(".", "/")}/$name/$version/$name-$version.jar"
}
}
11 changes: 8 additions & 3 deletions docs/changelog/v4.md
@@ -1,6 +1,11 @@
## 4.0.1
* Updated Lavaplayer to 2.10
* Updated OSHI to 6.4.8
## v4.0.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))
* Always include plugin info & user data when serializing (introduced in [`v4.0.1`](https://github.com/lavalink-devs/Lavalink/releases/tag/4.0.1))
* Updated oshi to `6.4.11`

## v4.0.1
* Updated Lavaplayer to `2.1.0`
* 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
Expand Down
Expand Up @@ -3,10 +3,7 @@
package dev.arbjerg.lavalink.protocol.v4

import dev.arbjerg.lavalink.protocol.v4.serialization.asPolymorphicDeserializer
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
Expand Down Expand Up @@ -133,6 +130,7 @@ data class PlaylistInfo(
@Serializable
data class Playlist(
val info: PlaylistInfo,
@EncodeDefault
val pluginInfo: JsonObject = JsonObject(emptyMap()),
val tracks: List<Track>
) : LoadResult.Data {
Expand Down
@@ -1,9 +1,8 @@
package dev.arbjerg.lavalink.protocol.v4

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.Serializable
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonNames
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.serializer
import kotlin.jvm.JvmInline

inline fun <reified T> JsonObject.deserialize(): T =
Expand Down Expand Up @@ -31,7 +30,9 @@ data class Player(
data class Track(
val encoded: String,
val info: TrackInfo,
@EncodeDefault
val pluginInfo: JsonObject = JsonObject(emptyMap()),
@EncodeDefault
val userData: JsonObject = JsonObject(emptyMap())
) : LoadResult.Data {

Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
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.8")
library("oshi", "com.github.oshi", "oshi-core").version("6.4.11")
}

fun VersionCatalogBuilder.other() {
Expand Down

0 comments on commit 3a20917

Please sign in to comment.