mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Gradle.................
This commit is contained in:
		
							
								
								
									
										2
									
								
								foundation-core/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								foundation-core/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
dependencies {
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
package cloud.kubelet.foundation.core
 | 
			
		||||
 | 
			
		||||
import cloud.kubelet.foundation.core.command.BackupCommand
 | 
			
		||||
import io.papermc.paper.event.player.ChatEvent
 | 
			
		||||
import net.kyori.adventure.text.Component
 | 
			
		||||
import org.bukkit.command.CommandExecutor
 | 
			
		||||
import org.bukkit.event.EventHandler
 | 
			
		||||
import org.bukkit.event.Listener
 | 
			
		||||
import org.bukkit.plugin.java.JavaPlugin
 | 
			
		||||
 | 
			
		||||
class FoundationCorePlugin : JavaPlugin(), Listener {
 | 
			
		||||
  override fun onEnable() {
 | 
			
		||||
    val dataPath = dataFolder.toPath()
 | 
			
		||||
    val backupPath = dataPath.resolve(BACKUPS_DIRECTORY)
 | 
			
		||||
 | 
			
		||||
    // Create Foundation plugin directories.
 | 
			
		||||
    dataPath.toFile().mkdir()
 | 
			
		||||
    backupPath.toFile().mkdir()
 | 
			
		||||
 | 
			
		||||
    // Register this as an event listener.
 | 
			
		||||
    server.pluginManager.registerEvents(this, this)
 | 
			
		||||
 | 
			
		||||
    // Register commands.
 | 
			
		||||
    registerCommandExecutor("fbackup", BackupCommand(this, backupPath))
 | 
			
		||||
 | 
			
		||||
    val log = slF4JLogger
 | 
			
		||||
    log.info("Features:")
 | 
			
		||||
    Util.printFeatureStatus(log, "Backup: ", BACKUP_ENABLED)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun registerCommandExecutor(name: String, executor: CommandExecutor) {
 | 
			
		||||
    val command = getCommand(name) ?: throw Exception("Failed to get $name command")
 | 
			
		||||
    command.setExecutor(executor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @EventHandler
 | 
			
		||||
  private fun onChatMessage(e: ChatEvent) {
 | 
			
		||||
    e.isCancelled = true
 | 
			
		||||
    val name = e.player.displayName()
 | 
			
		||||
    val component = Component.empty()
 | 
			
		||||
      .append(leftBracket)
 | 
			
		||||
      .append(name)
 | 
			
		||||
      .append(rightBracket)
 | 
			
		||||
      .append(Component.text(' '))
 | 
			
		||||
      .append(e.message())
 | 
			
		||||
    server.sendMessage(component)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private const val BACKUPS_DIRECTORY = "backups"
 | 
			
		||||
 | 
			
		||||
    private val leftBracket: Component = Component.text('[')
 | 
			
		||||
    private val rightBracket: Component = Component.text(']')
 | 
			
		||||
 | 
			
		||||
    const val BACKUP_ENABLED = true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
package cloud.kubelet.foundation.core
 | 
			
		||||
 | 
			
		||||
import net.kyori.adventure.text.format.TextColor
 | 
			
		||||
 | 
			
		||||
object TextColors {
 | 
			
		||||
  val AMARANTH_PINK = TextColor.fromHexString("#F7A8B8")
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
package cloud.kubelet.foundation.core
 | 
			
		||||
 | 
			
		||||
import net.kyori.adventure.text.Component
 | 
			
		||||
import net.kyori.adventure.text.format.TextColor
 | 
			
		||||
import org.slf4j.Logger
 | 
			
		||||
 | 
			
		||||
object Util {
 | 
			
		||||
  private val leftBracket: Component = Component.text('[')
 | 
			
		||||
  private val rightBracket: Component = Component.text(']')
 | 
			
		||||
  private val whitespace: Component = Component.text(' ')
 | 
			
		||||
  private val foundationName: Component = Component.text("Foundation")
 | 
			
		||||
 | 
			
		||||
  fun printFeatureStatus(logger: Logger, feature: String?, state: Boolean) {
 | 
			
		||||
    logger.info("{}: {}", feature, if (state) "Enabled" else "Disabled")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun formatSystemMessage(message: String?): Component {
 | 
			
		||||
    return formatSystemMessage(TextColors.AMARANTH_PINK, message)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun formatSystemMessage(prefixColor: TextColor?, message: String?): Component {
 | 
			
		||||
    return leftBracket
 | 
			
		||||
      .append(foundationName.color(prefixColor))
 | 
			
		||||
      .append(rightBracket)
 | 
			
		||||
      .append(whitespace)
 | 
			
		||||
      .append(Component.text(message!!))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,143 @@
 | 
			
		||||
package cloud.kubelet.foundation.core.command
 | 
			
		||||
 | 
			
		||||
import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
			
		||||
import cloud.kubelet.foundation.core.Util
 | 
			
		||||
import net.kyori.adventure.text.Component
 | 
			
		||||
import net.kyori.adventure.text.format.TextColor
 | 
			
		||||
import org.bukkit.Server
 | 
			
		||||
import org.bukkit.command.Command
 | 
			
		||||
import org.bukkit.command.CommandExecutor
 | 
			
		||||
import org.bukkit.command.CommandSender
 | 
			
		||||
import java.io.BufferedOutputStream
 | 
			
		||||
import java.io.FileInputStream
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import java.nio.file.Path
 | 
			
		||||
import java.time.Instant
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean
 | 
			
		||||
import java.util.zip.ZipEntry
 | 
			
		||||
import java.util.zip.ZipOutputStream
 | 
			
		||||
 | 
			
		||||
class BackupCommand(
 | 
			
		||||
  private val plugin: FoundationCorePlugin,
 | 
			
		||||
  private val backupPath: Path
 | 
			
		||||
) : CommandExecutor {
 | 
			
		||||
  override fun onCommand(
 | 
			
		||||
    sender: CommandSender, command: Command, label: String, args: Array<String>
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
    if (!FoundationCorePlugin.BACKUP_ENABLED) {
 | 
			
		||||
      sender.sendMessage(
 | 
			
		||||
        Component
 | 
			
		||||
          .text("Backup is not enabled.")
 | 
			
		||||
          .color(TextColor.fromHexString("#FF0000"))
 | 
			
		||||
      )
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (RUNNING.get()) {
 | 
			
		||||
      sender.sendMessage(
 | 
			
		||||
        Component
 | 
			
		||||
          .text("Backup is already running.")
 | 
			
		||||
          .color(TextColor.fromHexString("#FF0000"))
 | 
			
		||||
      )
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      val server = sender.server
 | 
			
		||||
      server.scheduler.runTaskAsynchronously(plugin, Runnable {
 | 
			
		||||
        runBackup(server)
 | 
			
		||||
      })
 | 
			
		||||
    } catch (e: Exception) {
 | 
			
		||||
      sender.sendMessage(String.format("Failed to backup: %s", e.message))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun runBackup(server: Server) {
 | 
			
		||||
    RUNNING.set(true)
 | 
			
		||||
 | 
			
		||||
    server.sendMessage(Util.formatSystemMessage("Backup started."))
 | 
			
		||||
 | 
			
		||||
    val backupFile =
 | 
			
		||||
      backupPath.resolve(String.format("backup-%s.zip", Instant.now().toString())).toFile()
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      FileOutputStream(backupFile).use { zipFileStream ->
 | 
			
		||||
        ZipOutputStream(BufferedOutputStream(zipFileStream)).use { zipStream ->
 | 
			
		||||
          backupPlugins(server, zipStream)
 | 
			
		||||
          backupWorlds(server, zipStream)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      RUNNING.set(false)
 | 
			
		||||
      server.sendMessage(Util.formatSystemMessage("Backup finished."))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun backupPlugins(server: Server, zipStream: ZipOutputStream) {
 | 
			
		||||
    try {
 | 
			
		||||
      addDirectoryToZip(zipStream, server.pluginsFolder.toPath())
 | 
			
		||||
    } catch (e: IOException) {
 | 
			
		||||
      // TODO: Add error handling.
 | 
			
		||||
      e.printStackTrace()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun backupWorlds(server: Server, zipStream: ZipOutputStream) {
 | 
			
		||||
    val worlds = server.worlds
 | 
			
		||||
    for (world in worlds) {
 | 
			
		||||
      val worldPath = world.worldFolder.toPath()
 | 
			
		||||
 | 
			
		||||
      // Save the world.
 | 
			
		||||
      server.scheduler.runTask(plugin, Runnable {
 | 
			
		||||
        world.save()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      // Disable auto saving to prevent any world corruption while creating a ZIP.
 | 
			
		||||
      world.isAutoSave = false
 | 
			
		||||
      try {
 | 
			
		||||
        addDirectoryToZip(zipStream, worldPath)
 | 
			
		||||
      } catch (e: IOException) {
 | 
			
		||||
        // TODO: Add error handling.
 | 
			
		||||
        e.printStackTrace()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Re-enable auto saving for this world.
 | 
			
		||||
      world.isAutoSave = true
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun addDirectoryToZip(zipStream: ZipOutputStream, directoryPath: Path) {
 | 
			
		||||
    val paths = Files.walk(directoryPath)
 | 
			
		||||
      .filter { path: Path? -> Files.isRegularFile(path) }
 | 
			
		||||
      .toList()
 | 
			
		||||
    val buffer = ByteArray(1024)
 | 
			
		||||
    val backupsPath = backupPath.toRealPath()
 | 
			
		||||
 | 
			
		||||
    for (path in paths) {
 | 
			
		||||
      val realPath = path.toRealPath()
 | 
			
		||||
 | 
			
		||||
      if (realPath.startsWith(backupsPath)) {
 | 
			
		||||
        plugin.slF4JLogger.info("Skipping file for backup: {}", realPath)
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      FileInputStream(path.toFile()).use { fileStream ->
 | 
			
		||||
        val entry = ZipEntry(path.toString())
 | 
			
		||||
        zipStream.putNextEntry(entry)
 | 
			
		||||
 | 
			
		||||
        var n: Int
 | 
			
		||||
        while (fileStream.read(buffer).also { n = it } > -1) {
 | 
			
		||||
          zipStream.write(buffer, 0, n)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val RUNNING = AtomicBoolean()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								foundation-core/src/main/resources/plugin.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								foundation-core/src/main/resources/plugin.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
name: Foundation
 | 
			
		||||
version: '${version}'
 | 
			
		||||
main: cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
			
		||||
api-version: 1.18
 | 
			
		||||
prefix: Foundation
 | 
			
		||||
load: STARTUP
 | 
			
		||||
authors:
 | 
			
		||||
  - kubelet
 | 
			
		||||
commands:
 | 
			
		||||
  fbackup:
 | 
			
		||||
    description: Foundation Backup
 | 
			
		||||
    usage: /fbackup
 | 
			
		||||
    permission: foundation.backup
 | 
			
		||||
		Reference in New Issue
	
	Block a user