From 3a8c3b63cfb996fe956d145d35bd0b2a3f726e62 Mon Sep 17 00:00:00 2001 From: Hadron67 <604700341@qq.com> Date: Fri, 15 May 2020 22:30:44 +0800 Subject: [PATCH] fixed server and confirming --- .../com/hadroncfy/sreplay/SReplayMod.java | 7 +- .../sreplay/command/ConfirmationManager.java | 70 +++++++++++-------- .../sreplay/command/SReplayCommand.java | 27 +++---- .../com/hadroncfy/sreplay/config/Formats.java | 8 ++- .../hadroncfy/sreplay/server/FileEntry.java | 29 +++++++- .../hadroncfy/sreplay/server/HttpHandler.java | 6 +- .../com/hadroncfy/sreplay/server/Server.java | 41 ++++++----- 7 files changed, 115 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/hadroncfy/sreplay/SReplayMod.java b/src/main/java/com/hadroncfy/sreplay/SReplayMod.java index 92031ea..0b6cbf2 100644 --- a/src/main/java/com/hadroncfy/sreplay/SReplayMod.java +++ b/src/main/java/com/hadroncfy/sreplay/SReplayMod.java @@ -14,6 +14,7 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.List; import com.google.gson.JsonParseException; @@ -47,10 +48,12 @@ public static Server getServer(){ } public static List listRecordings() { - return Arrays.asList(config.savePath.listFiles(f -> !f.isDirectory())); + List files = Arrays.asList(config.savePath.listFiles(f -> !f.isDirectory())); + Collections.sort(files); + return files; } - public static void loadConfig() throws IOException { + public static void loadConfig() throws IOException, JsonParseException { File file = new File("config", "sreplay.json"); if (file.exists()){ try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)){ diff --git a/src/main/java/com/hadroncfy/sreplay/command/ConfirmationManager.java b/src/main/java/com/hadroncfy/sreplay/command/ConfirmationManager.java index c8d7284..05fab9b 100644 --- a/src/main/java/com/hadroncfy/sreplay/command/ConfirmationManager.java +++ b/src/main/java/com/hadroncfy/sreplay/command/ConfirmationManager.java @@ -2,48 +2,60 @@ import java.util.HashMap; import java.util.Map; +import java.util.Random; import java.util.Timer; import java.util.TimerTask; +import com.hadroncfy.sreplay.SReplayMod; +import com.hadroncfy.sreplay.config.TextRenderer; + +import net.minecraft.server.command.ServerCommandSource; + public class ConfirmationManager { + private static final Random random = new Random(); private final Map confirms = new HashMap<>(); private final long timeout; - public ConfirmationManager(long timeout){ + private final int codeBound; + public ConfirmationManager(long timeout, int codeBound){ this.timeout = timeout; + this.codeBound = codeBound; } - public void submit(String label, String code, ConfirmationHandler h){ - synchronized(this){ - confirms.put(label, new ConfirmationEntry(label, code, h)); + public synchronized void submit(String label, ServerCommandSource src, Runnable h){ + ConfirmationEntry e = confirms.get(label); + if (e != null){ + e.t.cancel(); } + final int code = random.nextInt(codeBound); + src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().confirmingHint, Integer.toString(code)), false); + confirms.put(label, new ConfirmationEntry(src, label, code, h)); } - public boolean confirm(String label, String code){ - synchronized(this){ - ConfirmationEntry h = confirms.get(label); - if (h != null){ + public synchronized boolean confirm(String label, int code){ + ConfirmationEntry h = confirms.get(label); + if (h != null){ + if (code == h.code){ h.t.cancel(); - h.handler.onConfirm(code.equals(h.code), false); - if (code.equals(h.code)){ - confirms.remove(label); - } - return true; + h.handler.run(); + confirms.remove(label); + } + else { + h.src.sendError(SReplayMod.getFormats().incorrectConfirmationCode); } - return false; + return true; } + return false; } - public boolean cancel(String label){ - synchronized(this){ - ConfirmationEntry h = confirms.get(label); - if (h != null){ - h.t.cancel(); - h.handler.onConfirm(false, true); - confirms.remove(label); - return true; - } - return false; + public synchronized boolean cancel(String label){ + ConfirmationEntry h = confirms.get(label); + if (h != null){ + h.t.cancel(); + confirms.remove(label); + h.src.sendFeedback(SReplayMod.getFormats().operationCancelled, true); + return true; } + return false; } @FunctionalInterface @@ -53,10 +65,12 @@ public interface ConfirmationHandler { private class ConfirmationEntry extends TimerTask { final String label; - final ConfirmationHandler handler; + final ServerCommandSource src; + final Runnable handler; final Timer t; - final String code; - public ConfirmationEntry(String label, String code, ConfirmationHandler h){ + final int code; + public ConfirmationEntry(ServerCommandSource src, String label, int code, Runnable h){ + this.src = src; this.label = label; this.handler = h; t = new Timer(); @@ -68,7 +82,7 @@ public ConfirmationEntry(String label, String code, ConfirmationHandler h){ public void run() { synchronized(ConfirmationManager.this){ confirms.remove(label); - handler.onConfirm(false, true); + src.sendFeedback(SReplayMod.getFormats().operationCancelled, true); } } } diff --git a/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java b/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java index 1a1f57b..f39a044 100644 --- a/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java +++ b/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java @@ -1,5 +1,6 @@ package com.hadroncfy.sreplay.command; +import com.google.gson.JsonParseException; import com.hadroncfy.sreplay.SReplayMod; import com.hadroncfy.sreplay.config.TextRenderer; import com.hadroncfy.sreplay.recording.Photographer; @@ -42,7 +43,7 @@ public class SReplayCommand { private static final Logger LOGGER = LogManager.getLogger(); private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); - private final ConfirmationManager cm = new ConfirmationManager(20000); + private final ConfirmationManager cm = new ConfirmationManager(20000, 999); private final Random rand = new Random(); public SReplayCommand(){ @@ -69,7 +70,7 @@ public void register(CommandDispatcher d) { .suggests(this::suggestRecordingFile) .executes(this::deleteRecording))) .then(literal("confirm") - .then(argument("code", StringArgumentType.word()).executes(this::confirm))) + .then(argument("code", IntegerArgumentType.integer(0)).executes(this::confirm))) .then(literal("cancel").executes(this::cancel)) .then(literal("reload").executes(this::reload)) .then(literal("server") @@ -308,7 +309,7 @@ public int reload(CommandContext ctx){ SReplayMod.loadConfig(); ctx.getSource().sendFeedback(render(SReplayMod.getFormats().reloadedConfig), false); return 1; - } catch (IOException e) { + } catch (IOException | JsonParseException e) { e.printStackTrace(); ctx.getSource().sendFeedback(render(SReplayMod.getFormats().failedToReloadConfig, e.toString()), false); return 0; @@ -316,7 +317,7 @@ public int reload(CommandContext ctx){ } public int confirm(CommandContext ctx) { - final String code = StringArgumentType.getString(ctx, "code"); + final int code = IntegerArgumentType.getInteger(ctx, "code"); if (!cm.confirm(ctx.getSource().getName(), code)) { ctx.getSource().sendFeedback(SReplayMod.getFormats().nothingToConfirm, false); } @@ -347,21 +348,11 @@ public int deleteRecording(CommandContext ctx) { final File rec = new File(SReplayMod.getConfig().savePath, StringArgumentType.getString(ctx, "recording")); final MinecraftServer server = src.getMinecraftServer(); if (rec.exists()) { - String code = Integer.toString(rand.nextInt(100)); src.sendFeedback(render(SReplayMod.getFormats().aboutToDeleteRecording, rec.getName()), true); - src.sendFeedback(render(SReplayMod.getFormats().confirmingHint, code), false); - cm.submit(src.getName(), code, (match, cancelled) -> { - if (!cancelled) { - if (match) { - rec.delete(); - server.getPlayerManager() - .sendToAll(render(SReplayMod.getFormats().deletedRecordingFile, src.getName(), rec.getName())); - } else { - src.sendFeedback(SReplayMod.getFormats().incorrectConfirmationCode, false); - } - } else { - src.sendFeedback(SReplayMod.getFormats().operationCancelled, false); - } + cm.submit(src.getName(), src, () -> { + rec.delete(); + server.getPlayerManager() + .sendToAll(render(SReplayMod.getFormats().deletedRecordingFile, src.getName(), rec.getName())); }); } else { src.sendFeedback(render(SReplayMod.getFormats().fileNotFound, rec.getName()), true); diff --git a/src/main/java/com/hadroncfy/sreplay/config/Formats.java b/src/main/java/com/hadroncfy/sreplay/config/Formats.java index 1881d35..566f4a1 100644 --- a/src/main/java/com/hadroncfy/sreplay/config/Formats.java +++ b/src/main/java/com/hadroncfy/sreplay/config/Formats.java @@ -36,10 +36,14 @@ private static Text red(String s){ recordingFileItem = new LiteralText("- $1($2M) ").setStyle(new Style().setColor(Formatting.GREEN)) .append(new LiteralText("[下载]").setStyle(new Style().setColor(Formatting.BLUE).setClickEvent( new ClickEvent(Action.RUN_COMMAND, "/sreplay get $1") - ))) + ).setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new LiteralText("点击以生成下载链接").setStyle(new Style().setItalic(true).setColor(Formatting.GRAY)) + )))) .append(new LiteralText("[删除]").setStyle(new Style().setColor(Formatting.RED).setClickEvent( new ClickEvent(Action.RUN_COMMAND, "/sreplay delete $1") - ))), + ).setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new LiteralText("点击以删除").setStyle(new Style().setItalic(true).setColor(Formatting.GRAY)) + )))), savingRecordingFile = new LiteralText("[SReplay] 正在保存") .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN))) .append(new LiteralText("的录像文件")), diff --git a/src/main/java/com/hadroncfy/sreplay/server/FileEntry.java b/src/main/java/com/hadroncfy/sreplay/server/FileEntry.java index 3b31b94..7e90c66 100644 --- a/src/main/java/com/hadroncfy/sreplay/server/FileEntry.java +++ b/src/main/java/com/hadroncfy/sreplay/server/FileEntry.java @@ -2,12 +2,31 @@ import java.io.File; import java.util.Date; +import java.util.Random; public class FileEntry { + private static final Random random = new Random(); private final File file; - private final Date expiresOn; + private final String path; + private Date expiresOn; + + private static String randomString(int len){ + final StringBuilder sb = new StringBuilder(); + while (len --> 0){ + int i = random.nextInt(16); + if (i >= 10){ + sb.append((char)(i - 10 + 'a')); + } + else { + sb.append((char)('0' + i)); + } + } + return sb.toString(); + } + public FileEntry(File file, long last){ this.file = file; + path = "/" + randomString(32) + "/" + file.getName(); expiresOn = new Date(new Date().getTime() + last); } @@ -15,6 +34,14 @@ public boolean isExpired(){ return new Date().after(expiresOn); } + public String getPath(){ + return path; + } + + public void touch(long last){ + expiresOn = new Date(new Date().getTime() + last); + } + public File getFile(){ return file; } diff --git a/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java b/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java index 81cc94a..62abc56 100644 --- a/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java +++ b/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java @@ -30,16 +30,16 @@ public HttpHandler(Server server){ @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { final String path = msg.uri(); - LOGGER.info("Got request"); + LOGGER.info("Got request " + path + " from " + ctx.channel().remoteAddress().toString()); server.removeExpiredFiles(); - final FileEntry fileEntry = server.urls.get(path); + final FileEntry fileEntry = server.getFile(path); if (fileEntry == null || !fileEntry.getFile().exists()){ // No no no, no 404 ctx.close(); return; } final File file = fileEntry.getFile(); - server.urls.remove(path); + server.removeFile(fileEntry); final HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(200)); response.headers().set("Content-Type", "application/zip"); HttpUtil.setContentLength(response, file.length()); diff --git a/src/main/java/com/hadroncfy/sreplay/server/Server.java b/src/main/java/com/hadroncfy/sreplay/server/Server.java index ab7216a..0103714 100644 --- a/src/main/java/com/hadroncfy/sreplay/server/Server.java +++ b/src/main/java/com/hadroncfy/sreplay/server/Server.java @@ -3,6 +3,7 @@ import java.io.File; import java.net.InetAddress; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Random; @@ -30,7 +31,6 @@ public class Server { private static final Logger LOGGER = LogManager.getLogger(); - private static final Random random = new Random(); // Stolen from ServerNetworkIo public static final Lazy DEFAULT_CHANNEL = new Lazy<>(() -> { return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); @@ -39,7 +39,8 @@ public class Server { return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); }); - final Map urls = new HashMap<>(); + private final Map urls = new HashMap<>(); + private final Map filesByFile = new HashMap<>(); private ChannelFuture channel; public synchronized ChannelFuture bind(InetAddress address, int port){ @@ -88,32 +89,34 @@ public synchronized ChannelFuture stop(){ } void removeExpiredFiles(){ - for (String path: urls.keySet()){ + for (String path: new HashSet<>(urls.keySet())){ final FileEntry f = urls.get(path); if (f.isExpired()){ - urls.remove(path); + removeFile(f); } } } - private static String randomString(int len){ - final StringBuilder sb = new StringBuilder(); - while (len --> 0){ - int i = random.nextInt(16); - if (i >= 10){ - sb.append((char)(i - 10 + 'a')); - } - else { - sb.append((char)('0' + i)); - } - } - return sb.toString(); + FileEntry getFile(String path){ + return urls.get(path); + } + + void removeFile(FileEntry f){ + urls.remove(f.getPath()); + filesByFile.remove(f.getFile()); } public String addFile(File file, long last){ - final String path = '/' + randomString(32) + '/' + file.getName(); removeExpiredFiles(); - urls.put(path, new FileEntry(file, last)); - return path; + FileEntry fe = filesByFile.get(file); + if (fe == null){ + fe = new FileEntry(file, last); + urls.put(fe.getPath(), fe); + filesByFile.put(file, fe); + } + else { + fe.touch(last); + } + return fe.getPath(); } } \ No newline at end of file