diff --git a/build.gradle.kts b/build.gradle.kts index 076413c..86f7565 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import de.chojo.Repo import io.papermc.hangarpublishplugin.model.Platforms import net.minecrell.pluginyml.bukkit.BukkitPluginDescription import net.minecrell.pluginyml.paper.PaperPluginDescription +import xyz.jpenilla.runpaper.task.RunServer plugins { kotlin("jvm") version "1.9.22" @@ -47,6 +48,13 @@ tasks { runServer { minecraftVersion("1.20.4") } + register("runServer2") { + group = "run paper" + minecraftVersion("1.20.4") + runDirectory.set(File("run-2")) + pluginJars(*rootProject.getTasksByName("shadowJar", false).map { (it as Jar).archiveFile } + .toTypedArray()) + } shadowJar { relocate("org.bstats", "net.onelitefeather.clipboardconnect.org.bstats") } diff --git a/docker/compose.yml b/docker/compose.yml new file mode 100644 index 0000000..866f338 --- /dev/null +++ b/docker/compose.yml @@ -0,0 +1,8 @@ +version: '3' +services: + keydb: + image: eqalpha/keydb + container_name: clipboard-connect-keydb + restart: unless-stopped + ports: + - "127.0.0.1:6379:6379" \ No newline at end of file diff --git a/src/main/kotlin/net/onelitefeather/clipboardconnect/ClipboardConnect.kt b/src/main/kotlin/net/onelitefeather/clipboardconnect/ClipboardConnect.kt index 4180b0b..3dd298a 100644 --- a/src/main/kotlin/net/onelitefeather/clipboardconnect/ClipboardConnect.kt +++ b/src/main/kotlin/net/onelitefeather/clipboardconnect/ClipboardConnect.kt @@ -23,6 +23,7 @@ import net.onelitefeather.clipboardconnect.commands.LoadCommand import net.onelitefeather.clipboardconnect.commands.SaveCommand import net.onelitefeather.clipboardconnect.commands.SetupCommand import net.onelitefeather.clipboardconnect.conversation.ConversationContext +import net.onelitefeather.clipboardconnect.listener.PlayerJoinListener import net.onelitefeather.clipboardconnect.listener.PlayerQuitListener import net.onelitefeather.clipboardconnect.listener.SetupListener import net.onelitefeather.clipboardconnect.services.SetupService @@ -138,6 +139,7 @@ class ClipboardConnect : JavaPlugin() { val redisFile = Path(dataFolder.toString(), "redis.yml") if (Files.exists(redisFile)) { server.pluginManager.registerEvents(injector.instance(PlayerQuitListener::class.java), this) + server.pluginManager.registerEvents(injector.instance(PlayerJoinListener::class.java), this) injector.instance(AnnotationParser::class.java).parse(injector.instance(SaveCommand::class.java)) injector.instance(AnnotationParser::class.java).parse(injector.instance(LoadCommand::class.java)) } else { diff --git a/src/main/kotlin/net/onelitefeather/clipboardconnect/listener/PlayerJoinListener.kt b/src/main/kotlin/net/onelitefeather/clipboardconnect/listener/PlayerJoinListener.kt new file mode 100644 index 0000000..7513119 --- /dev/null +++ b/src/main/kotlin/net/onelitefeather/clipboardconnect/listener/PlayerJoinListener.kt @@ -0,0 +1,67 @@ +package net.onelitefeather.clipboardconnect.listener + +import com.sk89q.worldedit.bukkit.BukkitAdapter +import jakarta.inject.Inject +import jakarta.inject.Named +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import net.onelitefeather.clipboardconnect.ClipboardConnect +import net.onelitefeather.clipboardconnect.services.SyncService +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerJoinEvent + +/** + * Listener class that handles the 'PlayerJoinEvent' when a player joins the server. + * + * @property syncService The SyncService instance used for syncing player data. + * @property plugin The ClipboardConnect plugin instance. + */ +class PlayerJoinListener +@Inject constructor( + private val syncService: SyncService, + private val plugin: ClipboardConnect, + @Named("prefix") private val prefix: Component, +) : Listener { + + /** + * Handles the 'PlayerQuitEvent' when a player quits the server. + * + * @param event The PlayerQuitEvent object. + */ + @EventHandler(priority = EventPriority.LOWEST) + fun playerJoin(event: PlayerJoinEvent) { + val player = event.player + plugin.componentLogger.debug( + MiniMessage.miniMessage() + .deserialize(" is logging in", Placeholder.component("player", player.name())) + ) + if (!player.hasPermission("clipboardconnect.service.load")) return + plugin.componentLogger.debug( + MiniMessage.miniMessage() + .deserialize(" permission check was successful", Placeholder.component("player", player.name())) + ) + val worldEditPlayer = BukkitAdapter.adapt(player) + plugin.componentLogger.debug( + MiniMessage.miniMessage() + .deserialize("Try to pull clipboard for ", Placeholder.component("player", player.name())) + ) + if (syncService.syncPull(worldEditPlayer)) { + plugin.componentLogger.debug( + MiniMessage.miniMessage().deserialize( + "Clipboard from was successful written into actor", + Placeholder.unparsed("actor", worldEditPlayer.name) + ) + ) + player.sendMessage( + MiniMessage.miniMessage().deserialize( + "Clipboard was successfully transfered to this server", + Placeholder.component("prefix", prefix) + ) + ) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/onelitefeather/clipboardconnect/services/SyncService.kt b/src/main/kotlin/net/onelitefeather/clipboardconnect/services/SyncService.kt index 0c66a48..c269e89 100644 --- a/src/main/kotlin/net/onelitefeather/clipboardconnect/services/SyncService.kt +++ b/src/main/kotlin/net/onelitefeather/clipboardconnect/services/SyncService.kt @@ -6,6 +6,7 @@ import com.github.luben.zstd.ZstdOutputStream import com.sk89q.worldedit.EmptyClipboardException import com.sk89q.worldedit.WorldEdit import com.sk89q.worldedit.bukkit.BukkitAdapter +import com.sk89q.worldedit.bukkit.BukkitPlayer import com.sk89q.worldedit.extension.platform.Actor import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat import com.sk89q.worldedit.session.ClipboardHolder @@ -46,52 +47,45 @@ import kotlin.time.toJavaDuration * @property duration The duration for which clipboards are stored in Redis. */ @Singleton -class SyncService @Inject constructor(private val config: FileConfiguration, private val plugin: ClipboardConnect, @Named("prefix") private val prefix: Component, @Named("fawe") private val faweSupport: Boolean) { +class SyncService @Inject constructor( + private val config: FileConfiguration, + private val plugin: ClipboardConnect, + @Named("prefix") private val prefix: Component, + @Named("fawe") private val faweSupport: Boolean +) { private val logger = ComponentLogger.logger(javaClass) private val redisson: RedissonClient = buildRedis() private val topicName = "ClipboardConnect" - private val topicNameUUID = "ClipboardConnect-UUID" private val serverName = config.getString("servername") ?: "Unknown" private val codec = TypedJsonJacksonCodec(ClipboardMessage::class.java) - private val messageRQueue = redisson.getQueue(topicName,codec) - private val messageUUIDRQueue = redisson.getQueue(topicNameUUID) + private val waitForUpload = redisson.getList(topicName) + private val pubSub = redisson.getTopic(topicName, codec) private val duration: Duration = loadDuration() private val pushMarker = MarkerFactory.getMarker("Sync Push") private val pullMarker = MarkerFactory.getMarker("Sync Pull") init { - plugin.server.scheduler.runTaskTimerAsynchronously(plugin, this::pollUpdates, 0, 20) - messageRQueue.expire(Duration.parse("5m").toJavaDuration()) - messageUUIDRQueue.expire(Duration.parse("5m").toJavaDuration()) + pubSub.addListener(ClipboardMessage::class.java, this::onPollMessage) } - private fun pollUpdates() { - try { - val message = messageRQueue.peek() - logger.debug(MiniMessage.miniMessage().deserialize("Pull message queue")) - if (message != null) { - logger.debug(MiniMessage.miniMessage().deserialize("Found message")) - val player = Bukkit.getPlayer(message.userId()) ?: return - if (messageUUIDRQueue.contains(player.uniqueId.toString())) { - return - } - logger.debug(MiniMessage.miniMessage().deserialize("Found player")) - if (!player.hasPermission("clipboardconnect.service.load")) return - logger.debug(MiniMessage.miniMessage().deserialize("Player permission check ok")) - messageUUIDRQueue.add(player.uniqueId.toString()) - if (syncPull(BukkitAdapter.adapt(player))) { - logger.debug(MiniMessage.miniMessage().deserialize("Pull was successful")) - player.sendMessage(MiniMessage.miniMessage().deserialize("Clipboard from was successfully transfered to this server", Placeholder.unparsed("server", message.fromServer()), Placeholder.component("prefix",prefix))) - logger.debug(MiniMessage.miniMessage().deserialize("Remove message from queue")) - messageRQueue.remove(message) - messageUUIDRQueue.remove(player.uniqueId.toString()) - } + private fun onPollMessage(channel: CharSequence, clipboardMessage: ClipboardMessage) { + if (channel == topicName) { + val message = clipboardMessage; + val player = Bukkit.getPlayer(message.userId()) ?: return + logger.debug(MiniMessage.miniMessage().deserialize("Found player")) + if (!player.hasPermission("clipboardconnect.service.load")) return + if (syncPull(BukkitAdapter.adapt(player))) { + logger.debug(MiniMessage.miniMessage().deserialize("Pull was successful")) + player.sendMessage( + MiniMessage.miniMessage().deserialize( + "Clipboard from was successfully transfered to this server", + Placeholder.unparsed("server", message.fromServer()), + Placeholder.component("prefix", prefix) + ) + ) } - } catch (e: Exception) { - e.printStackTrace() - Bukkit.getPluginManager().disablePlugin(plugin) } } @@ -110,7 +104,10 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri logger.error(MiniMessage.miniMessage().deserialize("Failed to initialize a redis connection"), e) plugin.server.pluginManager.disablePlugin(plugin) } catch (e: Exception) { - logger.error(MiniMessage.miniMessage().deserialize("Something went wrong while trying to initialize a redis connection"), e) + logger.error( + MiniMessage.miniMessage() + .deserialize("Something went wrong while trying to initialize a redis connection"), e + ) plugin.server.pluginManager.disablePlugin(plugin) } } @@ -125,11 +122,21 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri * @return True if the synchronization and save were successful, false otherwise. */ @Suppress("DEPRECATION") - fun syncPush(actor: Actor, automatic: Boolean = true): Boolean { - logger.debug(pushMarker, MiniMessage.miniMessage().deserialize("Open actor stream from redis", Placeholder.unparsed("player", actor.name))) + fun syncPush(actor: Actor, automatic: Boolean = true): Boolean { + logger.debug( + pushMarker, + MiniMessage.miniMessage() + .deserialize("Open actor stream from redis", Placeholder.unparsed("player", actor.name)) + ) val stream = redisson.getBinaryStream(actor.uniqueId.toString()) if (stream.isExists) { - plugin.componentLogger.debug(pushMarker, MiniMessage.miniMessage().deserialize("Delete old actor stream from redis", Placeholder.unparsed("player", actor.name))) + plugin.componentLogger.debug( + pushMarker, + MiniMessage.miniMessage().deserialize( + "Delete old actor stream from redis", + Placeholder.unparsed("player", actor.name) + ) + ) stream.delete() } try { @@ -139,7 +146,7 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri MiniMessage.miniMessage() .deserialize("Find actor session", Placeholder.unparsed("player", actor.name)) ) - + waitForUpload.add(actor.uniqueId.toString()) val pushAsync = Runnable { val clipboardHolder = session.clipboard ?: return@Runnable logger.debug( @@ -159,7 +166,7 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri return@Runnable } - val format = if(faweSupport) { + val format = if (faweSupport) { BuiltInClipboardFormat.FAST } else { BuiltInClipboardFormat.SPONGE_SCHEMATIC @@ -184,10 +191,12 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri } } catch (e: Exception) { - logger.error(MiniMessage.miniMessage().deserialize( - "Something went wrong to write clipboard", - Placeholder.unparsed("actor", actor.name) - ), e) + logger.error( + MiniMessage.miniMessage().deserialize( + "Something went wrong to write clipboard", + Placeholder.unparsed("actor", actor.name) + ), e + ) } logger.debug( pushMarker, @@ -206,7 +215,8 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri Placeholder.unparsed("player", actor.name) ) ) - messageRQueue.add(ClipboardMessage(actor.uniqueId, serverName)) + pubSub.publish(ClipboardMessage(actor.uniqueId, serverName)) + waitForUpload.remove(actor.uniqueId.toString()) } } if (faweSupport) { @@ -229,51 +239,77 @@ class SyncService @Inject constructor(private val config: FileConfiguration, pri */ @Suppress("Deprecation") fun syncPull(actor: Actor): Boolean { - logger.debug(pullMarker, MiniMessage.miniMessage().deserialize("Open actor stream from redis to pull", Placeholder.unparsed("player", actor.name))) - val stream = redisson.getBinaryStream(actor.uniqueId.toString()) - if (stream.isExists) { - logger.debug(pullMarker, + if (waitForUpload.contains(actor.uniqueId.toString())) { + if (actor is BukkitPlayer) { + actor.player.sendMessage( MiniMessage.miniMessage().deserialize( - "Clipboard from was successful downloaded", - Placeholder.unparsed("actor", actor.name) + "The clipboard is still being transferred. Please wait until the clipboard is released.", + Placeholder.component("prefix", prefix) ) ) - logger.debug(pullMarker, MiniMessage.miniMessage().deserialize("Find actor session", Placeholder.unparsed("player", actor.name))) - val session = WorldEdit.getInstance().sessionManager.get(actor) - logger.debug(pullMarker, MiniMessage.miniMessage().deserialize("Open actor reader", Placeholder.unparsed("player", actor.name))) - val format = if(faweSupport) { - BuiltInClipboardFormat.FAST - } else { - BuiltInClipboardFormat.SPONGE_SCHEMATIC - } - try { - format.getReader(ZstdInputStream(stream.inputStream)).use { - logger.debug( - pullMarker, - MiniMessage.miniMessage().deserialize( - "Clipboard from was successful written into a clipboard holder", - Placeholder.unparsed("actor", actor.name) - ) + } + return false; + } + logger.debug( + pullMarker, + MiniMessage.miniMessage() + .deserialize("Open actor stream from redis to pull", Placeholder.unparsed("player", actor.name)) + ) + val stream = redisson.getBinaryStream(actor.uniqueId.toString()) + if (stream.isExists) { + logger.debug( + pullMarker, + MiniMessage.miniMessage().deserialize( + "Clipboard from was successful downloaded", + Placeholder.unparsed("actor", actor.name) + ) + ) + logger.debug( + pullMarker, + MiniMessage.miniMessage() + .deserialize("Find actor session", Placeholder.unparsed("player", actor.name)) + ) + val session = WorldEdit.getInstance().sessionManager.get(actor) + logger.debug( + pullMarker, + MiniMessage.miniMessage() + .deserialize("Open actor reader", Placeholder.unparsed("player", actor.name)) + ) + val format = if (faweSupport) { + BuiltInClipboardFormat.FAST + } else { + BuiltInClipboardFormat.SPONGE_SCHEMATIC + } + try { + format.getReader(ZstdInputStream(stream.inputStream)).use { + logger.debug( + pullMarker, + MiniMessage.miniMessage().deserialize( + "Clipboard from was successful written into a clipboard holder", + Placeholder.unparsed("actor", actor.name) ) - logger.debug( - pullMarker, - MiniMessage.miniMessage().deserialize( - "Create clipboard holder for actor", - Placeholder.unparsed("player", actor.name) - ) + ) + logger.debug( + pullMarker, + MiniMessage.miniMessage().deserialize( + "Create clipboard holder for actor", + Placeholder.unparsed("player", actor.name) ) - session.clipboard = ClipboardHolder(it.read()) - } - return true - } catch (e: Exception) { - logger.error(MiniMessage.miniMessage().deserialize( + ) + session.clipboard = ClipboardHolder(it.read()) + } + return true + } catch (e: Exception) { + logger.error( + MiniMessage.miniMessage().deserialize( "Something went wrong to load clipboard", Placeholder.unparsed("actor", actor.name) - ), e) - } - + ), e + ) } - return false + + } + return false } private fun loadDuration(): Duration {