Skip to content

Commit

Permalink
feat
Browse files Browse the repository at this point in the history
  • Loading branch information
zly2006 committed Nov 14, 2024
1 parent 1505953 commit 2b86c2f
Show file tree
Hide file tree
Showing 23 changed files with 372 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.github.zly2006.xbackup.multi;

import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.WorldChunk;

public interface ChunkHack {
void replaceChunk(ChunkPos pos, WorldChunk chunk);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.zly2006.xbackup.multi;

public interface IChunkHolder {
int x();
int z();

/**
* Entities, block entities, POI
*
* record tick, if tick mismatch, crash
*/
void unloadEntities();
void loadRegion();
void loadEntities();
}
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ class BackupDatabaseService(
}
}

suspend fun getLatestBackup(): Backup = dbQuery {
BackupTable.selectAll().last().toBackup()
suspend fun getLatestBackup(): Backup? = dbQuery {
BackupTable.selectAll().lastOrNull()?.toBackup()
}
}

Expand Down
30 changes: 30 additions & 0 deletions core/src/main/kotlin/com/github/zly2006/xbackup/Commands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,35 @@ object Commands {
1
}
}
literal("status") {
executes {
it.source.send(literalText("X Backup status: " + if (XBackup.isBusy) "Busy" else "OK"))
runBlocking {
val latest = XBackup.service.getLatestBackup()
if (latest != null) {
it.source.send(
CrossVersionText.ClickableText(
literalText("Latest backup: #${latest.id} ${latest.comment}"),
"/xb info ${latest.id}",
literalText("Click to view details")
)
)
if (XBackup.config.backupInterval != 0) {
val next = latest.created + XBackup.config.backupInterval * 1000
it.source.send(
literalText(
"Next backup at: " + SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(next)
)
)
}
}
else {
it.source.send(literalText("No backups yet"))
}
}
1
}
}
literal("restore") {
argument("id", IntegerArgumentType.integer(1)) {
literal("--chunk") {
Expand Down Expand Up @@ -337,6 +366,7 @@ object Commands {
return@ensureNotBusy
}
try {
XBackup.log.info("Preparing to restore...")
it.source.server.prepareRestore("Restoring backup #$id")
if (forceStop) {
XBackup.service.restore(id, path) { !filter(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ interface IOnedriveUtils {
fun initializeGraphForUserAuth(source: ServerCommandSource?, login: Boolean = false)

suspend fun uploadOneDrive(service: BackupDatabaseService, id: Int)
fun downloadBlob(hash: String): InputStream

suspend fun downloadBlob(hash: String): InputStream
}
19 changes: 17 additions & 2 deletions core/src/main/kotlin/com/github/zly2006/xbackup/XBackup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import net.minecraft.util.WorldSavePath
import org.jetbrains.exposed.sql.Database
import org.slf4j.LoggerFactory
import org.sqlite.SQLiteConfig
import org.sqlite.SQLiteConnection
import org.sqlite.SQLiteDataSource
import java.io.File
import kotlin.coroutines.CoroutineContext
import kotlin.io.path.*

Expand All @@ -33,6 +35,10 @@ object XBackup : ModInitializer {

// Restore
var reason = ""
set(value) {
field = value
log.info("Restore reason: $value")
}
var blockPlayerJoin = false
var disableSaving = false
var disableWatchdog = false
Expand Down Expand Up @@ -89,17 +95,26 @@ object XBackup : ModInitializer {
while (true) {
delay(10000)
val backup = service.getLatestBackup()
if ((System.currentTimeMillis() - backup.created) / 1000 > config.backupInterval && !isBusy) {
if (isBusy) continue
if (backup == null || (System.currentTimeMillis() - backup.created) / 1000 > config.backupInterval) {
try {
isBusy = true
server.broadcast(literalText("Running scheduled backup, please wait..."))
val (_, _, backId, totalSize, compressedSize, addedSize, millis) = service.createBackup(
server.getSavePath(WorldSavePath.ROOT).toAbsolutePath(),
"Scheduled backup"
) { true }
val localBackup = File("x_backup.db.back")
localBackup.delete()
try {
(service.database.connector().connection as? SQLiteConnection)?.createStatement()
?.execute("VACUUM INTO '$localBackup';")
} catch (e: Exception) {
log.error("Error backing up database", e)
}
server.broadcast(
literalText(
"Backup #$backId finished, ${sizeToString(totalSize)} " +
"Scheduled backup #$backId finished, ${sizeToString(totalSize)} " +
"(${sizeToString(compressedSize)} after compression) " +
"+${sizeToString(addedSize)} " +
"in ${millis}ms"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class OnedriveUtils : IOnedriveUtils {
XBackup.log.info("Uploaded backup $id to OneDrive")
}

override fun downloadBlob(hash: String): InputStream {
override suspend fun downloadBlob(hash: String): InputStream {
return client.me().drive().root().itemWithPath("X-Backup/blob/${hash}")
.content()
.buildRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class Impl : MultiVersioned {
}
else {
server.playerManager.playerList.toList().forEach {
if (it.pingMilliseconds == 0) {
// fuck carpet
it.networkHandler.disconnect(Text.translatable("multiplayer.disconnect.duplicate_login"))
}
it.networkHandler.disconnect(Text.of(reason))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,21 @@
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.nio.channels.ClosedChannelException;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

@Mixin(StorageIoWorker.class)
public abstract class MixinStorageIoWorker implements RestoreAware {
@Shadow @Final private RegionBasedStorage storage;

@Shadow @Final private Map<ChunkPos, Object> results;
@Shadow @Final private Map<ChunkPos, StorageIoWorker.Result> results;

@Shadow @Final private TaskExecutor<TaskQueue.PrioritizedTask> executor;

@Shadow protected abstract <T> CompletableFuture<T> run(Supplier<Either<T, Exception>> task);
@Shadow protected abstract void write(ChunkPos pos, StorageIoWorker.Result result);

@Shadow protected abstract void writeResult();
@Shadow protected abstract void writeRemainingResults();

@Unique
private boolean restoring = false;
Expand All @@ -52,7 +49,7 @@ public void preRestore() {
if (this.storage == null) return;
restoring = true;
while (executor != null && executor.getQueueSize() > 0) {
executor.run();
executor.queue.poll();
}
this.results.clear();
((RestoreAware) (Object) this.storage).preRestore();
Expand All @@ -62,7 +59,7 @@ public void preRestore() {
public void postRestore() {
if (this.storage == null) return;
while (executor != null && executor.getQueueSize() > 0) {
executor.run();
executor.queue.poll();
}
((RestoreAware) (Object) this.storage).postRestore();
restoring = false;
Expand All @@ -84,25 +81,29 @@ private void run(Supplier<Either<Object, Exception>> task, CallbackInfoReturnabl
* @reason
*/
@Overwrite
private void writeRemainingResults() {
this.executor.send(new TaskQueue.PrioritizedTask(1, () -> {
try {
this.writeResult();
} catch (Throwable e) {
if (restoring) {
// Ignore exceptions during restore
if (e instanceof ClosedChannelException || e.getCause() instanceof ClosedChannelException) {
return;
}
if (e instanceof ConcurrentModificationException) {
return;
}
if (e instanceof NoSuchElementException) {
return;
}
private void writeResult() {
try {
if (!this.results.isEmpty()) {
Iterator<Map.Entry<ChunkPos, StorageIoWorker.Result>> iterator = this.results.entrySet().iterator();
Map.Entry<ChunkPos, StorageIoWorker.Result> entry = iterator.next();
iterator.remove();
this.write(entry.getKey(), entry.getValue());
this.writeRemainingResults();
}
} catch (Throwable e) {
if (restoring) {
// Ignore exceptions during restore
if (e instanceof ClosedChannelException || e.getCause() instanceof ClosedChannelException) {
return;
}
if (e instanceof ConcurrentModificationException) {
return;
}
if (e instanceof NoSuchElementException) {
return;
}
throw e;
}
}));
throw e;
}
}
}
2 changes: 2 additions & 0 deletions xb-1.20.1/src/main/resources/xb120.accesswidener
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ accessible class net/minecraft/server/world/ServerLightingProvider$Stage
accessible class net/minecraft/server/world/ServerChunkManager$MainThreadExecutor
accessible field net/minecraft/util/math/ColumnPos x I
accessible field net/minecraft/util/math/ColumnPos z I
accessible class net/minecraft/world/storage/StorageIoWorker$Result
accessible field net/minecraft/util/thread/TaskExecutor queue Lnet/minecraft/util/thread/TaskQueue;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import net.minecraft.text.MutableText
import net.minecraft.text.Text
import net.minecraft.util.WorldSavePath
import net.minecraft.world.dimension.DimensionType
import org.jetbrains.exposed.sql.Database
import java.nio.file.Path

class Impl : MultiVersioned {
Expand Down Expand Up @@ -82,6 +81,10 @@ class Impl : MultiVersioned {
}
else {
server.playerManager.playerList.toList().forEach {
if (it.networkHandler.latency == 0) {
// fuck carpet
it.networkHandler.disconnect(Text.translatable("multiplayer.disconnect.duplicate_login"))
}
it.networkHandler.disconnect(Text.of(reason))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,21 @@
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.nio.channels.ClosedChannelException;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

@Mixin(StorageIoWorker.class)
public abstract class MixinStorageIoWorker implements RestoreAware {
@Shadow @Final private RegionBasedStorage storage;

@Shadow @Final private Map<ChunkPos, Object> results;
@Shadow @Final private Map<ChunkPos, StorageIoWorker.Result> results;

@Shadow @Final private TaskExecutor<TaskQueue.PrioritizedTask> executor;

@Shadow protected abstract <T> CompletableFuture<T> run(Supplier<Either<T, Exception>> task);
@Shadow protected abstract void write(ChunkPos pos, StorageIoWorker.Result result);

@Shadow protected abstract void writeResult();
@Shadow protected abstract void writeRemainingResults();

@Unique
private boolean restoring = false;
Expand All @@ -52,7 +49,7 @@ public void preRestore() {
if (this.storage == null) return;
restoring = true;
while (executor != null && executor.getQueueSize() > 0) {
executor.run();
executor.queue.poll();
}
this.results.clear();
((RestoreAware) (Object) this.storage).preRestore();
Expand All @@ -62,7 +59,7 @@ public void preRestore() {
public void postRestore() {
if (this.storage == null) return;
while (executor != null && executor.getQueueSize() > 0) {
executor.run();
executor.queue.poll();
}
((RestoreAware) (Object) this.storage).postRestore();
restoring = false;
Expand All @@ -84,25 +81,29 @@ private void run(Supplier<Either<Object, Exception>> task, CallbackInfoReturnabl
* @reason
*/
@Overwrite
private void writeRemainingResults() {
this.executor.send(new TaskQueue.PrioritizedTask(1, () -> {
try {
this.writeResult();
} catch (Throwable e) {
if (restoring) {
// Ignore exceptions during restore
if (e instanceof ClosedChannelException || e.getCause() instanceof ClosedChannelException) {
return;
}
if (e instanceof ConcurrentModificationException) {
return;
}
if (e instanceof NoSuchElementException) {
return;
}
private void writeResult() {
try {
if (!this.results.isEmpty()) {
Iterator<Map.Entry<ChunkPos, StorageIoWorker.Result>> iterator = this.results.entrySet().iterator();
Map.Entry<ChunkPos, StorageIoWorker.Result> entry = iterator.next();
iterator.remove();
this.write(entry.getKey(), entry.getValue());
this.writeRemainingResults();
}
} catch (Throwable e) {
if (restoring) {
// Ignore exceptions during restore
if (e instanceof ClosedChannelException || e.getCause() instanceof ClosedChannelException) {
return;
}
if (e instanceof ConcurrentModificationException) {
return;
}
if (e instanceof NoSuchElementException) {
return;
}
throw e;
}
}));
throw e;
}
}
}
2 changes: 2 additions & 0 deletions xb-1.20/src/main/resources/xb120.accesswidener
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ accessible method net/minecraft/world/ChunkPosDistanceLevelPropagator updateLeve
accessible class net/minecraft/server/world/ChunkTicketManager$TicketDistanceLevelPropagator
accessible class net/minecraft/server/world/ThreadedAnvilChunkStorage$TicketManager
accessible class net/minecraft/server/world/ServerLightingProvider$Stage
accessible class net/minecraft/world/storage/StorageIoWorker$Result
accessible field net/minecraft/util/thread/TaskExecutor queue Lnet/minecraft/util/thread/TaskQueue;
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class Impl : MultiVersioned {
}
else {
server.playerManager.playerList.toList().forEach {
if (it.networkHandler.latency == 0) {
// fuck carpet
it.networkHandler.disconnect(Text.translatable("multiplayer.disconnect.duplicate_login"))
}
it.networkHandler.disconnect(Text.of(reason))
}

Expand Down
Loading

0 comments on commit 2b86c2f

Please sign in to comment.