删除 FileUtils 中基于 java.io.File 的工具方法 (#4493)

This commit is contained in:
Glavo
2025-09-16 20:52:44 +08:00
committed by GitHub
parent b328ed2cc9
commit fc1cce5e5a
24 changed files with 108 additions and 246 deletions

View File

@@ -47,6 +47,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
@@ -92,11 +93,13 @@ public class HMCLGameRepository extends DefaultGameRepository {
case ROOT_FOLDER: case ROOT_FOLDER:
return super.getRunDirectory(id); return super.getRunDirectory(id);
case CUSTOM: case CUSTOM:
File dir = new File(getVersionSetting(id).getGameDir()); try {
if (!FileUtils.isValidPath(dir)) return getVersionRoot(id); return Path.of(getVersionSetting(id).getGameDir()).toFile();
return dir; } catch (InvalidPathException ignored) {
return getVersionRoot(id);
}
default: default:
throw new Error(); throw new AssertionError("Unreachable");
} }
} }
@@ -119,9 +122,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
}); });
try { try {
File file = new File(getBaseDirectory(), "launcher_profiles.json"); Path file = getBaseDirectory().toPath().resolve("launcher_profiles.json");
if (!file.exists() && !versions.isEmpty()) if (!Files.exists(file) && !versions.isEmpty()) {
FileUtils.writeText(file, PROFILE); Files.createDirectories(file.getParent());
Files.writeString(file, PROFILE);
}
} catch (IOException ex) { } catch (IOException ex) {
LOG.warning("Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex); LOG.warning("Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex);
} }
@@ -132,14 +137,14 @@ public class HMCLGameRepository extends DefaultGameRepository {
refreshVersionsAsync().start(); refreshVersionsAsync().start();
} }
private void clean(File directory) throws IOException { private void clean(Path directory) throws IOException {
FileUtils.deleteDirectory(new File(directory, "crash-reports")); FileUtils.deleteDirectory(directory.resolve("crash-reports"));
FileUtils.deleteDirectory(new File(directory, "logs")); FileUtils.deleteDirectory(directory.resolve("logs"));
} }
public void clean(String id) throws IOException { public void clean(String id) throws IOException {
clean(getBaseDirectory()); clean(getBaseDirectory().toPath());
clean(getRunDirectory(id)); clean(getRunDirectory(id).toPath());
} }
public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException { public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {
@@ -270,14 +275,14 @@ public class HMCLGameRepository extends DefaultGameRepository {
} }
public void setVersionIconFile(String id, File iconFile) throws IOException { public void setVersionIconFile(String id, File iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT); String ext = FileUtils.getExtension(iconFile.getName()).toLowerCase(Locale.ROOT);
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) { if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
throw new IllegalArgumentException("Unsupported icon file: " + ext); throw new IllegalArgumentException("Unsupported icon file: " + ext);
} }
deleteIconFile(id); deleteIconFile(id);
FileUtils.copyFile(iconFile, new File(getVersionRoot(id), "icon." + ext)); FileUtils.copyFile(iconFile.toPath(), getVersionRoot(id).toPath().resolve("icon." + ext));
} }
public void deleteIconFile(String id) { public void deleteIconFile(String id) {

View File

@@ -81,7 +81,7 @@ public final class ModpackHelper {
} }
public static boolean isFileModpackByExtension(File file) { public static boolean isFileModpackByExtension(File file) {
String ext = FileUtils.getExtension(file); String ext = FileUtils.getExtension(file.getName());
return "zip".equals(ext) || "mrpack".equals(ext); return "zip".equals(ext) || "mrpack".equals(ext);
} }

View File

@@ -207,7 +207,7 @@ public final class HMCLJavaRepository implements JavaRepository {
public Task<Void> getUninstallJavaTask(Platform platform, String name) { public Task<Void> getUninstallJavaTask(Platform platform, String name) {
return Task.runAsync(() -> { return Task.runAsync(() -> {
Files.deleteIfExists(getManifestFile(platform, name)); Files.deleteIfExists(getManifestFile(platform, name));
FileUtils.deleteDirectory(getJavaDir(platform, name).toFile()); FileUtils.deleteDirectory(getJavaDir(platform, name));
}); });
} }
@@ -220,7 +220,7 @@ public final class HMCLJavaRepository implements JavaRepository {
if (relativized.getNameCount() > 1) { if (relativized.getNameCount() > 1) {
String name = relativized.getName(0).toString(); String name = relativized.getName(0).toString();
Files.deleteIfExists(getManifestFile(java.getPlatform(), name)); Files.deleteIfExists(getManifestFile(java.getPlatform(), name));
FileUtils.deleteDirectory(getJavaDir(java.getPlatform(), name).toFile()); FileUtils.deleteDirectory(getJavaDir(java.getPlatform(), name));
} }
}); });
} }

