From 4b8ffd75dd6c92e88778c580cfe2357e9971a54f Mon Sep 17 00:00:00 2001 From: Hadron67 <604700341@qq.com> Date: Fri, 15 May 2020 11:54:37 +0800 Subject: [PATCH] test done --- .../sreplay/command/SReplayCommand.java | 103 ++++++++++++------ .../com/hadroncfy/sreplay/config/Config.java | 1 + .../com/hadroncfy/sreplay/config/Formats.java | 77 ++++++++----- .../hadroncfy/sreplay/recording/Recorder.java | 54 +++++++-- .../hadroncfy/sreplay/server/HttpHandler.java | 15 ++- .../com/hadroncfy/sreplay/server/Server.java | 32 +++--- 6 files changed, 193 insertions(+), 89 deletions(-) diff --git a/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java b/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java index 919daa3..6d3b595 100644 --- a/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java +++ b/src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java @@ -12,6 +12,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.replaymod.replaystudio.data.Marker; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; @@ -30,6 +31,7 @@ import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.server.command.CommandSource.suggestMatching; import static com.hadroncfy.sreplay.recording.Photographer.MCPR; +import static com.hadroncfy.sreplay.config.TextRenderer.render; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -59,7 +61,9 @@ public void register(CommandDispatcher d) { .then(buildPlayerParameterCommand()) .then(literal("pause").executes(this::pause)) .then(literal("resume").executes(this::resume)) - .then(literal("marker").then(argument("marker", StringArgumentType.word()).executes(this::marker))))) + .then(literal("marker").executes(this::getMarkers) + .then(literal("add").then(argument("marker", StringArgumentType.word()).executes(this::marker))) + .then(literal("remove").then(argument("markerId", IntegerArgumentType.integer(1)).executes(this::removeMarker)))))) .then(literal("list").executes(this::listRecordings)) .then(literal("delete").then(argument("recording", StringArgumentType.word()) .suggests(this::suggestRecordingFile) @@ -82,21 +86,52 @@ private CompletableFuture suggestRecordingFile(CommandContext f.getName()), sb); } + private int getMarkers(CommandContext ctx){ + final Photographer p = requirePlayer(ctx); + if (p != null){ + final String name = p.getGameProfile().getName(); + final ServerCommandSource src = ctx.getSource(); + src.sendFeedback(render(SReplayMod.getFormats().markerListTitle, name), false); + int i = 1; + for (Marker marker: p.getRecorder().getMarkers()){ + src.sendFeedback(render(SReplayMod.getFormats().markerListItem, name, Integer.toString(i), marker.getName()), false); + i++; + } + } + return 0; + } + + private int removeMarker(CommandContext ctx){ + final Photographer p = requirePlayer(ctx); + if (p != null){ + final String name = p.getGameProfile().getName(); + final ServerCommandSource src = ctx.getSource(); + final int id = IntegerArgumentType.getInteger(ctx, "markerId") - 1; + if (id < 0 || id >= p.getRecorder().getMarkers().size()){ + src.sendError(SReplayMod.getFormats().invalidMarkerId); + return 1; + } + p.getRecorder().removeMarker(id); + src.sendFeedback(render(SReplayMod.getFormats().markerRemoved, name, Integer.toString(id + 1)), true); + } + return 0; + } + private int getFile(CommandContext ctx){ final String fName = StringArgumentType.getString(ctx, "fileName"); final File f = new File(SReplayMod.getConfig().savePath, fName); if (f.exists()){ - final String path = SReplayMod.getServer().addFile(f, 30000); + final String path = SReplayMod.getServer().addFile(f, SReplayMod.getConfig().downloadTimeout); String url = "http://" + SReplayMod.getConfig().serverHostName; final int port = SReplayMod.getConfig().serverPort; if (port != 80){ url += ":" + port; } url += path; - ctx.getSource().sendFeedback(TextRenderer.render(SReplayMod.getFormats().downloadUrl, url), false); + ctx.getSource().sendFeedback(render(SReplayMod.getFormats().downloadUrl, url), false); } else { - ctx.getSource().sendError(TextRenderer.render(SReplayMod.getFormats().fileNotFound, fName)); + ctx.getSource().sendError(render(SReplayMod.getFormats().fileNotFound, fName)); return 1; } return 0; @@ -105,15 +140,20 @@ private int getFile(CommandContext ctx){ private int startServer(CommandContext ctx){ final ServerCommandSource src = ctx.getSource(); final MinecraftServer server = src.getMinecraftServer(); - final ChannelFuture ch = SReplayMod.getServer().bind(SReplayMod.getConfig().serverHost, SReplayMod.getConfig().serverPort); - ch.addListener(future -> { - if (future.isSuccess()){ - server.getPlayerManager().broadcastChatMessage(SReplayMod.getFormats().serverStarted, true); - } - else { - src.sendError(TextRenderer.render(SReplayMod.getFormats().serverStartFailed, future.cause().getMessage())); - } - }); + try { + final ChannelFuture ch = SReplayMod.getServer().bind(SReplayMod.getConfig().serverHost, SReplayMod.getConfig().serverPort); + ch.addListener(future -> { + if (future.isSuccess()){ + server.getPlayerManager().broadcastChatMessage(SReplayMod.getFormats().serverStarted, true); + } + else { + src.sendError(render(SReplayMod.getFormats().serverStartFailed, future.cause().getMessage())); + } + }); + } + catch(Exception e){ + e.printStackTrace(); + } return 0; } @@ -126,7 +166,7 @@ private int stopServer(CommandContext ctx){ server.getPlayerManager().broadcastChatMessage(SReplayMod.getFormats().serverStopped, true); } else { - src.sendError(TextRenderer.render(SReplayMod.getFormats().serverStopFailed, future.cause().getMessage())); + src.sendError(render(SReplayMod.getFormats().serverStopFailed, future.cause().getMessage())); } }); return 0; @@ -135,7 +175,7 @@ private int stopServer(CommandContext ctx){ private int getName(CommandContext ctx){ Photographer p = requirePlayer(ctx); if (p != null){ - ctx.getSource().sendFeedback(TextRenderer.render(SReplayMod.getFormats().recordingFile, p.getGameProfile().getName(), p.getSaveName()), false); + ctx.getSource().sendFeedback(render(SReplayMod.getFormats().recordingFile, p.getGameProfile().getName(), p.getSaveName()), false); return 1; } return 0; @@ -149,10 +189,11 @@ private int setName(CommandContext ctx){ name = name.substring(0, name.length() - MCPR.length()); } if (Photographer.checkForSaveFileDupe(ctx.getSource().getMinecraftServer(), SReplayMod.getConfig().savePath, name)){ - ctx.getSource().sendError(TextRenderer.render(SReplayMod.getFormats().recordFileExists, name)); + ctx.getSource().sendError(render(SReplayMod.getFormats().recordFileExists, name)); return 0; } p.setSaveName(name); + ctx.getSource().sendFeedback(render(SReplayMod.getFormats().renamedFile, p.getGameProfile().getName(), name), true); return 1; } return 0; @@ -217,7 +258,7 @@ private static Photographer requirePlayer(CommandContext ct } else { try { - ctx.getSource().sendFeedback(TextRenderer.render(SReplayMod.getConfig().formats.playerNotFound, name), true); + ctx.getSource().sendFeedback(render(SReplayMod.getConfig().formats.playerNotFound, name), true); } catch(Exception e){ e.printStackTrace(); @@ -231,7 +272,7 @@ public int marker(CommandContext ctx){ if (p != null){ String name = StringArgumentType.getString(ctx, "marker"); p.getRecorder().addMarker(name); - ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(TextRenderer.render(SReplayMod.getFormats().markerAdded, p.getGameProfile().getName(), name), false); + ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().markerAdded, p.getGameProfile().getName(), name), false); return 1; } else { @@ -243,7 +284,7 @@ public int pause(CommandContext ctx){ Photographer p = requirePlayer(ctx); if (p != null){ p.getRecorder().pauseRecording(); - ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(TextRenderer.render(SReplayMod.getFormats().recordingPaused, p.getGameProfile().getName()), false); + ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().recordingPaused, p.getGameProfile().getName()), false); return 1; } else { @@ -255,7 +296,7 @@ public int resume(CommandContext ctx){ Photographer p = requirePlayer(ctx); if (p != null){ p.getRecorder().resumeRecording(); - ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(TextRenderer.render(SReplayMod.getFormats().recordingResumed, p.getGameProfile().getName()), false); + ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().recordingResumed, p.getGameProfile().getName()), false); return 1; } else { @@ -266,11 +307,11 @@ public int resume(CommandContext ctx){ public int reload(CommandContext ctx){ try { SReplayMod.loadConfig(); - ctx.getSource().sendFeedback(TextRenderer.render(SReplayMod.getFormats().reloadedConfig), false); + ctx.getSource().sendFeedback(render(SReplayMod.getFormats().reloadedConfig), false); return 1; } catch (IOException e) { e.printStackTrace(); - ctx.getSource().sendFeedback(TextRenderer.render(SReplayMod.getFormats().failedToReloadConfig, e.toString()), false); + ctx.getSource().sendFeedback(render(SReplayMod.getFormats().failedToReloadConfig, e.toString()), false); return 0; } } @@ -293,10 +334,10 @@ public int cancel(CommandContext ctx) { public int listRecordings(CommandContext ctx) { final ServerCommandSource src = ctx.getSource(); - src.sendFeedback(SReplayMod.getFormats().recordingFileListHead, true); + src.sendFeedback(SReplayMod.getFormats().recordingFileListHead, false); SReplayMod.listRecordings().forEach(f -> { String size = String.format("%.2f", f.length() / 1024F / 1024F); - src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().recordingFileItem, f.getName(), size), false); + src.sendFeedback(render(SReplayMod.getFormats().recordingFileItem, f.getName(), size), false); }); return 0; @@ -308,14 +349,14 @@ public int deleteRecording(CommandContext ctx) { final MinecraftServer server = src.getMinecraftServer(); if (rec.exists()) { String code = Integer.toString(rand.nextInt(100)); - src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().aboutToDeleteRecording, rec.getName()), true); - src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().confirmingHint, code), false); + 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(TextRenderer.render(SReplayMod.getFormats().deletedRecordingFile, src.getName(), rec.getName())); + .sendToAll(render(SReplayMod.getFormats().deletedRecordingFile, src.getName(), rec.getName())); } else { src.sendFeedback(SReplayMod.getFormats().incorrectConfirmationCode, false); } @@ -324,7 +365,7 @@ public int deleteRecording(CommandContext ctx) { } }); } else { - src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().fileNotFound, rec.getName()), true); + src.sendFeedback(render(SReplayMod.getFormats().fileNotFound, rec.getName()), true); } return 0; } @@ -334,7 +375,7 @@ public int playerTp(CommandContext ctx) { if (p != null){ Vec3d pos = ctx.getSource().getPosition(); p.tp(ctx.getSource().getWorld().getDimension().getType(), pos.x, pos.y, pos.z); - ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(TextRenderer.render(SReplayMod.getFormats().teleportedBotToYou, p.getGameProfile().getName(), ctx.getSource().getName()), true); + ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().teleportedBotToYou, p.getGameProfile().getName(), ctx.getSource().getName()), true); LOGGER.info("Teleported " + p.getGameProfile().getName() + " to " + ctx.getSource().getName()); return 1; } @@ -357,7 +398,7 @@ public int playerSpawn(CommandContext ctx) { return 0; } if (server.getPlayerManager().getPlayer(pName) != null) { - src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().playerIsLoggedIn, pName), true); + src.sendFeedback(render(SReplayMod.getFormats().playerIsLoggedIn, pName), true); return 0; } @@ -373,7 +414,7 @@ public int playerSpawn(CommandContext ctx) { Photographer photographer = Photographer.create(pName, server, src.getWorld().getDimension().getType(), src.getPosition(), SReplayMod.getConfig().savePath); photographer.connect(saveName); } catch (IOException e) { - src.sendFeedback(TextRenderer.render(SReplayMod.getFormats().failedToStartRecording, e.toString()), false); + src.sendFeedback(render(SReplayMod.getFormats().failedToStartRecording, e.toString()), false); e.printStackTrace(); } return 1; diff --git a/src/main/java/com/hadroncfy/sreplay/config/Config.java b/src/main/java/com/hadroncfy/sreplay/config/Config.java index 21010ba..be02653 100644 --- a/src/main/java/com/hadroncfy/sreplay/config/Config.java +++ b/src/main/java/com/hadroncfy/sreplay/config/Config.java @@ -24,6 +24,7 @@ public class Config { public InetAddress serverHost = InetAddress.getLoopbackAddress(); public String serverHostName = "localhost"; public int serverPort = 12346; + public long downloadTimeout = 60000; public long sizeLimit = -1; public long timeLimit = -1; public boolean autoReconnect = true; diff --git a/src/main/java/com/hadroncfy/sreplay/config/Formats.java b/src/main/java/com/hadroncfy/sreplay/config/Formats.java index 5e71945..6b9f7a3 100644 --- a/src/main/java/com/hadroncfy/sreplay/config/Formats.java +++ b/src/main/java/com/hadroncfy/sreplay/config/Formats.java @@ -1,35 +1,38 @@ package com.hadroncfy.sreplay.config; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; import net.minecraft.text.LiteralText; import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.text.ClickEvent.Action; import net.minecraft.util.Formatting; public class Formats { private static Text red(String s){ return new LiteralText(s).setStyle(new Style().setColor(Formatting.RED)); } - public Text playerNotFound = red("玩家$1未找到(或不是录像机器人)"), - recordFileExists = red("录像文件$1已存在"), - reloadedConfig = new LiteralText("已加载配置"), - failedToReloadConfig = red("加载配置失败:$1"), - nothingToConfirm = red("无待确认的操作"), - nothingToCancel = red("无待取消的操作"), - confirmingHint = new LiteralText("使用") + public Text playerNotFound = red("[SReplay] 玩家$1未找到(或不是录像机器人)"), + recordFileExists = red("[SReplay] 录像文件$1已存在"), + reloadedConfig = new LiteralText("[SReplay] 已加载配置"), + failedToReloadConfig = red("[SReplay] 加载配置失败:$1"), + nothingToConfirm = red("[SReplay] 无待确认的操作"), + nothingToCancel = red("[SReplay] 无待取消的操作"), + confirmingHint = new LiteralText("[SReplay] 使用") .append(new LiteralText("/sreplayer confirm $1").setStyle(new Style().setColor(Formatting.GOLD))) .append(new LiteralText("以确认此次操作")), deletedRecordingFile = new LiteralText("$1已删除录像文件$2"), - operationCancelled = new LiteralText("已取消操作"), - incorrectConfirmationCode = red("确认码不匹配"), - fileNotFound = red("文件$1不存在"), - teleportedBotToYou = new LiteralText("已将") + operationCancelled = new LiteralText("[SReplay] 已取消操作"), + incorrectConfirmationCode = red("[SReplay] 确认码不匹配"), + fileNotFound = red("[SReplay] 文件$1不存在"), + teleportedBotToYou = new LiteralText("[SReplay] 已将") .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.DARK_GRAY))) .append(new LiteralText("传送至$2")), - invalidPlayerName = red("非法玩家名"), - playerNameTooLong = red("玩家名长度不能超过16(否则会chunk ban!)"), - playerIsLoggedIn = red("玩家$1已登录"), - failedToStartRecording = red("录制失败:$1"), - recordingFileListHead = new LiteralText("录制文件列表:"), + invalidPlayerName = red("[SReplay] 非法玩家名"), + playerNameTooLong = red("[SReplay] 玩家名长度不能超过16(否则会chunk ban!)"), + playerIsLoggedIn = red("[SReplay] 玩家$1已登录"), + failedToStartRecording = red("[SReplay] 录制失败:$1"), + recordingFileListHead = new LiteralText("[SReplay] 录制文件列表:"), recordingFileItem = new LiteralText("- $1($2M)").setStyle(new Style().setColor(Formatting.GREEN)), savingRecordingFile = new LiteralText("正在保存") .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN))) @@ -38,25 +41,47 @@ private static Text red(String s){ .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN))) .append(new LiteralText("的录像文件")) .append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GREEN))), - failedToSaveRecordingFile = red("保存$1的录像文件失败:$2"), - startedRecording = new LiteralText("$1已开始录制"), - aboutToDeleteRecording = new LiteralText("将要删除录像文件$1"), - recordingFile = new LiteralText("$1正在录制") + failedToSaveRecordingFile = red("[SReplay] 保存$1的录像文件失败:$2"), + startedRecording = new LiteralText("[SReplay] $1已开始录制"), + aboutToDeleteRecording = new LiteralText("[SReplay] 将要删除录像文件$1"), + recordingFile = new LiteralText("[SReplay] $1正在录制") .append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD))), - sizeLimitTooSmall = red("大小限制不能小于10M"), - timeLimitTooSmall = red("时间限制不能小于10s"), - recordingPaused = new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)) + sizeLimitTooSmall = red("[SReplay] 大小限制不能小于10M"), + timeLimitTooSmall = red("[SReplay] 时间限制不能小于10s"), + recordingPaused = new LiteralText("[SReplay] ") + .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD))) .append(new LiteralText("已暂停录制")), - recordingResumed = new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)) + recordingResumed = new LiteralText("[SReplay] ") + .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD))) .append(new LiteralText("已继续开始录制")), - markerAdded = new LiteralText("已在") + markerAdded = new LiteralText("[SReplay] 已在") .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD))) .append(new LiteralText("添加标记")) .append(new LiteralText("$2").setStyle(new Style().setItalic(true).setColor(Formatting.GREEN))), + markerRemoved = new LiteralText("[SReplay] 已在") + .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD))) + .append(new LiteralText("删除标记")) + .append(new LiteralText("$2").setStyle(new Style().setItalic(true).setColor(Formatting.GREEN))), + invalidMarkerId = red("[SReplay] 无效的标记序号"), + markerListTitle = new LiteralText("[SReplay] ") + .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD))) + .append("的所有标记:"), + markerListItem = new LiteralText("- [$2] $3") + .append(new LiteralText("[删除]").setStyle(new Style().setColor(Formatting.GREEN) + .setClickEvent(new ClickEvent(Action.RUN_COMMAND, "/sreplay player $1 marker remove $2")) + )), + renamedFile = new LiteralText("[SReplay] 已将") + .append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN))) + .append(new LiteralText("的文件名设置为")) + .append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GREEN))), serverStarted = new LiteralText("[SReplay] 下载服务器已启动"), serverStartFailed = red("[SReplay] 下载服务器启动失败:$1"), serverStopped = new LiteralText("[SReplay] 下载服务器已停止"), serverStopFailed = new LiteralText("[SReplay] 下载服务器停止失败:$1"), downloadUrl = new LiteralText("[SReplay] 下载链接:") - .append(new LiteralText("$1").setStyle(new Style().setBold(true))); + .append(new LiteralText("$1").setStyle(new Style().setBold(true).setUnderline(true) + .setClickEvent(new ClickEvent(Action.OPEN_URL, "$1")) + .setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new LiteralText("点击以下载").setStyle(new Style().setColor(Formatting.GRAY).setItalic(true)) + )))); } \ No newline at end of file diff --git a/src/main/java/com/hadroncfy/sreplay/recording/Recorder.java b/src/main/java/com/hadroncfy/sreplay/recording/Recorder.java index 20abcb5..c997ad8 100644 --- a/src/main/java/com/hadroncfy/sreplay/recording/Recorder.java +++ b/src/main/java/com/hadroncfy/sreplay/recording/Recorder.java @@ -3,7 +3,9 @@ import java.io.DataOutputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -65,7 +67,7 @@ public class Recorder implements IPacketListener { private boolean paused = false; private boolean gPaused = false; private boolean followTick = false; - private final Set markers = new HashSet<>(); + private final List markers = new ArrayList<>(); private ISizeLimitExceededListener limiter = null; @@ -224,6 +226,44 @@ public void addMarker(String name){ m.setName(name); m.setTime((int) getRecordedTime()); markers.add(m); + + saveMarkers(); + } + + public List getMarkers(){ + return markers; + } + + public void removeMarker(int i){ + markers.remove(i); + saveMarkers(); + } + + private void saveMetadata(){ + saveService.submit(() -> { + String[] players = new String[uuids.size()]; + uuids.stream().map(uuid -> uuid.toString()).collect(Collectors.toList()).toArray(players); + metaData.setPlayers(players); + try { + replayFile.writeMetaData(metaData); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private void saveMarkers(){ + if (markers.size() > 0){ + saveService.submit(() -> { + Set m = new HashSet<>(); + m.addAll(markers); + try { + replayFile.writeMarkers(m); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } } public CompletableFuture saveRecording(File dest) { @@ -233,6 +273,8 @@ public CompletableFuture saveRecording(File dest) { server.getPlayerManager() .broadcastChatMessage(TextRenderer.render(SReplayMod.getFormats().savingRecordingFile, profile.getName()), true); return CompletableFuture.runAsync(() -> { + saveMetadata(); + saveMarkers(); saveService.shutdown(); try { saveService.awaitTermination(10, TimeUnit.SECONDS); @@ -241,15 +283,6 @@ public CompletableFuture saveRecording(File dest) { } try { synchronized (replayFile) { - String[] players = new String[uuids.size()]; - uuids.stream().map(uuid -> uuid.toString()).collect(Collectors.toList()).toArray(players); - metaData.setPlayers(players); - replayFile.writeMetaData(metaData); - - if (markers.size() > 0){ - replayFile.writeMarkers(markers); - } - replayFile.saveTo(dest); replayFile.close(); } @@ -273,6 +306,7 @@ public void onPacket(Packet p) { if (!stopped){ if (p instanceof PlayerSpawnS2CPacket){ uuids.add(((PlayerSpawnS2CPacketAccessor) p).getUUID()); + saveMetadata(); } if (p instanceof DisconnectS2CPacket){ return; diff --git a/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java b/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java index 6f300f2..0aaa8f3 100644 --- a/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java +++ b/src/main/java/com/hadroncfy/sreplay/server/HttpHandler.java @@ -6,10 +6,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.DefaultFileRegion; import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; @@ -38,13 +39,17 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) thro } final File file = fileEntry.getFile(); server.urls.remove(path); - // final RandomAccessFile raf = new RandomAccessFile(file, "r"); - // final long len = raf.length(); - final HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(200)); + final HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(200)); response.headers().set("Content-Type", "application/zip"); HttpUtil.setContentLength(response, file.length()); ctx.write(response); ctx.write(new DefaultFileRegion(file, 0, file.length())); - ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + ChannelFuture ch = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + ch.addListener(future -> { + if (!future.isSuccess()){ + LOGGER.error("Channel error: " + future.cause().getMessage()); + } + ctx.close(); + }); } } \ No newline at end of file diff --git a/src/main/java/com/hadroncfy/sreplay/server/Server.java b/src/main/java/com/hadroncfy/sreplay/server/Server.java index 32966cd..b7476f4 100644 --- a/src/main/java/com/hadroncfy/sreplay/server/Server.java +++ b/src/main/java/com/hadroncfy/sreplay/server/Server.java @@ -15,27 +15,18 @@ import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; -import io.netty.channel.DefaultFileRegion; import io.netty.channel.MultithreadEventLoopGroup; -import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseDecoder; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.stream.ChunkedWriteHandler; import net.minecraft.util.Lazy; public class Server { @@ -52,7 +43,7 @@ public class Server { final Map urls = new HashMap<>(); private ChannelFuture channel; - public ChannelFuture bind(InetAddress address, int port){ + public synchronized ChannelFuture bind(InetAddress address, int port){ Class clazz; Lazy lazy; if (Epoll.isAvailable()){ @@ -63,19 +54,26 @@ public ChannelFuture bind(InetAddress address, int port){ clazz = NioServerSocketChannel.class; lazy = DEFAULT_CHANNEL; } - return channel = new ServerBootstrap().group(lazy.get()).channel(clazz).localAddress(address, port).childHandler(new ChannelInitializer() { + final ChannelFuture channel = new ServerBootstrap().group(lazy.get()).channel(clazz).localAddress(address, port).childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline() .addLast(new HttpRequestDecoder()) - .addLast(new HttpResponseDecoder()) + .addLast(new HttpResponseEncoder()) .addLast(new HttpObjectAggregator(1048576)) + .addLast(new ChunkedWriteHandler()) .addLast(new HttpHandler(Server.this)); } }).bind(); + channel.addListener(future -> { + if (future.isSuccess()){ + this.channel = channel; + } + }); + return channel; } - public ChannelFuture stop(){ + public synchronized ChannelFuture stop(){ return channel.channel().closeFuture(); } @@ -93,10 +91,10 @@ private static String randomString(int len){ while (len --> 0){ int i = random.nextInt(16); if (i >= 10){ - sb.append(i - 10 + 'a'); + sb.append((char)(i - 10 + 'a')); } else { - sb.append('0' + i); + sb.append((char)('0' + i)); } } return sb.toString();