From 9649f8ab7d6c1e77937b094497ab46141e07f2d3 Mon Sep 17 00:00:00 2001 From: Nicolas BARBOTIN Date: Thu, 8 Feb 2018 19:28:00 +0100 Subject: [PATCH] + Server Block [WIP] --- src/main/java/net/montoyo/wd/SharedProxy.java | 6 +- src/main/java/net/montoyo/wd/WebDisplays.java | 2 + .../net/montoyo/wd/client/ClientProxy.java | 7 +- .../montoyo/wd/client/gui/CommandHandler.java | 18 + .../net/montoyo/wd/client/gui/GuiServer.java | 346 ++++++++++++++++++ .../wd/client/gui/controls/CheckBox.java | 3 + .../java/net/montoyo/wd/data/GuiData.java | 1 + .../java/net/montoyo/wd/data/ServerData.java | 36 ++ .../montoyo/wd/entity/TileEntityServer.java | 8 + .../net/montoyo/wd/item/ItemPeripheral.java | 3 + .../net/montoyo/wd/miniserv/PacketID.java | 4 +- .../montoyo/wd/miniserv/client/Client.java | 33 +- .../wd/miniserv/client/ClientTask.java | 39 +- .../wd/miniserv/client/ClientTaskGetFile.java | 4 +- .../client/ClientTaskGetFileList.java | 44 +++ .../miniserv/client/ClientTaskGetQuota.java | 44 +++ .../miniserv/client/ClientTaskUploadFile.java | 2 +- .../wd/miniserv/server/ServerClient.java | 38 ++ .../wd/net/client/CMessageMiniservKey.java | 2 +- .../java/net/montoyo/wd/utilities/Util.java | 15 + .../assets/webdisplays/lang/en_us.lang | 18 + .../resources/assets/webdisplays/sounds.json | 6 + .../assets/webdisplays/sounds/server.ogg | Bin 0 -> 37348 bytes .../webdisplays/textures/gui/server_bg.png | Bin 0 -> 817 bytes .../webdisplays/textures/gui/server_fg.png | Bin 0 -> 1314 bytes 25 files changed, 667 insertions(+), 12 deletions(-) create mode 100644 src/main/java/net/montoyo/wd/client/gui/CommandHandler.java create mode 100644 src/main/java/net/montoyo/wd/client/gui/GuiServer.java create mode 100644 src/main/java/net/montoyo/wd/data/ServerData.java create mode 100644 src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFileList.java create mode 100644 src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetQuota.java create mode 100644 src/main/resources/assets/webdisplays/sounds/server.ogg create mode 100644 src/main/resources/assets/webdisplays/textures/gui/server_bg.png create mode 100644 src/main/resources/assets/webdisplays/textures/gui/server_fg.png diff --git a/src/main/java/net/montoyo/wd/SharedProxy.java b/src/main/java/net/montoyo/wd/SharedProxy.java index 967b5cc..401f5e6 100644 --- a/src/main/java/net/montoyo/wd/SharedProxy.java +++ b/src/main/java/net/montoyo/wd/SharedProxy.java @@ -91,7 +91,11 @@ public class SharedProxy { public void setMiniservClientPort(int port) { } - public void startMiniServClient() { + public void startMiniservClient() { + } + + public boolean isMiniservDisabled() { + return false; } } diff --git a/src/main/java/net/montoyo/wd/WebDisplays.java b/src/main/java/net/montoyo/wd/WebDisplays.java index 536bc8a..2e218fc 100644 --- a/src/main/java/net/montoyo/wd/WebDisplays.java +++ b/src/main/java/net/montoyo/wd/WebDisplays.java @@ -82,6 +82,7 @@ public class WebDisplays { public SoundEvent soundUpgradeAdd; public SoundEvent soundUpgradeDel; public SoundEvent soundScreenCfg; + public SoundEvent soundServer; //Criterions public Criterion criterionPadBreak; @@ -176,6 +177,7 @@ public class WebDisplays { soundUpgradeAdd = registerSound(ev, "upgradeAdd"); soundUpgradeDel = registerSound(ev, "upgradeDel"); soundScreenCfg = registerSound(ev, "screencfgOpen"); + soundServer = registerSound(ev, "server"); } @SubscribeEvent diff --git a/src/main/java/net/montoyo/wd/client/ClientProxy.java b/src/main/java/net/montoyo/wd/client/ClientProxy.java index 886834c..6ffce9d 100644 --- a/src/main/java/net/montoyo/wd/client/ClientProxy.java +++ b/src/main/java/net/montoyo/wd/client/ClientProxy.java @@ -309,7 +309,7 @@ public class ClientProxy extends SharedProxy implements IResourceManagerReloadLi } @Override - public void startMiniServClient() { + public void startMiniservClient() { if(miniservPort <= 0) { Log.warning("Can't start miniserv client: miniserv is disabled"); return; @@ -331,6 +331,11 @@ public class ClientProxy extends SharedProxy implements IResourceManagerReloadLi msClientStarted = true; } + @Override + public boolean isMiniservDisabled() { + return miniservPort <= 0; + } + /**************************************** RESOURCE MANAGER METHODS ****************************************/ @Override diff --git a/src/main/java/net/montoyo/wd/client/gui/CommandHandler.java b/src/main/java/net/montoyo/wd/client/gui/CommandHandler.java new file mode 100644 index 0000000..ed4f299 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/CommandHandler.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CommandHandler { + + String value(); + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiServer.java b/src/main/java/net/montoyo/wd/client/gui/GuiServer.java new file mode 100644 index 0000000..79dcbc8 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiServer.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.client.audio.ISound; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundCategory; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.miniserv.client.Client; +import net.montoyo.wd.miniserv.client.ClientTask; +import net.montoyo.wd.miniserv.client.ClientTaskGetFileList; +import net.montoyo.wd.miniserv.client.ClientTaskGetQuota; +import net.montoyo.wd.utilities.Log; +import net.montoyo.wd.utilities.NameUUIDPair; +import net.montoyo.wd.utilities.Util; +import org.lwjgl.input.Keyboard; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import static org.lwjgl.opengl.GL11.*; + +public class GuiServer extends WDScreen { + + private static final ResourceLocation BG_IMAGE = new ResourceLocation("webdisplays", "textures/gui/server_bg.png"); + private static final ResourceLocation FG_IMAGE = new ResourceLocation("webdisplays", "textures/gui/server_fg.png"); + private static final HashMap COMMAND_MAP = new HashMap<>(); + + private final NameUUIDPair owner; + private final ArrayList lines = new ArrayList<>(); + private String prompt = ""; + private String userPrompt; + private int blinkTime; + private String lastCmd; + private boolean promptLocked; + private long queryTime; + private ClientTask currentTask; + + //Access command + private int accessTrials; + private int accessTime; + private int accessState = -1; + private PositionedSoundRecord accessSound; + + public GuiServer(NameUUIDPair owner) { + this.owner = owner; + //userPrompt = owner.name + "@miniserv$ "; + userPrompt = "> "; + + if(COMMAND_MAP.isEmpty()) + buildCommandMap(); + + lines.add("MiniServ 1.0"); + lines.add(tr("info")); + } + + private static String tr(String key, Object ... args) { + return I18n.format("webdisplays.server." + key, args); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float ptt) { + super.drawScreen(mouseX, mouseY, ptt); + + int x = (width - 256) / 2; + int y = (height - 176) / 2; + + GlStateManager.enableTexture2D(); + mc.renderEngine.bindTexture(BG_IMAGE); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + drawTexturedModalRect(x, y, 0, 0, 256, 176); + + x += 18; + y += 18; + + for(String line: lines) { + fontRenderer.drawString(line, x, y, 0xFFFFFFFF, false); + y += 12; + } + + if(!promptLocked) { + x = fontRenderer.drawString(userPrompt, x, y, 0xFFFFFFFF, false); + x = fontRenderer.drawString(prompt, x, y, 0xFFFFFFFF, false); + } + + if(blinkTime < 5) { + double xd = (double) (x + 1); + double yd = (double) y; + double zd = (double) zLevel; + + GlStateManager.disableTexture2D(); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + Tessellator t = Tessellator.getInstance(); + BufferBuilder bb = t.getBuffer(); + bb.begin(GL_QUADS, DefaultVertexFormats.POSITION); + bb.pos(xd, yd + 8.0f, zd).endVertex(); + bb.pos(xd + 6.0f, yd + 8.0f, zd).endVertex(); + bb.pos(xd + 6.0f, yd, zd).endVertex(); + bb.pos(xd, yd, zd).endVertex(); + t.draw(); + } + + GlStateManager.disableAlpha(); + GlStateManager.enableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + mc.renderEngine.bindTexture(FG_IMAGE); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + drawTexturedModalRect((width - 256) / 2, (height - 176) / 2, 0, 0, 256, 176); + } + + @Override + public void updateScreen() { + super.updateScreen(); + + if(accessState >= 0) { + if(--accessTime <= 0) { + accessState++; + + if(accessState == 1) { + if(lines.size() > 0) + lines.remove(lines.size() - 1); + + lines.add("access: PERMISSION DENIED....and..."); + accessTime = 20; + } else { + if(accessSound == null) { + accessSound = new PositionedSoundRecord(WebDisplays.INSTANCE.soundServer.getSoundName(), SoundCategory.MASTER, 1.0f, 1.0f, true, 0, ISound.AttenuationType.NONE, 0.0f, 0.0f, 0.0f); + mc.getSoundHandler().playSound(accessSound); + } + + writeLine("YOU DIDN'T SAY THE MAGIC WORD!"); + accessTime = 2; + } + } + } else { + blinkTime = (blinkTime + 1) % 10; + + if(currentTask != null && System.currentTimeMillis() - queryTime >= 10000) { + writeLine(tr("timeout")); + currentTask.cancel(); + clearTask(); + } + } + } + + @Override + public void handleKeyboardInput() throws IOException { + if(!promptLocked && Keyboard.getEventKeyState() && Keyboard.getEventKey() == Keyboard.KEY_UP) { + if(lastCmd != null) + prompt = lastCmd; + + return; + } + + super.handleKeyboardInput(); + + if(Keyboard.getEventKeyState() && Keyboard.getEventKey() == Keyboard.KEY_L && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL))) + lines.clear(); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + super.keyTyped(typedChar, keyCode); + + if(promptLocked) + return; + + if(keyCode == Keyboard.KEY_BACK) { + if(prompt.length() > 0) + prompt = prompt.substring(0, prompt.length() - 1); + } else if(keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER) { + if(prompt.length() > 0) { + writeLine(userPrompt + prompt); + evaluateCommand(prompt); + lastCmd = prompt; + prompt = ""; + } else + writeLine(userPrompt); + } else if(prompt.length() + 1 < 30 && typedChar >= 32 && typedChar <= 126) + prompt = prompt + typedChar; + + blinkTime = 0; + } + + private void evaluateCommand(String str) { + String[] args = str.trim().split("\\s+"); + Method handler = COMMAND_MAP.get(args[0].toLowerCase()); + + if(handler == null) { + writeLine(tr("unknowncmd")); + return; + } + + Object[] params; + if(handler.getParameterCount() == 0) + params = new Object[0]; + else { + String[] args2 = new String[args.length - 1]; + System.arraycopy(args, 1, args2, 0, args2.length); + params = new Object[] { args2 }; + } + + try { + handler.invoke(this, params); + } catch(IllegalAccessException | InvocationTargetException e) { + Log.errorEx("Caught exception while running command \"%s\"", e, str); + writeLine(tr("error")); + } + } + + private void writeLine(String line) { + while(lines.size() >= 11) + lines.remove(0); + + lines.add(line); + } + + private static void buildCommandMap() { + COMMAND_MAP.clear(); + + Method[] methods = GuiServer.class.getMethods(); + for(Method m: methods) { + CommandHandler cmd = m.getAnnotation(CommandHandler.class); + + if(cmd != null && Modifier.isPublic(m.getModifiers())) { + if(m.getParameterCount() == 0 || (m.getParameterCount() == 1 && m.getParameterTypes()[0] == String[].class)) + COMMAND_MAP.put(cmd.value().toLowerCase(), m); + } + } + } + + @Override + public void onGuiClosed() { + super.onGuiClosed(); + + if(accessSound != null) + mc.getSoundHandler().stopSound(accessSound); + } + + private boolean queueTask(ClientTask task) { + if(Client.getInstance().addTask(task)) { + promptLocked = true; + queryTime = System.currentTimeMillis(); + currentTask = task; + return true; + } else { + writeLine(tr("queryerr")); + return false; + } + } + + private void clearTask() { + promptLocked = false; + currentTask = null; + } + + @CommandHandler("clear") + public void commandClear() { + lines.clear(); + } + + @CommandHandler("help") + public void commandHelp() { + for(String c : COMMAND_MAP.keySet()) + writeLine(c + " - " + tr("help." + c)); + } + + @CommandHandler("exit") + public void commandExit() { + mc.displayGuiScreen(null); + } + + @CommandHandler("access") + public void commandAccess(String[] args) { + boolean handled = false; + + if(args.length >= 1 && args[0].equalsIgnoreCase("security")) { + if(args.length == 1 || (args.length == 2 && args[1].equalsIgnoreCase("grid"))) + handled = true; + } else if(args.length == 3 && args[0].equalsIgnoreCase("main") && args[1].equalsIgnoreCase("security") && args[2].equalsIgnoreCase("grid")) + handled = true; + + if(handled) { + writeLine("access: PERMISSION DENIED."); + + if(++accessTrials >= 3) { + promptLocked = true; + accessState = 0; + accessTime = 20; + } + } else + writeLine(tr("argerror")); + } + + @CommandHandler("owner") + public void commandOwner() { + writeLine(tr("ownername", owner.name)); + writeLine(tr("owneruuid")); + writeLine(owner.uuid.toString()); + } + + @CommandHandler("quota") + public void commandQuota() { + if(!mc.player.getGameProfile().getId().equals(owner.uuid)) { + writeLine(tr("errowner")); + return; + } + + ClientTaskGetQuota task = new ClientTaskGetQuota(); + task.setFinishCallback((t) -> { + writeLine(tr("quota", Util.sizeString(t.getQuota()), Util.sizeString(t.getMaxQuota()))); + clearTask(); + }); + + queueTask(task); + } + + @CommandHandler("ls") + public void commandList() { + ClientTaskGetFileList task = new ClientTaskGetFileList(owner.uuid); + task.setFinishCallback((t) -> { + String[] files = t.getFileList(); + if(files != null) + Arrays.stream(files).forEach(this::writeLine); + + clearTask(); + }); + + queueTask(task); + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/controls/CheckBox.java b/src/main/java/net/montoyo/wd/client/gui/controls/CheckBox.java index ab7d4ae..ab98abf 100644 --- a/src/main/java/net/montoyo/wd/client/gui/controls/CheckBox.java +++ b/src/main/java/net/montoyo/wd/client/gui/controls/CheckBox.java @@ -5,6 +5,7 @@ package net.montoyo.wd.client.gui.controls; import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.renderer.GlStateManager; import net.minecraft.init.SoundEvents; import net.minecraft.util.ResourceLocation; import net.montoyo.wd.client.gui.loading.JsonOWrapper; @@ -71,6 +72,8 @@ public class CheckBox extends BasicControl { @Override public void draw(int mouseX, int mouseY, float ptt) { if(visible) { + GlStateManager.disableAlpha(); + bindTexture(checked ? texChecked : texUnchecked); blend(true); fillTexturedRect(x, y, WIDTH, HEIGHT, 0.0, 0.0, 1.0, 1.0); diff --git a/src/main/java/net/montoyo/wd/data/GuiData.java b/src/main/java/net/montoyo/wd/data/GuiData.java index ef355a7..afa0632 100644 --- a/src/main/java/net/montoyo/wd/data/GuiData.java +++ b/src/main/java/net/montoyo/wd/data/GuiData.java @@ -22,6 +22,7 @@ public abstract class GuiData { dataTable.put("ScreenConfig", ScreenConfigData.class); dataTable.put("Keyboard", KeyboardData.class); dataTable.put("RedstoneCtrl", RedstoneCtrlData.class); + dataTable.put("Server", ServerData.class); } public static Class classOf(String name) { diff --git a/src/main/java/net/montoyo/wd/data/ServerData.java b/src/main/java/net/montoyo/wd/data/ServerData.java new file mode 100644 index 0000000..b23be03 --- /dev/null +++ b/src/main/java/net/montoyo/wd/data/ServerData.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.data; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.montoyo.wd.client.gui.GuiServer; +import net.montoyo.wd.utilities.NameUUIDPair; + +public class ServerData extends GuiData { + + public NameUUIDPair owner; + + public ServerData() { + } + + public ServerData(NameUUIDPair owner) { + this.owner = owner; + } + + @SideOnly(Side.CLIENT) + @Override + public GuiScreen createGui(GuiScreen old, World world) { + return new GuiServer(owner); + } + + @Override + public String getName() { + return "Server"; + } + +} diff --git a/src/main/java/net/montoyo/wd/entity/TileEntityServer.java b/src/main/java/net/montoyo/wd/entity/TileEntityServer.java index 67a38da..f3ae578 100644 --- a/src/main/java/net/montoyo/wd/entity/TileEntityServer.java +++ b/src/main/java/net/montoyo/wd/entity/TileEntityServer.java @@ -5,9 +5,12 @@ package net.montoyo.wd.entity; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; +import net.montoyo.wd.data.ServerData; import net.montoyo.wd.utilities.NameUUIDPair; +import net.montoyo.wd.utilities.Util; import javax.annotation.Nonnull; import java.util.UUID; @@ -49,6 +52,11 @@ public class TileEntityServer extends TileEntity { if(world.isRemote) return true; + //TODO: Check if miniserv is disabled + //Util.toast(ply, "noMiniserv"); + + if(owner != null && ply instanceof EntityPlayerMP) + (new ServerData(owner)).sendTo((EntityPlayerMP) ply); return true; } diff --git a/src/main/java/net/montoyo/wd/item/ItemPeripheral.java b/src/main/java/net/montoyo/wd/item/ItemPeripheral.java index 470668a..ac87227 100644 --- a/src/main/java/net/montoyo/wd/item/ItemPeripheral.java +++ b/src/main/java/net/montoyo/wd/item/ItemPeripheral.java @@ -16,6 +16,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3i; import net.minecraft.world.World; +import net.montoyo.wd.WebDisplays; import net.montoyo.wd.block.BlockKeyboardRight; import net.montoyo.wd.core.DefaultPeripheral; @@ -53,6 +54,8 @@ public class ItemPeripheral extends ItemMultiTexture { public void addInformation(ItemStack is, @Nullable World world, List tt, ITooltipFlag ttFlags) { if(is.getMetadata() == 1) //CC Interface tt.add("" + ChatFormatting.RED + I18n.format("webdisplays.message.missingCC")); //CC is not available for 1.12.2 + else if(is.getMetadata() == 11 && WebDisplays.PROXY.isMiniservDisabled()) //Server + tt.add("" + ChatFormatting.RED + I18n.format("webdisplays.message.noMiniserv")); } } diff --git a/src/main/java/net/montoyo/wd/miniserv/PacketID.java b/src/main/java/net/montoyo/wd/miniserv/PacketID.java index f076404..569b5f7 100644 --- a/src/main/java/net/montoyo/wd/miniserv/PacketID.java +++ b/src/main/java/net/montoyo/wd/miniserv/PacketID.java @@ -12,7 +12,9 @@ public enum PacketID { BEGIN_FILE_UPLOAD, //C->S FILE_PART, //C->S and S->C FILE_STATUS, //S->C - GET_FILE; //C->S + GET_FILE, //C->S + QUOTA, //C->S and S->C + LIST; //C->S and S->C public static PacketID fromInt(int i) { PacketID[] values = values(); diff --git a/src/main/java/net/montoyo/wd/miniserv/client/Client.java b/src/main/java/net/montoyo/wd/miniserv/client/Client.java index 3a69d5b..7f02c47 100644 --- a/src/main/java/net/montoyo/wd/miniserv/client/Client.java +++ b/src/main/java/net/montoyo/wd/miniserv/client/Client.java @@ -216,7 +216,7 @@ public class Client extends AbstractClient implements Runnable { private void unsafeLoop() throws Throwable { selector.select(); - if(currentTask == null) + if(currentTask == null || currentTask.isCanceled()) nextTask(); for(SelectionKey key: selector.selectedKeys()) { @@ -297,6 +297,27 @@ public class Client extends AbstractClient implements Runnable { } } + @PacketHandler(PacketID.QUOTA) + public void handleQuota(DataInputStream dis) throws IOException { + long q = dis.readLong(); + long m = dis.readLong(); + + if(currentTask instanceof ClientTaskGetQuota) + ((ClientTaskGetQuota) currentTask).onQuotaData(q, m); + } + + @PacketHandler(PacketID.LIST) + public void handleList(DataInputStream dis) throws IOException { + int cnt = dis.readByte() & 0xFF; + String[] files = new String[cnt]; + + for(int i = 0; i < cnt; i++) + files[i] = readString(dis); + + if(currentTask instanceof ClientTaskGetFileList) + ((ClientTaskGetFileList) currentTask).onFileList(files); + } + public void nextTask() { if(currentTask != null) currentTask.onFinished(); @@ -326,4 +347,14 @@ public class Client extends AbstractClient implements Runnable { return true; } + public void wakeup() { + boolean conn; + synchronized(this) { + conn = connected; + } + + if(conn) + selector.wakeup(); + } + } diff --git a/src/main/java/net/montoyo/wd/miniserv/client/ClientTask.java b/src/main/java/net/montoyo/wd/miniserv/client/ClientTask.java index 4554bfa..dc0160e 100644 --- a/src/main/java/net/montoyo/wd/miniserv/client/ClientTask.java +++ b/src/main/java/net/montoyo/wd/miniserv/client/ClientTask.java @@ -4,11 +4,15 @@ package net.montoyo.wd.miniserv.client; +import net.montoyo.wd.WebDisplays; + import java.util.function.Consumer; -public abstract class ClientTask { +public abstract class ClientTask { - private Consumer finishCallback; + private Consumer finishCallback; + private volatile boolean canceled; + protected boolean runCallbackOnMcThread; protected final Client client = Client.getInstance(); public abstract void start(); @@ -16,12 +20,37 @@ public abstract class ClientTask { public void onFinished() { //Called by Client, don't call it from a ClientTask! - if(finishCallback != null) - finishCallback.accept(this); + if(finishCallback != null && !isCanceled()) { + if(runCallbackOnMcThread) + WebDisplays.PROXY.enqueue(() -> finishCallback.accept((T) this)); + else + finishCallback.accept((T) this); + } } - public void setFinishCallback(Consumer finishCallback) { + public void setFinishCallback(Consumer finishCallback) { this.finishCallback = finishCallback; } + public void setRunCallbackOnMinecraftThread(boolean runCallbackOnMcThread) { + this.runCallbackOnMcThread = runCallbackOnMcThread; + } + + public final void cancel() { + synchronized(this) { + canceled = true; + } + + Client.getInstance().wakeup(); + } + + public final boolean isCanceled() { + boolean ret; + synchronized(this) { + ret = canceled; + } + + return ret; + } + } diff --git a/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFile.java b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFile.java index ce28fb1..b94e7f4 100644 --- a/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFile.java +++ b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFile.java @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -public class ClientTaskGetFile extends ClientTask { +public class ClientTaskGetFile extends ClientTask { private final UUID uuid; private final String fname; @@ -86,6 +86,7 @@ public class ClientTaskGetFile extends ClientTask { while(!hasResponse) { if(System.currentTimeMillis() - t > 10000) { responseLock.unlock(); + cancel(); return Constants.GETF_STATUS_TIMED_OUT; } @@ -124,6 +125,7 @@ public class ClientTaskGetFile extends ClientTask { data = new byte[0]; dataLen = -1; dataLock.unlock(); + cancel(); return data; } diff --git a/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFileList.java b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFileList.java new file mode 100644 index 0000000..9e8880c --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetFileList.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv.client; + +import net.montoyo.wd.miniserv.OutgoingPacket; +import net.montoyo.wd.miniserv.PacketID; + +import java.util.UUID; + +public class ClientTaskGetFileList extends ClientTask { + + private final UUID user; + private String[] files; + + public ClientTaskGetFileList(UUID user) { + this.user = user; + runCallbackOnMcThread = true; + } + + @Override + public void start() { + OutgoingPacket pkt = new OutgoingPacket(); + pkt.writeByte(PacketID.LIST.ordinal()); + pkt.writeLong(user.getMostSignificantBits()); + pkt.writeLong(user.getLeastSignificantBits()); + client.sendPacket(pkt); + } + + @Override + public void abort() { + } + + public void onFileList(String[] files) { + this.files = files; + client.nextTask(); + } + + public String[] getFileList() { + return files; + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetQuota.java b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetQuota.java new file mode 100644 index 0000000..fc5631a --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskGetQuota.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv.client; + +import net.montoyo.wd.miniserv.OutgoingPacket; +import net.montoyo.wd.miniserv.PacketID; + +public class ClientTaskGetQuota extends ClientTask { + + private long quota; + private long maxQuota; + + public ClientTaskGetQuota() { + runCallbackOnMcThread = true; + } + + @Override + public void start() { + OutgoingPacket pkt = new OutgoingPacket(); + pkt.writeByte(PacketID.QUOTA.ordinal()); + client.sendPacket(pkt); + } + + @Override + public void abort() { + } + + public void onQuotaData(long q, long m) { + quota = q; + maxQuota = m; + client.nextTask(); + } + + public long getMaxQuota() { + return maxQuota; + } + + public long getQuota() { + return quota; + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskUploadFile.java b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskUploadFile.java index 7621ea5..5cc2f9e 100644 --- a/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskUploadFile.java +++ b/src/main/java/net/montoyo/wd/miniserv/client/ClientTaskUploadFile.java @@ -16,7 +16,7 @@ import java.io.IOException; import java.nio.file.Files; import java.util.function.Consumer; -public class ClientTaskUploadFile extends ClientTask implements Consumer { +public class ClientTaskUploadFile extends ClientTask implements Consumer { private static final byte[] UPLOAD_BUFFER = new byte[65535]; diff --git a/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java b/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java index d2ee633..babae53 100644 --- a/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java +++ b/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java @@ -13,6 +13,7 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; +import java.util.Arrays; import java.util.UUID; import java.util.function.Consumer; @@ -236,6 +237,43 @@ public class ServerClient extends AbstractClient { } } + @PacketHandler(PacketID.QUOTA) + public void handleQuota(DataInputStream dis) { + OutgoingPacket pkt = new OutgoingPacket(); + pkt.writeByte(PacketID.QUOTA.ordinal()); + pkt.writeLong(quota); + pkt.writeLong(Server.getInstance().getMaxQuota()); + + sendPacket(pkt); + } + + @PacketHandler(PacketID.LIST) + public void handleList(DataInputStream dis) throws IOException { + long msb = dis.readLong(); + long lsb = dis.readLong(); + File dir = new File(Server.getInstance().getDirectory(), (new UUID(msb, lsb)).toString()); + String[] list = null; + + if(dir.exists() && dir.isDirectory()) { + File[] files = dir.listFiles(); + + if(files != null) + list = Arrays.stream(files).filter(f -> f.isFile() && !Util.isFileNameInvalid(f.getName())).map(File::getName).toArray(String[]::new); + } + + OutgoingPacket pkt = new OutgoingPacket(); + pkt.writeByte(PacketID.LIST.ordinal()); + + if(list == null) + pkt.writeByte(0); + else { + pkt.writeByte(list.length); + Arrays.stream(list).forEach(pkt::writeString); + } + + sendPacket(pkt); + } + private void finishUpload(int status) { if(currentFile != null) { OutgoingPacket pkt = new OutgoingPacket(); diff --git a/src/main/java/net/montoyo/wd/net/client/CMessageMiniservKey.java b/src/main/java/net/montoyo/wd/net/client/CMessageMiniservKey.java index fbd5568..5cca09a 100644 --- a/src/main/java/net/montoyo/wd/net/client/CMessageMiniservKey.java +++ b/src/main/java/net/montoyo/wd/net/client/CMessageMiniservKey.java @@ -40,7 +40,7 @@ public class CMessageMiniservKey implements IMessage, Runnable { public void run() { if(Client.getInstance().decryptKey(encryptedKey)) { Log.info("Successfully received and decrypted key, starting miniserv client..."); - WebDisplays.PROXY.startMiniServClient(); + WebDisplays.PROXY.startMiniservClient(); } } diff --git a/src/main/java/net/montoyo/wd/utilities/Util.java b/src/main/java/net/montoyo/wd/utilities/Util.java index 4703a81..32d71e9 100644 --- a/src/main/java/net/montoyo/wd/utilities/Util.java +++ b/src/main/java/net/montoyo/wd/utilities/Util.java @@ -227,4 +227,19 @@ public abstract class Util { public static boolean isFileNameInvalid(String fname) { return fname.isEmpty() || fname.length() > 64 || fname.charAt(0) == '.' || fname.indexOf('/') >= 0 || fname.indexOf('\\') >= 0; } + + public static final String[] SIZES = { "bytes", "KiB", "MiB", "GiB", "TiB" }; + + public static String sizeString(long l) { + double d = (double) l; + int size = 0; + + while(l >= 1024L && size + 1 < SIZES.length) { + d /= 1024.0; + l /= 1024L; + size++; + } + + return String.format("%.2f %s", d, SIZES[size]); + } } diff --git a/src/main/resources/assets/webdisplays/lang/en_us.lang b/src/main/resources/assets/webdisplays/lang/en_us.lang index e770751..113e2a0 100644 --- a/src/main/resources/assets/webdisplays/lang/en_us.lang +++ b/src/main/resources/assets/webdisplays/lang/en_us.lang @@ -50,6 +50,7 @@ webdisplays.message.missingOC=OpenComputers is not available. webdisplays.message.upgradeError=Upgrade error :( Check logs... webdisplays.message.upgradeOk=Upgrade installed! webdisplays.message.linkAbort=Linker reset +webdisplays.message.noMiniserv=Server block is disabled on this server webdisplays.gui.screencfg.owner=Screen owner: webdisplays.gui.screencfg.friends=Friends: webdisplays.gui.screencfg.permissions=Permissions: @@ -110,3 +111,20 @@ webdisplays.side.north=North webdisplays.side.south=South webdisplays.side.west=West webdisplays.side.east=East +webdisplays.server.info=Type "help" if you need some. +webdisplays.server.unknowncmd=Unknown command. +webdisplays.server.error=Internal error. Check logs. +webdisplays.server.argerror=Unrecognized argument. +webdisplays.server.queryerr=Query error, check logs. +webdisplays.server.errowner=Only the owner can access this. +webdisplays.server.timeout=Query timed out. Check logs. +webdisplays.server.ownername=Owner name: %s +webdisplays.server.owneruuid=Owner UUID: +webdisplays.server.quota=%s/%s used +webdisplays.server.help.help=Displays this text +webdisplays.server.help.clear=Clears the screen +webdisplays.server.help.exit=Leaves this console +webdisplays.server.help.access=§kNo help data +webdisplays.server.help.owner=Displays the server owner +webdisplays.server.help.quota=Displays the storage quota +webdisplays.server.help.ls=Lists the files on this server diff --git a/src/main/resources/assets/webdisplays/sounds.json b/src/main/resources/assets/webdisplays/sounds.json index f1f2b2b..68c0910 100644 --- a/src/main/resources/assets/webdisplays/sounds.json +++ b/src/main/resources/assets/webdisplays/sounds.json @@ -29,5 +29,11 @@ "sounds": [ "webdisplays:screencfg_open" ] + }, + "server": { + "category": "master", + "sounds": [ + "webdisplays:server" + ] } } diff --git a/src/main/resources/assets/webdisplays/sounds/server.ogg b/src/main/resources/assets/webdisplays/sounds/server.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3ba744518b11c04e394913a89c582d28c08e6c91 GIT binary patch literal 37348 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 I3qCX#0C71d!vFvP literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/webdisplays/textures/gui/server_bg.png b/src/main/resources/assets/webdisplays/textures/gui/server_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1b72912256aeb3d39beb8eecfe720306748371 GIT binary patch literal 817 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6&6|H(?D8gCb z5n0T@z%2~Ij105pNB{-dOFVsD*&i`+2ug^XFzl~qU|`zk>EaktaqI0ZU$4UfA`KU- zow*)u5H`OkYE$q)?N0ORlN-8E7`0sAYEj5txZsDdx|+t3J;iRhmxXo|*vDlW+xakV zQFzY3EOXQU3rD`C?|WaD{&S)3)@$E|>(4P4?2Pf@D9e#(>*$a;Zn*2bxQ_jnrN&GG z3=SaovoPG)UiXsSOLG~U^p|hH|J`R@FhwZ5oQ*d+#4A#!@!Y4wb~5Kr`@i^mYj*5? zX^Whb4@C+;?|7qfA+j!GdORc447gzwGXxlv!LFj%AS9O|F{x!X4U9~X=UZMn|8@PZ zV^P^tf*w`G)f+#MpU;rB_T!&~j18gw+t*kBdad5@)05%GJk`_twHUJVnBF|lW>8>g z0Hz=Y4j|(|8}H!{Pb=jGXU+^>`}MZ#f?i>VU&Z`el4Xu}bey^Qdh$gF;Y%D0)CJ#O Yv#z@B>LaoEaURHEPgg&ebxsLQ0I$2RiU0rr literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/webdisplays/textures/gui/server_fg.png b/src/main/resources/assets/webdisplays/textures/gui/server_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..70f935e8f3449f16e555c14396325276a0c8790c GIT binary patch literal 1314 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6&6|H(?D8gCb z5n0T@z%2~Ij105pNB{-dOFVsD*&i`+2ui4QU$~daz`(NJ)5S5Q;?~>Sj$OAsL>eBh zniOuU&cSo$ul|%;1%vK63qv<=T<|GKH9G91apFGyp1FK)!-VdwSAV#K1!y1y{Mq-3 z`~K&`|NABdMa{atvy$O`@cZ-j>-Y{Fuf4JTVf?Iz`e%P1eg5h`ho0=ew_opvvp!Jf zsgrInW2%0y%~BA{vF-SGZKe;$Yair)KYr;ZliPF#Hu-y-Ij>B)G%M)Ww=?E<`TjIi zeA~~f$6OUE#608q#kRe6zunxKHt^^%K4KH#S8KQ0>B&*-Ebv?}Ua(=0aN{?&1etGE zJ0cn9*nj?>x97Sx(}i_EKTa$Un<%Bn%40u=-!ac`Q)RmHj9rXJX4h64{K+={vzLQ| zan5Z9uXMfB7roysW~}(KaQ@cHnl=FrhBK1?YUZ!Ml*6Q&^J+Sa!ZC%o&)b{--`unA z?0as8Is6XQPH~4gx5WrHO!-y*t61XSkBGb64VjD`)9>H@pzpr^e>|tdE1v&S>waH% zWMN@=7W+Rx{lCSZ_J8*0-ac{Mdic*9v4kHFV>qs*{m9;-|6v-#l`o&!`5v)t;4^Co zWk@)cdLZLR_O2Jj4CiwgOmmoDKagu+IFQYFLuo_X`l)H}rj@fMes%WV(78EbzAzI>6*W03>AM}ZvVdH%lDm`@9ecTybms3U(L&~EQjG^-GP;n zTH>-f@9#Mn{V_;gc4>|9tK3hF@7DfQVkmycc;y`1hfAtuAHQ?`k7r_FV&GseN_kYH=e_MGIN)GIezs>OA&$PMc<^|*n^RFszcxv~iw7`;K&$iHf=^D<@ zWiNytm_2L=`h3`){kn24$0~6202AhP)&*_XRcqG1ne#V1y?(|f)+uSnywZQ%2PWLB zOO{NTX7FdHWX)mz=jrQAHg9W@n4--TARoh)lm2IEWnW^1{2uuacWfJEm@c$ExWv1l z?A(*WxB2b=Hy86eFeLafxJ4iM!tlpKv)1#k^{y~TDsw> z2SeOW;s4KVWwxK=U;W*5f9;jDui^|A@(mZoHthXZmC6w0^>V#H!<|lt-*aOAA5LXF iv7X1CfmYzZ>wkvBKh=-kt+oRddJLYfelF{r5}E)FH^@Z* literal 0 HcmV?d00001