View File

@@ -460,8 +460,10 @@ public final class FXUtils {
} }
public static void openFolder(File file) { public static void openFolder(File file) {
if (!FileUtils.makeDirectory(file)) { try {
LOG.error("Unable to make directory " + file); Files.createDirectories(file.toPath());
} catch (IOException e) {
LOG.warning("Failed to create directory " + file);
return; return;
} }

View File

@@ -114,7 +114,7 @@ public final class LocalModpackPage extends ModpackPage {
if (!name.isPresent()) { if (!name.isPresent()) {
// trim: https://github.com/HMCL-dev/HMCL/issues/962 // trim: https://github.com/HMCL-dev/HMCL/issues/962
txtModpackName.setText(FileUtils.getNameWithoutExtension(selectedFile)); txtModpackName.setText(FileUtils.getNameWithoutExtension(selectedFile.getName()));
} }
Controllers.confirm(i18n("modpack.type.manual.warning"), i18n("install.modpack"), MessageDialogPane.MessageType.WARNING, Controllers.confirm(i18n("modpack.type.manual.warning"), i18n("install.modpack"), MessageDialogPane.MessageType.WARNING,

View File

@@ -87,8 +87,7 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED; ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
if (basePath.length() > "minecraft/".length()) { if (basePath.length() > "minecraft/".length()) {
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory()); state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory());
if (file.isFile() && Objects.equals(FileUtils.getNameWithoutExtension(file), version)) // Ignore <version>.json, <version>.jar if (file.isFile() && Objects.equals(FileUtils.getNameWithoutExtension(file.getName()), version)) state = ModAdviser.ModSuggestion.HIDDEN;
state = ModAdviser.ModSuggestion.HIDDEN;
if (file.isDirectory() && Objects.equals(file.getName(), version + "-natives")) // Ignore <version>-natives if (file.isDirectory() && Objects.equals(file.getName(), version + "-natives")) // Ignore <version>-natives
state = ModAdviser.ModSuggestion.HIDDEN; state = ModAdviser.ModSuggestion.HIDDEN;
if (state == ModAdviser.ModSuggestion.HIDDEN) if (state == ModAdviser.ModSuggestion.HIDDEN)

View File

@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.IOUtils;
import org.tukaani.xz.XZInputStream; import org.tukaani.xz.XZInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -254,6 +253,9 @@ public final class SettingsPage extends SettingsView {
@Override @Override
protected void clearCacheDirectory() { protected void clearCacheDirectory() {
FileUtils.cleanDirectoryQuietly(new File(Settings.instance().getCommonDirectory(), "cache")); String commonDirectory = Settings.instance().getCommonDirectory();
if (commonDirectory != null) {
FileUtils.cleanDirectoryQuietly(Path.of(commonDirectory, "cache"));
}
} }
} }

View File

@@ -50,7 +50,7 @@ public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.Da
setItems(MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new)); setItems(MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new));
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)), FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it.getName())),
mods -> mods.forEach(this::installSingleDatapack), this::refresh); mods -> mods.forEach(this::installSingleDatapack), this::refresh);
} }

View File

@@ -49,7 +49,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
private String gameVersion; private String gameVersion;
{ {
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "exe").contains(FileUtils.getExtension(it)), mods -> { FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "exe").contains(FileUtils.getExtension(it.getName())), mods -> {
if (!mods.isEmpty()) if (!mods.isEmpty())
doInstallOffline(mods.get(0)); doInstallOffline(mods.get(0));
}); });

