Run recovery in background with progress indicator (closes #326)

This commit is contained in:
Jonas Herzig
2020-08-28 16:34:09 +02:00
parent 26f3c3639c
commit ebe6da28fb
2 changed files with 84 additions and 48 deletions

View File

@@ -403,7 +403,7 @@ public class ReplayMod implements
for (File file : files) {
if (file.isDirectory() && file.getName().endsWith(".mcpr.tmp")) {
File origFile = new File(file.getParentFile(), Files.getNameWithoutExtension(file.getName()));
new RestoreReplayGui(GuiScreen.wrap(mc.currentScreen), origFile).display();
new RestoreReplayGui(this, GuiScreen.wrap(mc.currentScreen), origFile).display();
}
}
}

View File

@@ -2,6 +2,8 @@ package com.replaymod.core.gui;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Utils;
import com.replaymod.core.versions.MCVer;
import com.replaymod.replaystudio.io.ReplayInputStream;
import com.replaymod.replaystudio.io.ReplayOutputStream;
@@ -12,22 +14,30 @@ import com.replaymod.replaystudio.studio.ReplayStudio;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.util.crash.CrashReport;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.function.Consumer;
import static com.replaymod.replaystudio.util.Utils.readInt;
import static com.replaymod.replaystudio.util.Utils.writeInt;
public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
private static Logger LOGGER = LogManager.getLogger();
public final GuiScreen parent;
public final File file;
@@ -37,8 +47,10 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
textPanel, buttonPanel).setLayout(new VerticalLayout().setSpacing(20));
public final GuiButton yesButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.yes");
public final GuiButton noButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.no");
private final ReplayMod core;
public RestoreReplayGui(GuiScreen parent, File file) {
public RestoreReplayGui(ReplayMod core, GuiScreen parent, File file) {
this.core = core;
this.parent = parent;
this.file = file;
@@ -47,52 +59,7 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())),
new GuiLabel().setI18nText("replaymod.gui.restorereplay3"));
yesButton.onClick(() -> {
try {
ReplayStudio studio = new ReplayStudio();
ReplayFile replayFile = new ZipReplayFile(studio, null, file);
// Commit all not-yet-committed files into the main zip file.
// If we don't do this, then re-writing packet data below can actually overwrite uncommitted packet data!
replayFile.save();
ReplayMetaData metaData = replayFile.getMetaData();
if (metaData != null && metaData.getDuration() == 0) {
// Try to restore replay duration
// We need to re-write the packet data in case there are any incomplete packets dangling at the end
try (ReplayInputStream in = replayFile.getPacketData(MCVer.getPacketTypeRegistry(true));
ReplayOutputStream out = replayFile.writePacketData()) {
while (true) {
// To prevent failing at un-parsable packets and to support recovery in minimal mode,
// we do not use the ReplayIn/OutputStream methods but instead parse the packets ourselves.
int time = readInt(in);
int length = readInt(in);
if (time == -1 || length == -1) {
break;
}
byte[] buf = new byte[length];
IOUtils.readFully(in, buf);
// Fully read, update replay duration
metaData.setDuration(time);
// Write packet back into recovered replay
writeInt(out, time);
writeInt(out, length);
out.write(buf);
}
} catch (Throwable t) {
t.printStackTrace();
}
// Write back the actual duration
try (OutputStream out = replayFile.write("metaData.json")) {
metaData.setGenerator(metaData.getGenerator() + "(+ ReplayMod Replay Recovery)");
String json = (new Gson()).toJson(metaData);
out.write(json.getBytes());
}
}
replayFile.save();
replayFile.close();
} catch (IOException e) {
e.printStackTrace();
}
recoverInBackground();
parent.display();
});
noButton.onClick(() -> {
@@ -121,4 +88,73 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
protected RestoreReplayGui getThis() {
return this;
}
private void recoverInBackground() {
GuiLabel label = new GuiLabel().setI18nText("replaymod.gui.replaysaving.title").setColor(Colors.BLACK);
GuiProgressBar progressBar = new GuiProgressBar().setHeight(14);
GuiPanel savingProcess = new GuiPanel()
.setLayout(new VerticalLayout())
.addElements(new VerticalLayout.Data(0.5), label, progressBar);
new Thread(() -> {
core.runLater(() -> core.getBackgroundProcesses().addProcess(savingProcess));
try {
tryRecover(progressBar::setProgress);
} catch (IOException e) {
LOGGER.error("Recovering replay file:", e);
CrashReport crashReport = CrashReport.create(e, "Recovering replay file");
core.runLater(() -> Utils.error(LOGGER, VanillaGuiScreen.setup(getMinecraft().currentScreen), crashReport, () -> {}));
} finally {
core.runLater(() -> core.getBackgroundProcesses().removeProcess(savingProcess));
}
}).start();
}
private void tryRecover(Consumer<Float> progress) throws IOException {
ReplayStudio studio = new ReplayStudio();
ReplayFile replayFile = new ZipReplayFile(studio, null, file);
// Commit all not-yet-committed files into the main zip file.
// If we don't do this, then re-writing packet data below can actually overwrite uncommitted packet data!
replayFile.save();
progress.accept(0.4f);
ReplayMetaData metaData = replayFile.getMetaData();
if (metaData != null && metaData.getDuration() == 0) {
// Try to restore replay duration
// We need to re-write the packet data in case there are any incomplete packets dangling at the end
try (ReplayInputStream in = replayFile.getPacketData(MCVer.getPacketTypeRegistry(true));
ReplayOutputStream out = replayFile.writePacketData()) {
while (true) {
// To prevent failing at un-parsable packets and to support recovery in minimal mode,
// we do not use the ReplayIn/OutputStream methods but instead parse the packets ourselves.
int time = readInt(in);
int length = readInt(in);
if (time == -1 || length == -1) {
break;
}
byte[] buf = new byte[length];
IOUtils.readFully(in, buf);
// Fully read, update replay duration
metaData.setDuration(time);
// Write packet back into recovered replay
writeInt(out, time);
writeInt(out, length);
out.write(buf);
}
} catch (Throwable t) {
t.printStackTrace();
}
// Write back the actual duration
try (OutputStream out = replayFile.write("metaData.json")) {
metaData.setGenerator(metaData.getGenerator() + "(+ ReplayMod Replay Recovery)");
String json = (new Gson()).toJson(metaData);
out.write(json.getBytes());
}
}
progress.accept(0.6f);
replayFile.save();
progress.accept(0.9f);
replayFile.close();
progress.accept(1f);
}
}