From f9c58a11652713866f3fd8a91800066d4e802e88 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 18:40:46 +0100 Subject: [PATCH] Try raw file name before percent encoding it (closes #536) --- .../java/com/replaymod/core/utils/Utils.java | 43 ++++++++++++++++++- .../recording/gui/GuiSavingReplay.java | 4 +- .../handler/ConnectionEventHandler.java | 2 +- .../replay/gui/screen/GuiReplayViewer.java | 12 +++--- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index b082c276..688edc7c 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -55,10 +55,12 @@ import javax.net.ssl.TrustManagerFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; import java.security.KeyManagementException; import java.security.KeyStore; @@ -169,8 +171,45 @@ public class Utils { private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false); - public static String replayNameToFileName(String replayName) { - return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"; + public static Path replayNameToPath(Path folder, String replayName) { + // If we can, prefer directly using the replay name as the file name + if (isUsable(folder, replayName + ".mcpr")) { + return folder.resolve(replayName + ".mcpr"); + } else { + // otherwise, fall back to percent encoding + return folder.resolve(REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"); + } + } + + /** + * Checks whether a given file name is actually usable with the file system / operating system at the given folder. + */ + private static boolean isUsable(Path folder, String fileName) { + Path path = folder.resolve(fileName); + if (Files.exists(path)) { + return true; // if it already exits, it's definitely usable + } + + // Otherwise, there's no sure way to know, so we just gotta try + try (OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW)) { + outputStream.flush(); + } catch (IOException e) { + return false; + } + + // Looking good, but now we gotta clean up that mess (and Anti-Virus / Cloud Sync are know to lock them) + int attempts = 0; + while (true) { + try { + Files.delete(path); + return true; + } catch (IOException e) { + if (attempts++ > 100) { + LOGGER.warn("Repeatedly failed to clean up temporary test file at " + path + ": ", e); + return false; // while we were able to use it, it's taken now and we can't get it back + } + } + } } public static String fileNameToReplayName(String fileName) { diff --git a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java index 6fb66db9..9e33903a 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java @@ -155,9 +155,9 @@ public class GuiSavingReplay { try { Path replaysFolder = core.folders.getReplayFolder(); - Path newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName)); + Path newPath = Utils.replayNameToPath(replaysFolder, newName); for (int i = 1; Files.exists(newPath); i++) { - newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")")); + newPath = Utils.replayNameToPath(replaysFolder, newName + " (" + i + ")"); } Files.move(path, newPath); } catch (IOException e) { diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index 60a43a50..0211c8e0 100644 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -127,7 +127,7 @@ public class ConnectionEventHandler { } String name = sdf.format(Calendar.getInstance().getTime()); - Path outputPath = core.folders.getRecordingFolder().resolve(Utils.replayNameToFileName(name)); + Path outputPath = Utils.replayNameToPath(core.folders.getRecordingFolder(), name); ReplayFile replayFile = core.files.open(outputPath); replayFile.writeModInfo(ModCompat.getInstalledNetworkMods()); 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 5525618e..9a2dec3c 100644 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -49,6 +49,8 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -132,8 +134,8 @@ public class GuiReplayViewer extends GuiScreen { public final GuiButton renameButton = new GuiButton().onClick(new Runnable() { @Override public void run() { - final File file = list.getSelected().get(0).file; - String name = Utils.fileNameToReplayName(file.getName()); + final Path path = list.getSelected().get(0).file.toPath(); + String name = Utils.fileNameToReplayName(path.getFileName().toString()); final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name); final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this, new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK), @@ -149,16 +151,16 @@ public class GuiReplayViewer extends GuiScreen { } }).onTextChanged(obj -> { popup.getYesButton().setEnabled(!nameField.getText().isEmpty() - && !new File(file.getParentFile(), Utils.replayNameToFileName(nameField.getText())).exists()); + && Files.notExists(Utils.replayNameToPath(path.getParent(), nameField.getText()))); }); popup.onAccept(() -> { // Sanitize their input String newName = nameField.getText().trim(); // This file is what they want - File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(newName)); + Path targetPath = Utils.replayNameToPath(path.getParent(), newName); try { // Finally, try to move it - FileUtils.moveFile(file, targetFile); + Files.move(path, targetPath); } catch (IOException e) { // We failed (might also be their OS) e.printStackTrace();