View File

@@ -59,7 +59,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
private String versionId; private String versionId;
public ModListPage() { public ModListPage() {
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> { FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it.getName())), mods -> {
mods.forEach(it -> { mods.forEach(it -> {
try { try {
modManager.addMod(it.toPath()); modManager.addMod(it.toPath());

View File

@@ -373,7 +373,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
@Override @Override
void onDelete() { void onDelete() {
try { try {
FileUtils.cleanDirectory(path.toFile()); FileUtils.cleanDirectory(path);
Files.deleteIfExists(path); Files.deleteIfExists(path);
refresh(); refresh();
} catch (IOException e) { } catch (IOException e) {

View File

@@ -166,14 +166,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
} }
private void clearLibraries() { private void clearLibraries() {
FileUtils.deleteDirectoryQuietly(new File(getProfile().getRepository().getBaseDirectory(), "libraries")); FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().toPath().resolve("libraries"));
} }
private void clearAssets() { private void clearAssets() {
HMCLGameRepository baseDirectory = getProfile().getRepository(); HMCLGameRepository baseDirectory = getProfile().getRepository();
FileUtils.deleteDirectoryQuietly(new File(baseDirectory.getBaseDirectory(), "assets")); FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().toPath().resolve("assets"));
if (version.get() != null) { if (version.get() != null) {
FileUtils.deleteDirectoryQuietly(new File(baseDirectory.getRunDirectory(version.get().getVersion()), "resources")); FileUtils.deleteDirectoryQuietly(baseDirectory.getRunDirectory(version.get().getVersion()).toPath().resolve("resources"));
} }
} }

View File

@@ -53,7 +53,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
private String gameVersion; private String gameVersion;
public WorldListPage() { public WorldListPage() {
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> { FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it.getName())), modpacks -> {
installWorld(modpacks.get(0)); installWorld(modpacks.get(0));
}); });

View File

@@ -421,6 +421,6 @@ public class ForgeNewInstallTask extends Task<Version> {
@Override @Override
public void postExecute() throws Exception { public void postExecute() throws Exception {
FileUtils.deleteDirectory(tempDir.toFile()); FileUtils.deleteDirectory(tempDir);
} }
} }

View File

@@ -24,10 +24,11 @@ import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.*; import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@@ -71,12 +72,12 @@ public class ForgeOldInstallTask extends Task<Version> {
// unpack the universal jar in the installer file. // unpack the universal jar in the installer file.
Library forgeLibrary = new Library(installProfile.getInstall().getPath()); Library forgeLibrary = new Library(installProfile.getInstall().getPath());
File forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary); Path forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary).toPath();
if (!FileUtils.makeFile(forgeFile)) Files.createDirectories(forgeFile.getParent());
throw new IOException("Cannot make directory " + forgeFile.getParent());
ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath()); ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath());
try (InputStream is = zipFile.getInputStream(forgeEntry); OutputStream os = new FileOutputStream(forgeFile)) { try (InputStream is = zipFile.getInputStream(forgeEntry);
OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
is.transferTo(os); is.transferTo(os);
} }

View File

@@ -103,7 +103,7 @@ public final class GameLibrariesTask extends Task<Void> {
if (library.getChecksums() != null && !library.getChecksums().isEmpty() && !LibraryDownloadTask.checksumValid(file, library.getChecksums())) { if (library.getChecksums() != null && !library.getChecksums().isEmpty() && !LibraryDownloadTask.checksumValid(file, library.getChecksums())) {
return true; return true;
} }
if (FileUtils.getExtension(file).equals("jar")) { if (FileUtils.getExtension(file.getName()).equals("jar")) {
try { try {
FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(jar, jar); FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(jar, jar);
} catch (IOException ignored) { } catch (IOException ignored) {

View File

@@ -105,7 +105,7 @@ public class LibraryDownloadTask extends Task<Void> {
Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary); Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary);
if (libPath.isPresent()) { if (libPath.isPresent()) {
try { try {
FileUtils.copyFile(libPath.get().toFile(), jar); FileUtils.copyFile(libPath.get(), jar.toPath());
cached = true; cached = true;
return; return;
} catch (IOException e) { } catch (IOException e) {

View File

@@ -417,6 +417,6 @@ public class NeoForgeOldInstallTask extends Task<Version> {
@Override @Override
public void postExecute() throws Exception { public void postExecute() throws Exception {
FileUtils.deleteDirectory(tempDir.toFile()); FileUtils.deleteDirectory(tempDir);
} }
} }

View File

@@ -167,7 +167,7 @@ public final class OptiFineInstallTask extends Task<Version> {
if (Files.exists(launchWrapper2)) { if (Files.exists(launchWrapper2)) {
Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper", "2.0")); Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper", "2.0"));
File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper); File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);
FileUtils.makeDirectory(launchWrapperFile.getAbsoluteFile().getParentFile()); Files.createDirectories(launchWrapperFile.toPath().toAbsolutePath().getParent());
FileUtils.copyFile(launchWrapper2, launchWrapperFile.toPath()); FileUtils.copyFile(launchWrapper2, launchWrapperFile.toPath());
hasLaunchWrapper = true; hasLaunchWrapper = true;
libraries.add(launchWrapper); libraries.add(launchWrapper);
@@ -182,7 +182,7 @@ public final class OptiFineInstallTask extends Task<Version> {
if (Files.exists(launchWrapperJar)) { if (Files.exists(launchWrapperJar)) {
File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper); File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);
FileUtils.makeDirectory(launchWrapperFile.getAbsoluteFile().getParentFile()); Files.createDirectories(launchWrapperFile.toPath().toAbsolutePath().getParent());
FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath()); FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath());
hasLaunchWrapper = true; hasLaunchWrapper = true;

View File

@@ -214,7 +214,7 @@ public class DefaultGameRepository implements GameRepository {
if (fromVersion.getId().equals(fromVersion.getJar())) if (fromVersion.getId().equals(fromVersion.getJar()))
fromVersion = fromVersion.setJar(null); fromVersion = fromVersion.setJar(null);
FileUtils.writeText(toJson, JsonUtils.GSON.toJson(fromVersion.setId(to))); JsonUtils.writeToJsonFile(toJson, fromVersion.setId(to));
// fix inheritsFrom of versions that inherits from version [from]. // fix inheritsFrom of versions that inherits from version [from].
for (Version version : getVersions()) { for (Version version : getVersions()) {
@@ -235,7 +235,7 @@ public class DefaultGameRepository implements GameRepository {
if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY) if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY)
return false; return false;
if (!versions.containsKey(id)) if (!versions.containsKey(id))
return FileUtils.deleteDirectoryQuietly(getVersionRoot(id)); return FileUtils.deleteDirectoryQuietly(getVersionRoot(id).toPath());
File file = getVersionRoot(id); File file = getVersionRoot(id);
if (!file.exists()) if (!file.exists())
return true; return true;
@@ -247,19 +247,23 @@ public class DefaultGameRepository implements GameRepository {
try { try {
versions.remove(id); versions.remove(id);
if (FileUtils.moveToTrash(removedFile)) { if (FileUtils.moveToTrash(removedFile.toPath())) {
return true; return true;
} }
// remove json files first to ensure HMCL will not recognize this folder as a valid version. // remove json files first to ensure HMCL will not recognize this folder as a valid version.
List<File> jsons = FileUtils.listFilesByExtension(removedFile, "json");
jsons.forEach(f -> { for (Path path : FileUtils.listFilesByExtension(removedFile.toPath(), "json")) {
if (!f.delete()) try {
LOG.warning("Unable to delete file " + f); Files.delete(path);
}); } catch (IOException e) {
LOG.warning("Failed to delete file " + path, e);
}
}
// remove the version from version list regardless of whether the directory was removed successfully or not. // remove the version from version list regardless of whether the directory was removed successfully or not.
try { try {
FileUtils.deleteDirectory(removedFile); FileUtils.deleteDirectory(removedFile.toPath());
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Unable to remove version folder: " + file, e); LOG.warning("Unable to remove version folder: " + file, e);
} }
@@ -288,10 +292,10 @@ public class DefaultGameRepository implements GameRepository {
// If user renamed the json file by mistake or created the json file in a wrong name, // If user renamed the json file by mistake or created the json file in a wrong name,
// we will find the only json and rename it to correct name. // we will find the only json and rename it to correct name.
if (!json.exists()) { if (!json.exists()) {
List<File> jsons = FileUtils.listFilesByExtension(dir, "json"); List<Path> jsons = FileUtils.listFilesByExtension(dir.toPath(), "json");
if (jsons.size() == 1) { if (jsons.size() == 1) {
LOG.info("Renaming json file " + jsons.get(0) + " to " + json); LOG.info("Renaming json file " + jsons.get(0) + " to " + json);
if (!jsons.get(0).renameTo(json)) { if (!jsons.get(0).toFile().renameTo(json)) {
LOG.warning("Cannot rename json file, ignoring version " + id); LOG.warning("Cannot rename json file, ignoring version " + id);
return Stream.empty(); return Stream.empty();
} }

View File

@@ -378,7 +378,7 @@ public class DefaultLauncher extends Launcher {
public void decompressNatives(File destination) throws NotDecompressingNativesException { public void decompressNatives(File destination) throws NotDecompressingNativesException {
try { try {
FileUtils.cleanDirectoryQuietly(destination); FileUtils.cleanDirectoryQuietly(destination.toPath());
for (Library library : version.getLibraries()) for (Library library : version.getLibraries())
if (library.isNative()) if (library.isNative())
new Unzipper(repository.getLibraryFile(version, library), destination) new Unzipper(repository.getLibraryFile(version, library), destination)
@@ -596,7 +596,7 @@ public class DefaultLauncher extends Launcher {
if (isUsingLog4j()) if (isUsingLog4j())
extractLog4jConfigurationFile(); extractLog4jConfigurationFile();
String scriptExtension = FileUtils.getExtension(scriptFile); String scriptExtension = FileUtils.getExtension(scriptFile.getName());
boolean usePowerShell = "ps1".equals(scriptExtension); boolean usePowerShell = "ps1".equals(scriptExtension);
if (!usePowerShell) { if (!usePowerShell) {
@@ -616,8 +616,7 @@ public class DefaultLauncher extends Launcher {
} }
} }
if (!FileUtils.makeFile(scriptFile)) Files.createDirectories(scriptFile.toPath().getParent());
throw new IOException("Script file: " + scriptFile + " cannot be created.");
try (OutputStream outputStream = Files.newOutputStream(scriptFile.toPath())) { try (OutputStream outputStream = Files.newOutputStream(scriptFile.toPath())) {
Charset charset = StandardCharsets.UTF_8; Charset charset = StandardCharsets.UTF_8;

View File

@@ -63,7 +63,7 @@ public class Datapack {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(datapacks)) { try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(datapacks)) {
for (Path datapack : directoryStream) { for (Path datapack : directoryStream) {
if (Files.isDirectory(datapack) && packs.contains(FileUtils.getName(datapack))) if (Files.isDirectory(datapack) && packs.contains(FileUtils.getName(datapack)))
FileUtils.deleteDirectory(datapack.toFile()); FileUtils.deleteDirectory(datapack);
else if (Files.isRegularFile(datapack) && packs.contains(FileUtils.getNameWithoutExtension(datapack))) else if (Files.isRegularFile(datapack) && packs.contains(FileUtils.getNameWithoutExtension(datapack)))
Files.delete(datapack); Files.delete(datapack);
} }
@@ -106,14 +106,14 @@ public class Datapack {
Files.delete(packPng); Files.delete(packPng);
} }
} else { } else {
FileUtils.copyFile(path.toFile(), datapacks.resolve(FileUtils.getName(path)).toFile()); FileUtils.copyFile(path, datapacks.resolve(FileUtils.getName(path)));
} }
} }
public void deletePack(Pack pack) throws IOException { public void deletePack(Pack pack) throws IOException {
Path subPath = pack.file; Path subPath = pack.file;
if (Files.isDirectory(subPath)) if (Files.isDirectory(subPath))
FileUtils.deleteDirectory(subPath.toFile()); FileUtils.deleteDirectory(subPath);
else if (Files.isRegularFile(subPath)) else if (Files.isRegularFile(subPath))
Files.delete(subPath); Files.delete(subPath);

View File

@@ -19,11 +19,9 @@ package org.jackhuang.hmcl.mod;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.Unzipper; import org.jackhuang.hmcl.util.io.Unzipper;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
@@ -63,8 +61,7 @@ public class ModpackInstallTask<T> extends Task<Void> {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
Set<String> entries = new HashSet<>(); Set<String> entries = new HashSet<>();
if (!FileUtils.makeDirectory(dest)) Files.createDirectories(dest.toPath());
throw new IOException("Unable to make directory " + dest);
HashMap<String, ModpackConfiguration.FileInformation> files = new HashMap<>(); HashMap<String, ModpackConfiguration.FileInformation> files = new HashMap<>();
for (ModpackConfiguration.FileInformation file : overrides) for (ModpackConfiguration.FileInformation file : overrides)

View File

@@ -17,16 +17,19 @@
*/ */
package org.jackhuang.hmcl.util.io; package org.jackhuang.hmcl.util.io;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.glavo.chardet.DetectedCharset; import org.glavo.chardet.DetectedCharset;
import org.glavo.chardet.UniversalDetector; import org.glavo.chardet.UniversalDetector;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer; import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*; import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -34,6 +37,7 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -81,16 +85,12 @@ public final class FileUtils {
return StringUtils.substringBeforeLast(fileName, '.'); return StringUtils.substringBeforeLast(fileName, '.');
} }
public static String getNameWithoutExtension(File file) {
return StringUtils.substringBeforeLast(file.getName(), '.');
}
public static String getNameWithoutExtension(Path file) { public static String getNameWithoutExtension(Path file) {
return StringUtils.substringBeforeLast(getName(file), '.'); return StringUtils.substringBeforeLast(getName(file), '.');
} }
public static String getExtension(File file) { public static String getExtension(String fileName) {
return StringUtils.substringAfterLast(file.getName(), '.'); return StringUtils.substringAfterLast(fileName, '.');
} }
public static String getExtension(Path file) { public static String getExtension(Path file) {
@@ -105,13 +105,8 @@ public final class FileUtils {
} }
public static String getName(Path path) { public static String getName(Path path) {
if (path.getFileName() == null) return ""; Path fileName = path.getFileName();
return StringUtils.removeSuffix(path.getFileName().toString(), "/", "\\"); return fileName != null ? fileName.toString() : "";
}
public static String getName(Path path, String candidate) {
if (path.getFileName() == null) return candidate;
else return getName(path);
} }
// https://learn.microsoft.com/biztalk/core/restrictions-when-configuring-the-file-adapter // https://learn.microsoft.com/biztalk/core/restrictions-when-configuring-the-file-adapter
@@ -210,21 +205,6 @@ public final class FileUtils {
return new String(bytes, OperatingSystem.NATIVE_CHARSET); return new String(bytes, OperatingSystem.NATIVE_CHARSET);
} }
/**
* Write plain text to file. Characters are encoded into bytes using UTF-8.
* <p>
* We don't care about platform difference of line separator. Because readText accept all possibilities of line separator.
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
* All characters in text will be written into the file in binary format. Existing data will be erased.
*
* @param file the path to the file
* @param text the text being written to file
* @throws IOException if an I/O error occurs
*/
public static void writeText(File file, String text) throws IOException {
writeText(file.toPath(), text);
}
/** /**
* Write plain text to file. Characters are encoded into bytes using UTF-8. * Write plain text to file. Characters are encoded into bytes using UTF-8.
* <p> * <p>
@@ -241,19 +221,6 @@ public final class FileUtils {
Files.writeString(file, text); Files.writeString(file, text);
} }
/**
* Write byte array to file.
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
* All bytes in byte array will be written into the file in binary format. Existing data will be erased.
*
* @param file the path to the file
* @param data the data being written to file
* @throws IOException if an I/O error occurs
*/
public static void writeBytes(File file, byte[] data) throws IOException {
writeBytes(file.toPath(), data);
}
/** /**
* Write byte array to file. * Write byte array to file.
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting. * It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
@@ -278,22 +245,7 @@ public final class FileUtils {
Files.deleteIfExists(directory); Files.deleteIfExists(directory);
} }
public static void deleteDirectory(File directory) public static boolean deleteDirectoryQuietly(Path directory) {
throws IOException {
if (!directory.exists())
return;
if (!isSymlink(directory))
cleanDirectory(directory);
if (!directory.delete()) {
String message = "Unable to delete directory " + directory + ".";
throw new IOException(message);
}
}
public static boolean deleteDirectoryQuietly(File directory) {
try { try {
deleteDirectory(directory); deleteDirectory(directory);
return true; return true;
@@ -315,7 +267,7 @@ public final class FileUtils {
} }
public static void copyDirectory(Path src, Path dest, Predicate<String> filePredicate) throws IOException { public static void copyDirectory(Path src, Path dest, Predicate<String> filePredicate) throws IOException {
Files.walkFileTree(src, new SimpleFileVisitor<Path>() { Files.walkFileTree(src, new SimpleFileVisitor<>() {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (!filePredicate.test(src.relativize(file).toString())) { if (!filePredicate.test(src.relativize(file).toString())) {
@@ -373,9 +325,9 @@ public final class FileUtils {
* @param file the file being moved to trash. * @param file the file being moved to trash.
* @return false if moveToTrash does not exist, or platform does not support Desktop.Action.MOVE_TO_TRASH * @return false if moveToTrash does not exist, or platform does not support Desktop.Action.MOVE_TO_TRASH
*/ */
public static boolean moveToTrash(File file) { public static boolean moveToTrash(Path file) {
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && hasKnownDesktop()) { if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && hasKnownDesktop()) {
if (!file.exists()) { if (!Files.exists(file)) {
return false; return false;
} }
@@ -395,7 +347,7 @@ public final class FileUtils {
Files.createDirectories(infoDir); Files.createDirectories(infoDir);
Files.createDirectories(filesDir); Files.createDirectories(filesDir);
String name = file.getName(); String name = getName(file);
Path infoFile = infoDir.resolve(name + ".trashinfo"); Path infoFile = infoDir.resolve(name + ".trashinfo");
Path targetFile = filesDir.resolve(name); Path targetFile = filesDir.resolve(name);
@@ -408,13 +360,13 @@ public final class FileUtils {
} }
String time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS)); String time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS));
if (file.isDirectory()) { if (Files.isDirectory(file)) {
FileUtils.copyDirectory(file.toPath(), targetFile); FileUtils.copyDirectory(file, targetFile);
} else { } else {
FileUtils.copyFile(file.toPath(), targetFile); FileUtils.copyFile(file, targetFile);
} }
FileUtils.writeText(infoFile, "[Trash Info]\nPath=" + file.getAbsolutePath() + "\nDeletionDate=" + time + "\n"); FileUtils.writeText(infoFile, "[Trash Info]\nPath=" + file.toAbsolutePath().normalize() + "\nDeletionDate=" + time + "\n");
FileUtils.forceDelete(file); FileUtils.forceDelete(file);
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Failed to move " + file + " to trash", e); LOG.warning("Failed to move " + file + " to trash", e);
@@ -425,7 +377,7 @@ public final class FileUtils {
} }
try { try {
return java.awt.Desktop.getDesktop().moveToTrash(file); return java.awt.Desktop.getDesktop().moveToTrash(file.toFile());
} catch (Exception e) { } catch (Exception e) {
return false; return false;
} }
@@ -460,36 +412,8 @@ public final class FileUtils {
}); });
} }
public static void cleanDirectory(File directory) @CanIgnoreReturnValue
throws IOException { public static boolean cleanDirectoryQuietly(Path directory) {
if (!directory.exists()) {
if (!makeDirectory(directory))
throw new IOException("Failed to create directory: " + directory);
return;
}
if (!directory.isDirectory()) {
String message = directory + " is not a directory";
throw new IllegalArgumentException(message);
}
File[] files = directory.listFiles();
if (files == null)
throw new IOException("Failed to list contents of " + directory);
IOException exception = null;
for (File file : files)
try {
forceDelete(file);
} catch (IOException ioe) {
exception = ioe;
}
if (null != exception)
throw exception;
}
public static boolean cleanDirectoryQuietly(File directory) {
try { try {
cleanDirectory(directory); cleanDirectory(directory);
return true; return true;
@@ -498,53 +422,12 @@ public final class FileUtils {
} }
} }
public static void forceDelete(File file) public static void forceDelete(Path file)
throws IOException { throws IOException {
if (file.isDirectory()) { if (Files.isDirectory(file))
deleteDirectory(file); deleteDirectory(file);
} else { else
boolean filePresent = file.exists(); Files.delete(file);
if (!file.delete()) {
if (!filePresent)
throw new FileNotFoundException("File does not exist: " + file);
throw new IOException("Unable to delete file: " + file);
}
}
}
public static boolean isSymlink(File file)
throws IOException {
Objects.requireNonNull(file, "File must not be null");
if (File.separatorChar == '\\')
return false;
File fileInCanonicalDir;
if (file.getParent() == null)
fileInCanonicalDir = file;
else {
File canonicalDir = file.getParentFile().getCanonicalFile();
fileInCanonicalDir = new File(canonicalDir, file.getName());
}
return !fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile());
}
public static void copyFile(File srcFile, File destFile)
throws IOException {
Objects.requireNonNull(srcFile, "Source must not be null");
Objects.requireNonNull(destFile, "Destination must not be null");
if (!srcFile.exists())
throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
if (srcFile.isDirectory())
throw new IOException("Source '" + srcFile + "' exists but is a directory");
if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath()))
throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
File parentFile = destFile.getParentFile();
if (parentFile != null && !FileUtils.makeDirectory(parentFile))
throw new IOException("Destination '" + parentFile + "' directory cannot be created");
if (destFile.exists() && !destFile.canWrite())
throw new IOException("Destination '" + destFile + "' exists but is read-only");
Files.copy(srcFile.toPath(), destFile.toPath(), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
} }
public static void copyFile(Path srcFile, Path destFile) public static void copyFile(Path srcFile, Path destFile)
@@ -555,50 +438,20 @@ public final class FileUtils {
throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
if (Files.isDirectory(srcFile)) if (Files.isDirectory(srcFile))
throw new IOException("Source '" + srcFile + "' exists but is a directory"); throw new IOException("Source '" + srcFile + "' exists but is a directory");
Path parentFile = destFile.getParent(); Files.createDirectories(destFile.getParent());
Files.createDirectories(parentFile);
if (Files.exists(destFile) && !Files.isWritable(destFile)) if (Files.exists(destFile) && !Files.isWritable(destFile))
throw new IOException("Destination '" + destFile + "' exists but is read-only"); throw new IOException("Destination '" + destFile + "' exists but is read-only");
Files.copy(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); Files.copy(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
} }
public static void moveFile(File srcFile, File destFile) throws IOException { public static List<Path> listFilesByExtension(Path file, String extension) {
copyFile(srcFile, destFile); try (Stream<Path> list = Files.list(file)) {
srcFile.delete(); return list.filter(it -> Files.isRegularFile(it) && extension.equals(getExtension(it)))
} .toList();
} catch (IOException e) {
public static boolean makeDirectory(File directory) { LOG.warning("Failed to list files by extension " + extension, e);
directory.mkdirs(); return List.of();
return directory.isDirectory();
}
public static boolean makeFile(File file) {
return makeDirectory(file.getAbsoluteFile().getParentFile()) && (file.exists() || Lang.test(file::createNewFile));
}
public static List<File> listFilesByExtension(File file, String extension) {
List<File> result = new ArrayList<>();
File[] files = file.listFiles();
if (files != null)
for (File it : files)
if (extension.equals(getExtension(it)))
result.add(it);
return result;
}
/**
* Tests whether the file is convertible to [java.nio.file.Path] or not.
*
* @param file the file to be tested
* @return true if the file is convertible to Path.
*/
public static boolean isValidPath(File file) {
try {
file.toPath();
return true;
} catch (InvalidPathException ignored) {
return false;
} }
} }