/* * Copyright (C) 2018 BARBOTIN Nicolas */ package net.montoyo.wd.client.gui; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.resources.language.I18n; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.client.resources.sounds.SoundInstance; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; import net.montoyo.wd.WebDisplays; import net.montoyo.wd.miniserv.Constants; import net.montoyo.wd.miniserv.client.*; import net.montoyo.wd.net.WDNetworkRegistry; import net.montoyo.wd.utilities.*; import net.montoyo.wd.utilities.math.Vector3i; import net.montoyo.wd.utilities.data.BlockSide; import net.montoyo.wd.utilities.serialization.NameUUIDPair; import net.montoyo.wd.utilities.serialization.Util; import org.lwjgl.glfw.GLFW; import javax.annotation.Nullable; import javax.swing.filechooser.FileSystemView; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.function.Supplier; import static net.montoyo.wd.client.gui.GuiMinePad.getChar; 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 static final int MAX_LINE_LEN = 32; private static final int MAX_LINES = 12; private final Vector3i serverPos; 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 volatile long queryTime; private ClientTask currentTask; private int selectedLine = -1; //Access command private int accessTrials; private int accessTime; private int accessState = -1; private SimpleSoundInstance accessSound; //Upload wizard private boolean uploadWizard; private File uploadDir; private final ArrayList uploadFiles = new ArrayList<>(); private int uploadOffset; private boolean uploadFirstIsParent; private String uploadFilter = ""; private long uploadFilterTime; public GuiServer(Vector3i vec, NameUUIDPair owner) { super(Component.nullToEmpty(null)); serverPos = vec; this.owner = owner; userPrompt = "> "; if (COMMAND_MAP.isEmpty()) buildCommandMap(); lines.add("MiniServ 1.0"); lines.add(tr("info")); uploadCD(FileSystemView.getFileSystemView().getDefaultDirectory()); } private static String tr(String key, Object... args) { return I18n.get("webdisplays.server." + key, args); } @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float ptt) { super.render(graphics, mouseX, mouseY, ptt); int x = (width - 256) / 2; int y = (height - 176) / 2; // RenderSystem.enableTexture(); RenderSystem.setShaderTexture(0, BG_IMAGE); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); graphics.blit(BG_IMAGE, x, y, 0, 0, 256, 256); x += 18; y += 18; for (int i = 0; i < lines.size(); i++) { if (selectedLine == i) { drawWhiteQuad(x - 1, y - 2, font.width(lines.get(i)) + 1, 12); graphics.drawString(Minecraft.getInstance().font, lines.get(i), x, y, 0xFF129700, false); } else graphics.drawString(Minecraft.getInstance().font, lines.get(i), x, y, 0xFFFFFFFF, false); y += 12; } if (!promptLocked) { if (queue.isEmpty()) { x = graphics.drawString(Minecraft.getInstance().font, userPrompt, x, y, 0xFFFFFFFF, false); x = graphics.drawString(Minecraft.getInstance().font, prompt, x, y, 0xFFFFFFFF, false); } else { x = graphics.drawString(Minecraft.getInstance().font, tr("press_for_more"), x, y, 0xFFFFFFFF, false); } } if (!uploadWizard && blinkTime < 5) drawWhiteQuad(x + 1, y, 6, 8); // RenderSystem.enableTexture(); RenderSystem.enableBlend(); RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); RenderSystem.setShaderTexture(0, FG_IMAGE); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); // blit(graphics,(width - 256) / 2, (height - 176) / 2, 0, 0, 256, 176); } private void drawWhiteQuad(int x, int y, int w, int h) { float xd = (float) x; float xd2 = (float) (x + w); float yd = (float) y; float yd2 = (float) (y + h); float zd = (float) getBlitOffset(); // RenderSystem.disableTexture(); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); Tesselator t = Tesselator.getInstance(); BufferBuilder bb = t.getBuilder(); bb.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION); bb.vertex(xd, yd2, zd).endVertex(); bb.vertex(xd2, yd2, zd).endVertex(); bb.vertex(xd2, yd, zd).endVertex(); bb.vertex(xd, yd, zd).endVertex(); t.end(); // RenderSystem.enableTexture(); } private float getBlitOffset() { return 0; } @Override public void tick() { super.tick(); 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 SimpleSoundInstance(WebDisplays.INSTANCE.soundServer.getLocation(), SoundSource.MASTER, 1.0f, 1.0f, RandomSource.create(), true, 0, SoundInstance.Attenuation.NONE, 0.0f, 0.0f, 0.0f, false); minecraft.getSoundManager().play(accessSound); } writeLine("YOU DIDN'T SAY THE MAGIC WORD!"); accessTime = 2; } } } else { blinkTime = (blinkTime + 1) % 10; if (currentTask != null) { long queryTime; synchronized (this) { queryTime = this.queryTime; } if (System.currentTimeMillis() - queryTime >= 10000) { writeLine(tr("timeout")); currentTask.cancel(); clearTask(); } } if (!uploadFilter.isEmpty() && System.currentTimeMillis() - uploadFilterTime >= 1000) { Log.info("Upload filter cleared"); uploadFilter = ""; } } final int maxl = uploadWizard ? MAX_LINES : (MAX_LINES - 1); //Cuz prompt is hidden if (!queue.isEmpty()) { while (!queue.isEmpty()) { if (lines.size() >= maxl) break; writeLine(queue.remove(0)); } } while (lines.size() > maxl) lines.remove(0); } @Override public boolean keyReleased(int keyCode, int scanCode, int modifiers) { Supplier predicate = () -> super.keyReleased(keyCode, scanCode, modifiers); try { return handleKeyboardInput(keyCode, false, predicate); } catch (IOException e) { e.printStackTrace(); return false; } } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (keyCode == GLFW.GLFW_KEY_ESCAPE && !uploadWizard) { Minecraft.getInstance().setScreen(null); return true; } getChar(keyCode, scanCode).ifPresent(c -> { try { keyTyped(c, keyCode, modifiers); } catch (IOException e) { e.printStackTrace(); } }); try { return handleKeyboardInput(keyCode, true, () -> true); } catch (IOException e) { e.printStackTrace(); return false; } } public boolean handleKeyboardInput(int keyCode, boolean keyState, Supplier booleanSupplier) throws IOException { if (!queue.isEmpty()) return false; if (uploadWizard) { if (keyState) { if (keyCode == GLFW.GLFW_KEY_UP) { if (selectedLine > 3) selectedLine--; else if (uploadOffset > 0) { uploadOffset--; updateUploadScreen(); } } else if (keyCode == GLFW.GLFW_KEY_DOWN) { if (selectedLine < MAX_LINES - 1) selectedLine++; else if (uploadOffset + selectedLine - 2 < uploadFiles.size()) { uploadOffset++; updateUploadScreen(); } } else if (keyCode == GLFW.GLFW_KEY_PAGE_DOWN) { selectedLine = 3; int dst = uploadOffset - (MAX_LINES - 3); if (dst < 0) dst = 0; selectFile(dst); } else if (keyCode == GLFW.GLFW_KEY_PAGE_UP) { selectedLine = 3; int dst = uploadOffset + (MAX_LINES - 3); if (dst >= uploadFiles.size()) dst = uploadFiles.size() - 1; selectFile(dst); } else if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { File file = uploadFiles.get(uploadOffset + selectedLine - 3); if (file.isDirectory()) { uploadCD(file); updateUploadScreen(); } else startFileUpload(file, true); } else if (keyCode == GLFW.GLFW_KEY_F5) { uploadCD(uploadDir); updateUploadScreen(); } } if (keyCode == GLFW.GLFW_KEY_ESCAPE) { quitUploadWizard(); return true; //Don't let the screen handle this } return booleanSupplier.get(); } else { boolean value = booleanSupplier.get(); if (keyState) { boolean ctrl = Screen.hasControlDown(); if (keyCode == GLFW.GLFW_KEY_L && ctrl) lines.clear(); else if (keyCode == GLFW.GLFW_KEY_V && ctrl) { prompt += Minecraft.getInstance().keyboardHandler.getClipboard(); if (prompt.length() > MAX_LINE_LEN) prompt = prompt.substring(0, MAX_LINE_LEN); } else if (keyCode == GLFW.GLFW_KEY_UP) { if (lastCmd != null) { String tmp = prompt; prompt = lastCmd; lastCmd = tmp; } } } return value; } } @Override public boolean charTyped(char codePoint, int modifiers) { return super.charTyped(codePoint, modifiers); } protected void keyTyped(char typedChar, int keyCode, int modifier) throws IOException { //this.charTyped(typedChar, modifier); if (keyCode == GLFW.GLFW_KEY_DOWN) { if (!queue.isEmpty()) { writeLine(queue.remove(0)); return; } } if (!queue.isEmpty()) return; if (uploadWizard) { boolean found = false; uploadFilter += Character.toLowerCase(typedChar); uploadFilterTime = System.currentTimeMillis(); for (int i = uploadFirstIsParent ? 1 : 0; i < uploadFiles.size(); i++) { if (uploadFiles.get(i).getName().toLowerCase().startsWith(uploadFilter)) { selectFile(i); found = true; break; } } if (!found && uploadFilter.length() == 1) uploadFilter = ""; return; } else if (promptLocked) return; if (keyCode == GLFW.GLFW_KEY_SPACE) typedChar = ' '; if ( (typedChar == 'v' || typedChar == 'V') && (modifier & 2) == 2 ) return; if (keyCode == GLFW.GLFW_KEY_BACKSPACE) { if (prompt.length() > 0) prompt = prompt.substring(0, prompt.length() - 1); } else if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { if (prompt.length() > 0) { writeLine(userPrompt + prompt); evaluateCommand(prompt); lastCmd = prompt; prompt = ""; } else writeLine(userPrompt); } else if (prompt.length() + 1 < MAX_LINE_LEN && 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) { final int maxl = uploadWizard ? MAX_LINES : (MAX_LINES - 1); //Cuz prompt is hidden while (lines.size() >= maxl) 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); } } } private void quitUploadWizard() { lines.clear(); promptLocked = false; uploadWizard = false; selectedLine = -1; } @Override public void onClose() { super.onClose(); if (accessSound != null) Minecraft.getInstance().getSoundManager().stop(accessSound); } private boolean queueTask(ClientTask task) { if (Client.getInstance().addTask(task)) { promptLocked = true; queryTime = System.currentTimeMillis(); //No task is running so it's okay to have an unsynchronized access here currentTask = task; return true; } else { writeLine(tr("queryerr")); return false; } } private void clearTask() { promptLocked = false; currentTask = null; } private static String trimStringL(String str) { int delta = str.length() - MAX_LINE_LEN; if (delta <= 0) return str; return "..." + str.substring(delta + 3); } private static String trimStringR(String str) { return (str.length() <= MAX_LINE_LEN) ? str : (str.substring(0, MAX_LINE_LEN - 3) + "..."); } @CommandHandler("clear") public void commandClear() { lines.clear(); } @CommandHandler("help") public void commandHelp(String[] args) { queueRead = lines.size(); if (args.length > 0) { String cmd = args[0].toLowerCase(); if (COMMAND_MAP.containsKey(cmd)) queueLine(tr("help." + cmd)); else queueLine(tr("unknowncmd")); } else { for (String c : COMMAND_MAP.keySet()) queueLine(c + " - " + tr("help." + c)); } } @CommandHandler("exit") public void commandExit() { minecraft.setScreen(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 (!minecraft.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); } @CommandHandler("url") public void commandURL(String[] args) { if (args.length < 1) { writeLine(tr("fnamearg")); return; } String fname = Util.join(args, " "); if (Util.isFileNameInvalid(fname)) { writeLine(tr("nameerr")); return; } ClientTaskCheckFile task = new ClientTaskCheckFile(owner.uuid, fname); task.setFinishCallback((t) -> { int status = t.getStatus(); if (status == 0) { writeLine(tr("urlcopied")); Minecraft.getInstance().keyboardHandler.setClipboard(t.getURL()); } else if (status == Constants.GETF_STATUS_NOT_FOUND) writeLine(tr("notfound")); else writeLine(tr("error2", status)); clearTask(); }); queueTask(task); } private void uploadCD(File newDir) { try { uploadDir = newDir.getCanonicalFile(); } catch (IOException ex) { uploadDir = newDir; } uploadFiles.clear(); File parent = uploadDir.getParentFile(); if (parent != null && parent.exists()) { uploadFiles.add(parent); uploadFirstIsParent = true; } else uploadFirstIsParent = false; File[] children = uploadDir.listFiles(); if (children != null) { Collator c = Collator.getInstance(); c.setStrength(Collator.SECONDARY); c.setDecomposition(Collator.CANONICAL_DECOMPOSITION); Arrays.stream(children).filter(f -> !f.isHidden() && (f.isDirectory() || f.isFile())).sorted((a, b) -> c.compare(a.getName(), b.getName())).forEach(uploadFiles::add); } uploadOffset = 0; uploadFilter = ""; if (uploadWizard) selectedLine = 3; } private void updateUploadScreen() { lines.clear(); lines.add(tr("upload.info")); lines.add(trimStringL(uploadDir.getPath())); lines.add(""); for (int i = uploadOffset; i < uploadFiles.size() && lines.size() < MAX_LINES; i++) { if (i == 0 && uploadFirstIsParent) lines.add(tr("upload.parent")); else lines.add(trimStringR(uploadFiles.get(i).getName())); } } private void selectFile(int i) { int pos = 3 + i - uploadOffset; if (pos >= 3 && pos < MAX_LINES) { selectedLine = pos; return; } uploadOffset = i; if (uploadOffset + MAX_LINES - 3 > uploadFiles.size()) uploadOffset = uploadFiles.size() - MAX_LINES + 3; updateUploadScreen(); selectedLine = 3 + i - uploadOffset; } @CommandHandler("upload") public void commandUpload(String[] args) { if (!minecraft.player.getGameProfile().getId().equals(owner.uuid)) { writeLine(tr("errowner")); return; } if (args.length > 0) { File fle = new File(Util.join(args, " ")); if (!fle.exists()) { writeLine(tr("notfound")); return; } if (fle.isDirectory()) uploadCD(fle); else if (fle.isFile()) { startFileUpload(fle, false); return; } else { writeLine(tr("notfound")); return; } } uploadWizard = true; promptLocked = true; uploadOffset = 0; selectedLine = 3; updateUploadScreen(); } @CommandHandler("rm") public void commandDelete(String[] args) { if (!minecraft.player.getGameProfile().getId().equals(owner.uuid)) { writeLine(tr("errowner")); return; } if (args.length < 1) { writeLine(tr("fnamearg")); return; } String fname = Util.join(args, " "); if (Util.isFileNameInvalid(fname)) { writeLine(tr("nameerr")); return; } ClientTaskDeleteFile task = new ClientTaskDeleteFile(fname); task.setFinishCallback((t) -> { int status = t.getStatus(); if (status == 1) writeLine(tr("notfound")); else if (status != 0) writeLine(tr("error")); clearTask(); }); queueTask(task); } @CommandHandler("reconnect") public void commandReconnect() { Client.getInstance().stop(); WDNetworkRegistry.INSTANCE.sendToServer(Client.getInstance().beginConnection()); } private void startFileUpload(File f, boolean quit) { if (quit) quitUploadWizard(); if (Util.isFileNameInvalid(f.getName()) || f.getName().length() >= MAX_LINE_LEN - 3) { writeLine(tr("nameerr")); return; } ClientTaskUploadFile task; try { task = new ClientTaskUploadFile(f); } catch (IOException ex) { writeLine(tr("error")); ex.printStackTrace(); return; } task.setProgressCallback((cur, total) -> { synchronized (GuiServer.this) { queryTime = System.currentTimeMillis(); } }); task.setFinishCallback(t -> { int status = t.getUploadStatus(); if (status == 0) writeLine(tr("upload.done")); else if (status == Constants.FUPA_STATUS_FILE_EXISTS) writeLine(tr("upload.exists")); else if (status == Constants.FUPA_STATUS_EXCEEDS_QUOTA) writeLine(tr("upload.quota")); else writeLine(tr("error2", status)); clearTask(); }); if (queueTask(task)) writeLine(tr("upload.uploading")); } @Override public boolean isForBlock(BlockPos bp, BlockSide side) { return serverPos.equalsBlockPos(bp); } @Nullable @Override public String getWikiPageName() { return "Server"; } int queueRead = 0; ArrayList queue = new ArrayList<>(); private void queueLine(String line) { final int maxl = uploadWizard ? MAX_LINES : (MAX_LINES - 1); //Cuz prompt is hidden if (lines.size() < maxl) writeLine(line); else if (queueRead > 1) { writeLine(line); queueRead -= 1; } else queue.add(line); } }