diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 7e90cb10..9971ba6e 100644 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -1,5 +1,6 @@ package com.replaymod.core; +import com.google.common.net.PercentEscaper; import com.replaymod.compat.ReplayModCompat; import com.replaymod.core.gui.GuiBackgroundProcesses; import com.replaymod.core.gui.GuiReplaySettings; @@ -11,6 +12,8 @@ import com.replaymod.extras.ReplayModExtras; import com.replaymod.recording.ReplayModRecording; import com.replaymod.render.ReplayModRender; import com.replaymod.replay.ReplayModReplay; +import com.replaymod.replaystudio.replay.ReplayFile; +import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.studio.ReplayStudio; import com.replaymod.replaystudio.us.myles.ViaVersion.api.protocol.ProtocolVersion; import com.replaymod.replaystudio.util.I18n; @@ -88,13 +91,16 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -239,6 +245,36 @@ public class ReplayMod implements return Files.createDirectories(getReplayFolder().resolve("recording")); } + /** + * Folder in which replay cache files are stored. + * Distinct from the recording folder cause people kept confusing them with recordings. + */ + public Path getCacheFolder() throws IOException { + String str = getSettingsRegistry().get(Setting.CACHE_PATH); + Path path = getMinecraft().runDirectory.toPath().resolve(str); + Files.createDirectories(path); + try { + Files.setAttribute(path, "dos:hidden", true); + } catch (UnsupportedOperationException ignored) { + } + return path; + } + + private static final PercentEscaper CACHE_FILE_NAME_ENCODER = new PercentEscaper("-_ ", false); + + public Path getCachePathForReplay(Path replay) throws IOException { + Path replayFolder = getReplayFolder(); + Path cacheFolder = getCacheFolder(); + Path relative = replayFolder.toAbsolutePath().relativize(replay.toAbsolutePath()); + return cacheFolder.resolve(CACHE_FILE_NAME_ENCODER.escape(relative.toString())); + } + + public Path getReplayPathForCache(Path cache) throws IOException { + String relative = URLDecoder.decode(cache.getFileName().toString(), "UTF-8"); + Path replayFolder = getReplayFolder(); + return replayFolder.resolve(relative); + } + public static final DirectoryResourcePack jGuiResourcePack; public static final String JGUI_RESOURCE_PACK_NAME = "replaymod_jgui"; static { // Note: even preInit is too late and we'd have to issue another resource reload @@ -362,6 +398,23 @@ public class ReplayMod implements runPostStartup(() -> { final long DAYS = 24 * 60 * 60 * 1000; + // Cleanup any cache folders still remaining in the recording folder (we once used to put them there) + try { + Files.walkFileTree(getReplayFolder(), new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + String name = dir.getFileName().toString(); + if (name.endsWith(".mcpr.cache")) { + FileUtils.deleteDirectory(dir.toFile()); + return FileVisitResult.SKIP_SUBTREE; + } + return super.preVisitDirectory(dir, attrs); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + // Cleanup raw folder content three weeks after creation (these are pretty valuable for debugging) try (DirectoryStream paths = Files.newDirectoryStream(getRawReplayFolder())) { for (Path path : paths) { @@ -388,11 +441,10 @@ public class ReplayMod implements } // Cleanup cache folders 7 days after last modification or when its replay is gone - try (DirectoryStream paths = Files.newDirectoryStream(getReplayFolder())) { + try (DirectoryStream paths = Files.newDirectoryStream(getCacheFolder())) { for (Path path : paths) { - String name = path.getFileName().toString(); - if (name.endsWith(".mcpr.cache") && Files.isDirectory(path)) { - Path replay = path.resolveSibling(FilenameUtils.getBaseName(name)); + if (Files.isDirectory(path)) { + Path replay = getReplayPathForCache(path); long lastModified = Files.getLastModifiedTime(path).toMillis(); if (lastModified + 7 * DAYS < System.currentTimeMillis() || !Files.exists(replay)) { FileUtils.deleteDirectory(path.toFile()); @@ -720,4 +772,17 @@ public class ReplayMod implements return new ReplayStudio().isCompatible(fileFormatVersion, protocolVersion, MCVer.getProtocolVersion()); } } + + public ReplayFile openReplay(Path path) throws IOException { + return openReplay(path, path); + } + + public ReplayFile openReplay(Path input, Path output) throws IOException { + return new ZipReplayFile( + new ReplayStudio(), + input != null ? input.toFile() : null, + output.toFile(), + getCachePathForReplay(output).toFile() + ); + } } diff --git a/src/main/java/com/replaymod/core/Setting.java b/src/main/java/com/replaymod/core/Setting.java index 0b6422c2..a5b263b0 100644 --- a/src/main/java/com/replaymod/core/Setting.java +++ b/src/main/java/com/replaymod/core/Setting.java @@ -3,6 +3,7 @@ package com.replaymod.core; public final class Setting extends SettingsRegistry.SettingKeys { public static final Setting NOTIFICATIONS = make("notifications", "notifications", true); public static final Setting RECORDING_PATH = advanced("recordingPath", null, "./replay_recordings/"); + public static final Setting CACHE_PATH = advanced("cachePath", null, "./.replay_cache/"); private static Setting make(String key, String displayName, T defaultValue) { return new Setting<>("core", key, displayName, defaultValue); diff --git a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java index e982d0fb..4d9ba137 100644 --- a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java +++ b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java @@ -9,8 +9,6 @@ import com.replaymod.replaystudio.io.ReplayInputStream; import com.replaymod.replaystudio.io.ReplayOutputStream; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; -import com.replaymod.replaystudio.replay.ZipReplayFile; -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; @@ -110,8 +108,7 @@ public class RestoreReplayGui extends AbstractGuiScreen { } private void tryRecover(Consumer progress) throws IOException { - ReplayStudio studio = new ReplayStudio(); - ReplayFile replayFile = new ZipReplayFile(studio, null, file); + ReplayFile replayFile = ReplayMod.instance.openReplay(file.toPath()); // 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(); diff --git a/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java b/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java index 2729c6d8..1ee86995 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java +++ b/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java @@ -5,8 +5,7 @@ import com.replaymod.core.utils.Utils; import com.replaymod.editor.ReplayModEditor; import com.replaymod.replay.gui.overlay.GuiMarkerTimeline; import com.replaymod.replaystudio.data.Marker; -import com.replaymod.replaystudio.replay.ZipReplayFile; -import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.replay.ReplayFile; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; @@ -61,7 +60,7 @@ public class GuiEditReplay extends AbstractGuiPopup { super(container); this.inputPath = inputPath; - try (ZipReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), inputPath.toFile())) { + try (ReplayFile replayFile = ReplayMod.instance.openReplay(inputPath)) { markers = replayFile.getMarkers().or(HashSet::new); timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers); timeline.setSize(300, 20) @@ -148,7 +147,7 @@ public class GuiEditReplay extends AbstractGuiPopup { ProgressPopup progressPopup = new ProgressPopup(this); new Thread(() -> { - try (ZipReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), inputPath.toFile())) { + try (ReplayFile replayFile = ReplayMod.instance.openReplay(inputPath)) { replayFile.writeMarkers(markers); replayFile.save(); } catch (IOException e) { diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index efa40cb0..eca8f74f 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -3,7 +3,6 @@ package com.replaymod.editor.gui; import com.replaymod.core.ReplayMod; import com.replaymod.core.versions.MCVer; import com.replaymod.replaystudio.PacketData; -import com.replaymod.replaystudio.Studio; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.filter.SquashFilter; import com.replaymod.replaystudio.filter.StreamFilter; @@ -12,14 +11,11 @@ import com.replaymod.replaystudio.io.ReplayOutputStream; import com.replaymod.replaystudio.protocol.PacketTypeRegistry; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; -import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.stream.IteratorStream; -import com.replaymod.replaystudio.studio.ReplayStudio; import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair; import com.replaymod.replaystudio.util.Utils; import org.apache.commons.io.FilenameUtils; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -52,7 +48,7 @@ public class MarkerProcessor { public static final String MARKER_NAME_SPLIT = "_RM_SPLIT"; private static boolean hasWork(Path path) throws IOException { - try (ZipReplayFile inputReplayFile = new ZipReplayFile(new ReplayStudio(), path.toFile())) { + try (ReplayFile inputReplayFile = ReplayMod.instance.openReplay(path)) { return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName() != null && m.getName().startsWith("_RM_")); } } @@ -109,9 +105,10 @@ public class MarkerProcessor { } public static List> apply(Path path, Consumer progress) throws IOException { + ReplayMod mod = ReplayMod.instance; if (!hasWork(path)) { ReplayMetaData metaData; - try (ZipReplayFile inputReplayFile = new ZipReplayFile(new ReplayStudio(), path.toFile())) { + try (ReplayFile inputReplayFile = mod.openReplay(path)) { metaData = inputReplayFile.getMetaData(); } return Collections.singletonList(new Pair<>(path, metaData)); @@ -121,9 +118,7 @@ public class MarkerProcessor { int splitCounter = 0; PacketTypeRegistry registry = MCVer.getPacketTypeRegistry(true); - Studio studio = new ReplayStudio(); SquashFilter squashFilter = new SquashFilter(); - squashFilter.init(studio, null); List> outputPaths = new ArrayList<>(); @@ -135,7 +130,7 @@ public class MarkerProcessor { Files.createDirectories(inputPath.getParent()); Files.move(path, inputPath); - try (ZipReplayFile inputReplayFile = new ZipReplayFile(studio, inputPath.toFile())) { + try (ReplayFile inputReplayFile = mod.openReplay(inputPath)) { List markers = inputReplayFile.getMarkers().or(HashSet::new) .stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList()); Iterator markerIterator = markers.iterator(); @@ -152,8 +147,7 @@ public class MarkerProcessor { while (nextPacket != null && outputFileSuffixes.hasNext()) { Path outputPath = path.resolveSibling(replayName + outputFileSuffixes.next() + ".mcpr"); - File outputFile = outputPath.toFile(); - try (ZipReplayFile outputReplayFile = new ZipReplayFile(studio, null, outputFile)) { + try (ReplayFile outputReplayFile = mod.openReplay(null, outputPath)) { long duration = 0; Set outputMarkers = new HashSet<>(); ReplayMetaData metaData = inputReplayFile.getMetaData(); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index d066e080..43c77206 100644 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -12,8 +12,6 @@ import com.replaymod.recording.mixin.NetworkManagerAccessor; import com.replaymod.recording.packet.PacketListener; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; -import com.replaymod.replaystudio.replay.ZipReplayFile; -import com.replaymod.replaystudio.studio.ReplayStudio; import io.netty.channel.Channel; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ServerInfo; @@ -123,7 +121,7 @@ public class ConnectionEventHandler { String name = sdf.format(Calendar.getInstance().getTime()); Path outputPath = core.getRecordingFolder().resolve(Utils.replayNameToFileName(name)); - ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), outputPath.toFile()); + ReplayFile replayFile = core.openReplay(outputPath); replayFile.writeModInfo(ModCompat.getInstalledNetworkMods()); diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java index 0b12e8ba..cb483a81 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -13,8 +13,7 @@ import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.ReplaySender; import com.replaymod.replaystudio.pathing.path.Timeline; -import com.replaymod.replaystudio.replay.ZipReplayFile; -import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; @@ -216,9 +215,9 @@ public class GuiRenderQueue extends AbstractGuiPopup implements LOGGER.info("Opening replay {} for {} render jobs", next.getKey(), next.getValue().size()); ReplayHandler replayHandler; - ZipReplayFile replayFile = null; + ReplayFile replayFile = null; try { - replayFile = new ZipReplayFile(new ReplayStudio(), next.getKey()); + replayFile = mod.getCore().openReplay(next.getKey().toPath()); replayHandler = mod.startReplay(replayFile, true, false); } catch (IOException e) { Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {}); diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 2c390b3d..0fd60a70 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -20,8 +20,6 @@ import com.replaymod.replay.gui.screen.GuiModCompatWarning; import com.replaymod.replay.handler.GuiHandler; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.replay.ReplayFile; -import com.replaymod.replaystudio.replay.ZipReplayFile; -import com.replaymod.replaystudio.studio.ReplayStudio; import net.minecraft.client.MinecraftClient; import net.minecraft.client.options.KeyBinding; import org.apache.logging.log4j.LogManager; @@ -163,7 +161,7 @@ public class ReplayModReplay implements Module { } public void startReplay(File file) throws IOException { - startReplay(new ZipReplayFile(new ReplayStudio(), file)); + startReplay(core.openReplay(file.toPath())); } public void startReplay(ReplayFile replayFile) throws IOException { diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 80b72555..85188d31 100644 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -22,8 +22,6 @@ import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.Setting; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; -import com.replaymod.replaystudio.replay.ZipReplayFile; -import com.replaymod.replaystudio.studio.ReplayStudio; import de.johni0702.minecraft.gui.container.AbstractGuiContainer; import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; @@ -389,7 +387,7 @@ public class GuiReplayViewer extends GuiScreen { Arrays.sort(files, Comparator.comparingLong(f -> lastModified.computeIfAbsent(f, File::lastModified)).reversed()); for (final File file : files) { if (Thread.interrupted()) break; - try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) { + try (ReplayFile replayFile = ReplayMod.instance.openReplay(file.toPath())) { final Image thumb = Optional.ofNullable(replayFile.getThumbBytes().orNull()).flatMap(stream -> { try (InputStream in = stream) { return Optional.of(Image.read(in));