Split replay folder and file management into their own classes
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
package com.replaymod.core;
|
package com.replaymod.core;
|
||||||
|
|
||||||
import com.google.common.net.PercentEscaper;
|
|
||||||
import com.replaymod.compat.ReplayModCompat;
|
import com.replaymod.compat.ReplayModCompat;
|
||||||
|
import com.replaymod.core.files.ReplayFilesService;
|
||||||
|
import com.replaymod.core.files.ReplayFoldersService;
|
||||||
import com.replaymod.core.gui.GuiBackgroundProcesses;
|
import com.replaymod.core.gui.GuiBackgroundProcesses;
|
||||||
import com.replaymod.core.gui.GuiReplaySettings;
|
import com.replaymod.core.gui.GuiReplaySettings;
|
||||||
import com.replaymod.core.gui.RestoreReplayGui;
|
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.core.versions.scheduler.Scheduler;
|
import com.replaymod.core.versions.scheduler.Scheduler;
|
||||||
import com.replaymod.core.versions.scheduler.SchedulerImpl;
|
import com.replaymod.core.versions.scheduler.SchedulerImpl;
|
||||||
@@ -14,12 +14,9 @@ import com.replaymod.recording.ReplayModRecording;
|
|||||||
import com.replaymod.render.ReplayModRender;
|
import com.replaymod.render.ReplayModRender;
|
||||||
import com.replaymod.replay.ReplayModReplay;
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
import com.replaymod.replaystudio.lib.viaversion.api.protocol.version.ProtocolVersion;
|
import com.replaymod.replaystudio.lib.viaversion.api.protocol.version.ProtocolVersion;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||||
import com.replaymod.replaystudio.util.I18n;
|
import com.replaymod.replaystudio.util.I18n;
|
||||||
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
||||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.options.Option;
|
import net.minecraft.client.options.Option;
|
||||||
import net.minecraft.resource.DirectoryResourcePack;
|
import net.minecraft.resource.DirectoryResourcePack;
|
||||||
@@ -29,21 +26,12 @@ import net.minecraft.text.Text;
|
|||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@@ -73,6 +61,8 @@ public class ReplayMod implements Module, Scheduler {
|
|||||||
private final List<Module> modules = new ArrayList<>();
|
private final List<Module> modules = new ArrayList<>();
|
||||||
|
|
||||||
private final GuiBackgroundProcesses backgroundProcesses = new GuiBackgroundProcesses();
|
private final GuiBackgroundProcesses backgroundProcesses = new GuiBackgroundProcesses();
|
||||||
|
public final ReplayFoldersService folders = new ReplayFoldersService(settingsRegistry);
|
||||||
|
public final ReplayFilesService files = new ReplayFilesService(folders);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current MC version is supported by the embedded ReplayStudio version.
|
* Whether the current MC version is supported by the embedded ReplayStudio version.
|
||||||
@@ -116,58 +106,6 @@ public class ReplayMod implements Module, Scheduler {
|
|||||||
return settingsRegistry;
|
return settingsRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getReplayFolder() throws IOException {
|
|
||||||
String str = getSettingsRegistry().get(Setting.RECORDING_PATH);
|
|
||||||
return Files.createDirectories(getMinecraft().runDirectory.toPath().resolve(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Folder into which replay backups are saved before the MarkerProcessor is unleashed.
|
|
||||||
*/
|
|
||||||
public Path getRawReplayFolder() throws IOException {
|
|
||||||
return Files.createDirectories(getReplayFolder().resolve("raw"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Folder into which replays are recorded.
|
|
||||||
* Distinct from the main folder, so they cannot be opened while they are still saving.
|
|
||||||
*/
|
|
||||||
public Path getRecordingFolder() throws IOException {
|
|
||||||
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) {
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
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 = createJGuiResourcePack();
|
public static final DirectoryResourcePack jGuiResourcePack = createJGuiResourcePack();
|
||||||
public static final String JGUI_RESOURCE_PACK_NAME = "replaymod_jgui";
|
public static final String JGUI_RESOURCE_PACK_NAME = "replaymod_jgui";
|
||||||
private static DirectoryResourcePack createJGuiResourcePack() {
|
private static DirectoryResourcePack createJGuiResourcePack() {
|
||||||
@@ -230,114 +168,7 @@ public class ReplayMod implements Module, Scheduler {
|
|||||||
}
|
}
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
runPostStartup(() -> {
|
runPostStartup(() -> files.initialScan(this));
|
||||||
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<Path>() {
|
|
||||||
@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<Path> paths = Files.newDirectoryStream(getRawReplayFolder())) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
if (Files.getLastModifiedTime(path).toMillis() + 21 * DAYS < System.currentTimeMillis()) {
|
|
||||||
Files.delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move anything which is still in the recording folder into the regular replay folder
|
|
||||||
// so it can be opened and/or recovered
|
|
||||||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getRecordingFolder())) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
Path destination = getReplayFolder().resolve(path.getFileName());
|
|
||||||
if (Files.exists(destination)) {
|
|
||||||
continue; // better play it save
|
|
||||||
}
|
|
||||||
Files.move(path, destination);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup cache folders 7 days after last modification or when its replay is gone
|
|
||||||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getCacheFolder())) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup deleted corrupted replays
|
|
||||||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getReplayFolder())) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
String name = path.getFileName().toString();
|
|
||||||
if (name.endsWith(".mcpr.del") && Files.isDirectory(path)) {
|
|
||||||
long lastModified = Files.getLastModifiedTime(path).toMillis();
|
|
||||||
if (lastModified + 2 * DAYS < System.currentTimeMillis()) {
|
|
||||||
FileUtils.deleteDirectory(path.toFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore corrupted replays
|
|
||||||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getReplayFolder())) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
String name = path.getFileName().toString();
|
|
||||||
if (name.endsWith(".mcpr.tmp") && Files.isDirectory(path)) {
|
|
||||||
Path original = path.resolveSibling(FilenameUtils.getBaseName(name));
|
|
||||||
Path noRecoverMarker = original.resolveSibling(original.getFileName() + ".no_recover");
|
|
||||||
if (Files.exists(noRecoverMarker)) {
|
|
||||||
// This file, when its markers are processed, doesn't actually result in any replays.
|
|
||||||
// So we don't really need to recover it either, let's just get rid of it.
|
|
||||||
FileUtils.deleteDirectory(path.toFile());
|
|
||||||
Files.delete(noRecoverMarker);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
new RestoreReplayGui(this, GuiScreen.wrap(mc.currentScreen), original.toFile()).display();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup leftover no_recover files
|
|
||||||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getReplayFolder())) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
String name = path.getFileName().toString();
|
|
||||||
if (name.endsWith(".no_recover")) {
|
|
||||||
Files.delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -437,17 +268,4 @@ public class ReplayMod implements Module, Scheduler {
|
|||||||
return new ReplayStudio().isCompatible(fileFormatVersion, protocolVersion, MCVer.getProtocolVersion());
|
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
154
src/main/java/com/replaymod/core/files/ReplayFilesService.java
Normal file
154
src/main/java/com/replaymod/core/files/ReplayFilesService.java
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package com.replaymod.core.files;
|
||||||
|
|
||||||
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.gui.RestoreReplayGui;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||||
|
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
|
import net.minecraft.util.Util;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class ReplayFilesService {
|
||||||
|
private final ReplayFoldersService folders;
|
||||||
|
|
||||||
|
public ReplayFilesService(ReplayFoldersService folders) {
|
||||||
|
this.folders = folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplayFile open(Path path) throws IOException {
|
||||||
|
return open(path, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplayFile open(Path input, Path output) throws IOException {
|
||||||
|
return new ZipReplayFile(
|
||||||
|
new ReplayStudio(),
|
||||||
|
input != null ? input.toFile() : null,
|
||||||
|
output.toFile(),
|
||||||
|
folders.getCachePathForReplay(output).toFile()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialScan(ReplayMod core) {
|
||||||
|
// Move anything which is still in the recording folder into the regular replay folder
|
||||||
|
// so it can be opened and/or recovered
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(folders.getRecordingFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
Path destination = folders.getReplayFolder().resolve(path.getFileName());
|
||||||
|
if (Files.exists(destination)) {
|
||||||
|
continue; // better play it save
|
||||||
|
}
|
||||||
|
Files.move(path, destination);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore corrupted replays
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(folders.getReplayFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
String name = path.getFileName().toString();
|
||||||
|
if (name.endsWith(".mcpr.tmp") && Files.isDirectory(path)) {
|
||||||
|
Path original = path.resolveSibling(FilenameUtils.getBaseName(name));
|
||||||
|
Path noRecoverMarker = original.resolveSibling(original.getFileName() + ".no_recover");
|
||||||
|
if (Files.exists(noRecoverMarker)) {
|
||||||
|
// This file, when its markers are processed, doesn't actually result in any replays.
|
||||||
|
// So we don't really need to recover it either, let's just get rid of it.
|
||||||
|
FileUtils.deleteDirectory(path.toFile());
|
||||||
|
Files.delete(noRecoverMarker);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
new RestoreReplayGui(core, GuiScreen.wrap(core.getMinecraft().currentScreen), original.toFile()).display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run general purpose, non-essential cleanup in a background thread
|
||||||
|
new Thread(this::cleanup, "replaymod-cleanup").start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
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(folders.getReplayFolder(), new SimpleFileVisitor<Path>() {
|
||||||
|
@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<Path> paths = Files.newDirectoryStream(folders.getRawReplayFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
if (Files.getLastModifiedTime(path).toMillis() + 21 * DAYS < System.currentTimeMillis()) {
|
||||||
|
Files.delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup cache folders 7 days after last modification or when its replay is gone
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(folders.getCacheFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
if (Files.isDirectory(path)) {
|
||||||
|
Path replay = folders.getReplayPathForCache(path);
|
||||||
|
long lastModified = Files.getLastModifiedTime(path).toMillis();
|
||||||
|
if (lastModified + 7 * DAYS < System.currentTimeMillis() || !Files.exists(replay)) {
|
||||||
|
FileUtils.deleteDirectory(path.toFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup deleted corrupted replays
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(folders.getReplayFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
String name = path.getFileName().toString();
|
||||||
|
if (name.endsWith(".mcpr.del") && Files.isDirectory(path)) {
|
||||||
|
long lastModified = Files.getLastModifiedTime(path).toMillis();
|
||||||
|
if (lastModified + 2 * DAYS < System.currentTimeMillis()) {
|
||||||
|
FileUtils.deleteDirectory(path.toFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup leftover no_recover files
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(folders.getReplayFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
String name = path.getFileName().toString();
|
||||||
|
if (name.endsWith(".no_recover")) {
|
||||||
|
Files.delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.replaymod.core.files;
|
||||||
|
|
||||||
|
import com.google.common.net.PercentEscaper;
|
||||||
|
import com.replaymod.core.Setting;
|
||||||
|
import com.replaymod.core.SettingsRegistry;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class ReplayFoldersService {
|
||||||
|
private final Path mcDir = MinecraftClient.getInstance().runDirectory.toPath();
|
||||||
|
private final SettingsRegistry settings;
|
||||||
|
|
||||||
|
public ReplayFoldersService(SettingsRegistry settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getReplayFolder() throws IOException {
|
||||||
|
return Files.createDirectories(mcDir.resolve(settings.get(Setting.RECORDING_PATH)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folder into which replay backups are saved before the MarkerProcessor is unleashed.
|
||||||
|
*/
|
||||||
|
public Path getRawReplayFolder() throws IOException {
|
||||||
|
return Files.createDirectories(getReplayFolder().resolve("raw"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folder into which replays are recorded.
|
||||||
|
* Distinct from the main folder, so they cannot be opened while they are still saving.
|
||||||
|
*/
|
||||||
|
public Path getRecordingFolder() throws IOException {
|
||||||
|
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 {
|
||||||
|
Path path = Files.createDirectories(mcDir.resolve(settings.get(Setting.CACHE_PATH)));
|
||||||
|
try {
|
||||||
|
Files.setAttribute(path, "dos:hidden", true);
|
||||||
|
} catch (UnsupportedOperationException ignored) {
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,7 +108,7 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void tryRecover(Consumer<Float> progress) throws IOException {
|
private void tryRecover(Consumer<Float> progress) throws IOException {
|
||||||
ReplayFile replayFile = ReplayMod.instance.openReplay(file.toPath());
|
ReplayFile replayFile = ReplayMod.instance.files.open(file.toPath());
|
||||||
// Commit all not-yet-committed files into the main zip 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!
|
// If we don't do this, then re-writing packet data below can actually overwrite uncommitted packet data!
|
||||||
replayFile.save();
|
replayFile.save();
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
|||||||
super(container);
|
super(container);
|
||||||
this.inputPath = inputPath;
|
this.inputPath = inputPath;
|
||||||
|
|
||||||
try (ReplayFile replayFile = ReplayMod.instance.openReplay(inputPath)) {
|
try (ReplayFile replayFile = ReplayMod.instance.files.open(inputPath)) {
|
||||||
markers = replayFile.getMarkers().or(HashSet::new);
|
markers = replayFile.getMarkers().or(HashSet::new);
|
||||||
timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers);
|
timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers);
|
||||||
timeline.setSize(300, 20)
|
timeline.setSize(300, 20)
|
||||||
@@ -147,7 +147,7 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
|||||||
ProgressPopup progressPopup = new ProgressPopup(this);
|
ProgressPopup progressPopup = new ProgressPopup(this);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try (ReplayFile replayFile = ReplayMod.instance.openReplay(inputPath)) {
|
try (ReplayFile replayFile = ReplayMod.instance.files.open(inputPath)) {
|
||||||
replayFile.writeMarkers(markers);
|
replayFile.writeMarkers(markers);
|
||||||
replayFile.save();
|
replayFile.save();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class MarkerProcessor {
|
|||||||
public static final String MARKER_NAME_SPLIT = "_RM_SPLIT";
|
public static final String MARKER_NAME_SPLIT = "_RM_SPLIT";
|
||||||
|
|
||||||
private static boolean hasWork(Path path) throws IOException {
|
private static boolean hasWork(Path path) throws IOException {
|
||||||
try (ReplayFile inputReplayFile = ReplayMod.instance.openReplay(path)) {
|
try (ReplayFile inputReplayFile = ReplayMod.instance.files.open(path)) {
|
||||||
return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName() != null && m.getName().startsWith("_RM_"));
|
return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName() != null && m.getName().startsWith("_RM_"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ public class MarkerProcessor {
|
|||||||
ReplayMod mod = ReplayMod.instance;
|
ReplayMod mod = ReplayMod.instance;
|
||||||
if (!hasWork(path)) {
|
if (!hasWork(path)) {
|
||||||
ReplayMetaData metaData;
|
ReplayMetaData metaData;
|
||||||
try (ReplayFile inputReplayFile = mod.openReplay(path)) {
|
try (ReplayFile inputReplayFile = mod.files.open(path)) {
|
||||||
metaData = inputReplayFile.getMetaData();
|
metaData = inputReplayFile.getMetaData();
|
||||||
}
|
}
|
||||||
return Collections.singletonList(Pair.of(path, metaData));
|
return Collections.singletonList(Pair.of(path, metaData));
|
||||||
@@ -128,7 +128,7 @@ public class MarkerProcessor {
|
|||||||
|
|
||||||
List<Pair<Path, ReplayMetaData>> outputPaths = new ArrayList<>();
|
List<Pair<Path, ReplayMetaData>> outputPaths = new ArrayList<>();
|
||||||
|
|
||||||
Path rawFolder = ReplayMod.instance.getRawReplayFolder();
|
Path rawFolder = ReplayMod.instance.folders.getRawReplayFolder();
|
||||||
Path inputPath = rawFolder.resolve(path.getFileName());
|
Path inputPath = rawFolder.resolve(path.getFileName());
|
||||||
for (int i = 1; Files.exists(inputPath); i++) {
|
for (int i = 1; Files.exists(inputPath); i++) {
|
||||||
inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr");
|
inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr");
|
||||||
@@ -136,7 +136,7 @@ public class MarkerProcessor {
|
|||||||
Files.createDirectories(inputPath.getParent());
|
Files.createDirectories(inputPath.getParent());
|
||||||
Files.move(path, inputPath);
|
Files.move(path, inputPath);
|
||||||
|
|
||||||
try (ReplayFile inputReplayFile = mod.openReplay(inputPath)) {
|
try (ReplayFile inputReplayFile = mod.files.open(inputPath)) {
|
||||||
List<Marker> markers = inputReplayFile.getMarkers().or(HashSet::new)
|
List<Marker> markers = inputReplayFile.getMarkers().or(HashSet::new)
|
||||||
.stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList());
|
.stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList());
|
||||||
Iterator<Marker> markerIterator = markers.iterator();
|
Iterator<Marker> markerIterator = markers.iterator();
|
||||||
@@ -153,7 +153,7 @@ public class MarkerProcessor {
|
|||||||
|
|
||||||
while (nextPacket != null && outputFileSuffixes.hasNext()) {
|
while (nextPacket != null && outputFileSuffixes.hasNext()) {
|
||||||
Path outputPath = path.resolveSibling(replayName + outputFileSuffixes.next() + ".mcpr");
|
Path outputPath = path.resolveSibling(replayName + outputFileSuffixes.next() + ".mcpr");
|
||||||
try (ReplayFile outputReplayFile = mod.openReplay(null, outputPath)) {
|
try (ReplayFile outputReplayFile = mod.files.open(null, outputPath)) {
|
||||||
long duration = 0;
|
long duration = 0;
|
||||||
Set<Marker> outputMarkers = new HashSet<>();
|
Set<Marker> outputMarkers = new HashSet<>();
|
||||||
ReplayMetaData metaData = inputReplayFile.getMetaData();
|
ReplayMetaData metaData = inputReplayFile.getMetaData();
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class GuiSavingReplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Path replaysFolder = core.getReplayFolder();
|
Path replaysFolder = core.folders.getReplayFolder();
|
||||||
Path newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName));
|
Path newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName));
|
||||||
for (int i = 1; Files.exists(newPath); i++) {
|
for (int i = 1; Files.exists(newPath); i++) {
|
||||||
newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")"));
|
newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")"));
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ public class ConnectionEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String name = sdf.format(Calendar.getInstance().getTime());
|
String name = sdf.format(Calendar.getInstance().getTime());
|
||||||
Path outputPath = core.getRecordingFolder().resolve(Utils.replayNameToFileName(name));
|
Path outputPath = core.folders.getRecordingFolder().resolve(Utils.replayNameToFileName(name));
|
||||||
ReplayFile replayFile = core.openReplay(outputPath);
|
ReplayFile replayFile = core.files.open(outputPath);
|
||||||
|
|
||||||
replayFile.writeModInfo(ModCompat.getInstalledNetworkMods());
|
replayFile.writeModInfo(ModCompat.getInstalledNetworkMods());
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
|
|||||||
|
|
||||||
// We still have the replay, so we just save it (at least for a few weeks) in case they change their mind
|
// We still have the replay, so we just save it (at least for a few weeks) in case they change their mind
|
||||||
String replayName = FilenameUtils.getBaseName(outputPath.getFileName().toString());
|
String replayName = FilenameUtils.getBaseName(outputPath.getFileName().toString());
|
||||||
Path rawFolder = ReplayMod.instance.getRawReplayFolder();
|
Path rawFolder = ReplayMod.instance.folders.getRawReplayFolder();
|
||||||
Path rawPath = rawFolder.resolve(outputPath.getFileName());
|
Path rawPath = rawFolder.resolve(outputPath.getFileName());
|
||||||
for (int i = 1; Files.exists(rawPath); i++) {
|
for (int i = 1; Files.exists(rawPath); i++) {
|
||||||
rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr");
|
rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr");
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
|
|||||||
ReplayHandler replayHandler;
|
ReplayHandler replayHandler;
|
||||||
ReplayFile replayFile = null;
|
ReplayFile replayFile = null;
|
||||||
try {
|
try {
|
||||||
replayFile = mod.getCore().openReplay(next.getKey().toPath());
|
replayFile = mod.getCore().files.open(next.getKey().toPath());
|
||||||
replayHandler = mod.startReplay(replayFile, true, false);
|
replayHandler = mod.startReplay(replayFile, true, false);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {});
|
Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {});
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class ReplayModReplay implements Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void startReplay(File file) throws IOException {
|
public void startReplay(File file) throws IOException {
|
||||||
startReplay(core.openReplay(file.toPath()));
|
startReplay(core.files.open(file.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startReplay(ReplayFile replayFile) throws IOException {
|
public void startReplay(ReplayFile replayFile) throws IOException {
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
File folder = mod.getCore().getReplayFolder().toFile();
|
File folder = mod.getCore().folders.getReplayFolder().toFile();
|
||||||
|
|
||||||
MCVer.openFile(folder);
|
MCVer.openFile(folder);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -221,7 +221,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
list.setFolder(mod.getCore().getReplayFolder().toFile());
|
list.setFolder(mod.getCore().folders.getReplayFolder().toFile());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CrashException(CrashReport.create(e, "Getting replay folder"));
|
throw new CrashException(CrashReport.create(e, "Getting replay folder"));
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
Arrays.sort(files, Comparator.<File>comparingLong(f -> lastModified.computeIfAbsent(f, File::lastModified)).reversed());
|
Arrays.sort(files, Comparator.<File>comparingLong(f -> lastModified.computeIfAbsent(f, File::lastModified)).reversed());
|
||||||
for (final File file : files) {
|
for (final File file : files) {
|
||||||
if (Thread.interrupted()) break;
|
if (Thread.interrupted()) break;
|
||||||
try (ReplayFile replayFile = ReplayMod.instance.openReplay(file.toPath())) {
|
try (ReplayFile replayFile = ReplayMod.instance.files.open(file.toPath())) {
|
||||||
final Image thumb = Optional.ofNullable(replayFile.getThumbBytes().orNull()).flatMap(stream -> {
|
final Image thumb = Optional.ofNullable(replayFile.getThumbBytes().orNull()).flatMap(stream -> {
|
||||||
try (InputStream in = stream) {
|
try (InputStream in = stream) {
|
||||||
return Optional.of(Image.read(in));
|
return Optional.of(Image.read(in));
|
||||||
|
|||||||
Reference in New Issue
Block a user