Skip to content

Commit

Permalink
Ensure mod works correctly when user is not logged into the server
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasjackson committed Oct 16, 2023
1 parent 5dd84c0 commit d01929f
Show file tree
Hide file tree
Showing 9 changed files with 585 additions and 172 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import com.github.hashicraft.microservices.blocks.WebserverBlock;
import com.github.hashicraft.microservices.blocks.WebserverBlockEntity;
import com.github.hashicraft.stateful.blocks.EntityServerState;

import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.client.MinecraftClient;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
Expand All @@ -22,6 +24,7 @@
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.hashicraft.microservices.blocks;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
Expand Down Expand Up @@ -60,7 +61,9 @@ public class DatabaseBlock extends StatefulBlock {

// keeps a map of registered database blocks so we can check for updates
// on server tick
private static HashMap<BlockPos, Boolean> DATABASES = new HashMap<BlockPos, Boolean>();
private static Databases DATABASES = new Databases();
private static boolean initialized = false;

// background thread service
private static ExecutorService service = new ThreadPoolExecutor(4, 1000, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
Expand All @@ -79,34 +82,41 @@ public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEnt
if (world.isClient()) {
DatabaseBlockClicked.EVENT.invoker().interact(blockEntity, () -> {
blockEntity.markForUpdate();

PacketByteBuf buf = PacketByteBufs.create();
buf.writeBlockPos(pos);

// notify that the server has been reconfigured
// we need to wait until the block state has synced so wait here
service.submit(() -> {
try {
Thread.sleep(1000);
ClientPlayNetworking.send(Messages.DATABASE_BLOCK_UPDATED, buf);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
});
}

return ActionResult.SUCCESS;
}

@Override
public void onBroken(WorldAccess world, BlockPos pos, BlockState state) {
if (world.isClient()) {
PacketByteBuf buf = PacketByteBufs.create();
buf.writeBlockPos(pos);
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
LOGGER.info("createBlockEntity {} {}", pos, Client.isClient());

ClientPlayNetworking.send(Messages.DATABASE_BLOCK_REMOVE, buf);
}
return new DatabaseBlockEntity(pos, state, this);
}

@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
LOGGER.info("createBlockEntity {} {}", pos, Client.isClient());

if (Client.isClient()) {
public void onBroken(WorldAccess world, BlockPos pos, BlockState state) {
if (world.isClient()) {
PacketByteBuf buf = PacketByteBufs.create();
buf.writeBlockPos(pos);

ClientPlayNetworking.send(Messages.DATABASE_BLOCK_REGISTER, buf);
ClientPlayNetworking.send(Messages.DATABASE_BLOCK_REMOVE, buf);
}

return new DatabaseBlockEntity(pos, state, this);
}

public boolean emitsRedstonePower(BlockState state) {
Expand Down Expand Up @@ -153,96 +163,121 @@ public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Ran

// register this class to listen to server play networkiing events
public static void registerEvents() {
ServerPlayNetworking.registerGlobalReceiver(Messages.DATABASE_BLOCK_REGISTER,
ServerPlayNetworking.registerGlobalReceiver(Messages.DATABASE_BLOCK_REMOVE,
(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf,
PacketSender responseSender) -> {
BlockPos pos = buf.readBlockPos();
handleBlockRegister(pos);
handleBlockRemove(pos);
});

ServerPlayNetworking.registerGlobalReceiver(Messages.DATABASE_BLOCK_REMOVE,
ServerPlayNetworking.registerGlobalReceiver(Messages.DATABASE_BLOCK_UPDATED,
(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf,
PacketSender responseSender) -> {
BlockPos pos = buf.readBlockPos();
handleBlockRemove(pos);

// this needs to happen on the server thread or the block entity will not exist
server.execute(() -> {
handleBlockUpdate(pos, server.getOverworld());
});
});

ServerTickEvents.START_SERVER_TICK.register((MinecraftServer server) -> {
server.execute(() -> {
if (!initialized) {
initialized = true;
DATABASES = Databases.loadFromConfig();
}

handleServerTick(server.getOverworld());
});
});

}

public static void handleBlockRegister(BlockPos pos) {
MicroservicesMod.LOGGER.info("Received register_database message {}", pos);
if (!DATABASES.containsKey(pos)) {
DATABASES.put(pos, false);
public static void handleBlockUpdate(BlockPos pos, ServerWorld world) {
DatabaseBlockEntity blockEntity = (DatabaseBlockEntity) world.getBlockEntity(pos);

MicroservicesMod.LOGGER.info("Received update_database message {}", pos);

DatabaseContext context = new DatabaseContext();
context.setAddress(blockEntity.getDbAddress());
context.setUsername(blockEntity.getUsername());
context.setPassword(blockEntity.getPassword());
context.setDatabase(blockEntity.getDatabase());
context.setSQL(blockEntity.getSQLStatement());

DATABASES.add(pos, context);

try {
DATABASES.writeToConfig();
} catch (IOException e) {
MicroservicesMod.LOGGER.info("Unable to write config {}", e.getMessage());
}
}

public static void handleBlockRemove(BlockPos pos) {
MicroservicesMod.LOGGER.info("Received remove_database message {}", pos);
if (DATABASES.containsKey(pos)) {
if (DATABASES.exists(pos)) {
DATABASES.remove(pos);
}
}

public static void handleServerTick(ServerWorld world) {
// check if the block is powered
for (Entry<BlockPos, Boolean> entry : DATABASES.entrySet()) {
for (Entry<BlockPos, DatabaseContext> entry : DATABASES.entrySet()) {
BlockPos pos = entry.getKey();
DatabaseBlockEntity blockEntity = (DatabaseBlockEntity) world.getBlockEntity(pos);
int power = world.getReceivedRedstonePower(pos);

if (power > 0 && !entry.getValue()) {
DatabaseContext db = entry.getValue();

if (power > 0 && !db.getActive()) {
MicroservicesMod.LOGGER.info("block {} power on {}", entry.getKey(), power);
DATABASES.put(pos, true);

executeDBQuery(world, blockEntity);
// update active before executing the query as the query may take multiple
// ticks
db.setActive(true);
DATABASES.add(pos, db);

executeDBQuery(world, pos, db);
}

// when power is off reset the block
if (power == 0 && entry.getValue()) {
DATABASES.put(pos, false);
if (power == 0 && db.getActive()) {
db.setActive(false);
DATABASES.add(pos, db);
}
}
}

private static void executeDBQuery(ServerWorld world, DatabaseBlockEntity blockEntity) {
private static void executeDBQuery(ServerWorld world, BlockPos pos, DatabaseContext ctx) {
service.submit(() -> {
try {
LOGGER.info("Executing SQL statement {}", blockEntity.getSQLStatement());
LOGGER.info("Executing SQL statement {}", ctx.getSQL());

String result = executeSQLStatement(ctx);
// blockEntity.result = result;

String result = executeSQLStatement(blockEntity);
blockEntity.result = result;
// everything is ok emit redstone power
BlockState state = world.getBlockState(pos);
state = state.with(DatabaseBlock.POWERED, true);
world.setBlockState(pos, state, Block.NOTIFY_ALL);

BlockState state = blockEntity.getCachedState().with(DatabaseBlock.POWERED, true);
world.setBlockState(blockEntity.getPos(), state, Block.NOTIFY_ALL);
// schedule a block tick to update the block so it can disable
world.scheduleBlockTick(pos, MicroservicesMod.DATABASE_BLOCK, 40, TickPriority.NORMAL);
} catch (SQLException e) {
LOGGER.error("Error executing SQL statement {}", e);
blockEntity.result = e.getMessage();
// blockEntity.result = e.getMessage();
}

// update the block
blockEntity.setPropertiesToState();
blockEntity.serverStateUpdated(blockEntity.serverState);

// schedule a block tick to update the block so it can disable
world.scheduleBlockTick(blockEntity.getPos(), MicroservicesMod.DATABASE_BLOCK, 40, TickPriority.NORMAL);
});
}

private static String executeSQLStatement(DatabaseBlockEntity blockEntity) throws SQLException {
private static String executeSQLStatement(DatabaseContext ctx) throws SQLException {
// get the database details from the block entity gui
// we will substitute any environment variables that may be embedded in here
String address = Interpolate.getValue(blockEntity.getDbAddress());
String username = Interpolate.getValue(blockEntity.getUsername());
String password = Interpolate.getValue(blockEntity.getPassword());
String database = Interpolate.getValue(blockEntity.getDatabase());
String sql = Interpolate.getValue(blockEntity.getSQLStatement());
String address = Interpolate.getValue(ctx.getAddress());
String username = Interpolate.getValue(ctx.getUsername());
String password = Interpolate.getValue(ctx.getPassword());
String database = Interpolate.getValue(ctx.getDatabase());
String sql = Interpolate.getValue(ctx.getSQL());

// execute the SQL statement
Connection conn = DriverManager.getConnection(String.format("jdbc:postgresql://%s/%s", address, database),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.github.hashicraft.microservices.blocks;

import com.google.gson.annotations.Expose;

public class DatabaseContext {
private boolean active;

@Expose
private String address;

@Expose
private String username;

@Expose
private String password;

@Expose
private String database;

@Expose
private String sql;

// public getters and setters
public boolean getActive() {
return this.active;
}

public void setActive(boolean active) {
this.active = active;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getUsername() {
return this.username;
}

public void setUsername(String user) {
this.username = user;
}

public String getPassword() {
return this.password;
}

public void setPassword(String pass) {
this.password = pass;
}

public String getDatabase() {
return this.database;
}

public void setDatabase(String db) {
this.database = db;
}

public String getSQL() {
return this.sql;
}

public void setSQL(String sql) {
this.sql = sql;
}

public DatabaseContext() {
}
}
Loading

0 comments on commit d01929f

Please sign in to comment.