diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/SchematicsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/SchematicsPage.java index b7e21447c3..d0f7b4a047 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/SchematicsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/SchematicsPage.java @@ -41,6 +41,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.FileNameSet; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; @@ -161,10 +162,20 @@ public void onCreateDirectory() { return; Path parent = currentDirectory.path; + + FileNameSet existingPaths; + try { + existingPaths = FileNameSet.list(parent, null); + } catch (Exception e) { + LOG.warning("Failed to list folders in " + parent, e); + existingPaths = new FileNameSet(false); + } + Controllers.prompt( i18n("schematics.create_directory.prompt"), (result, handler) -> { Path targetDir = parent.resolve(result); + if (Files.exists(targetDir)) { handler.reject(i18n("schematics.create_directory.failed.already_exists")); return; @@ -178,7 +189,11 @@ public void onCreateDirectory() { LOG.warning("Failed to create directory: " + targetDir, e); handler.reject(i18n("schematics.create_directory.failed", targetDir)); } - }, "", new RequiredValidator(), new Validator(i18n("schematics.create_directory.failed.invalid_name"), FileUtils::isNameValid)); + }, + "", + new RequiredValidator(), + new Validator(i18n("schematics.create_directory.failed.invalid_name"), FileUtils::isNameValid), + new Validator(i18n("schematics.create_directory.failed.already_exists"), existingPaths::notContains)); } private DirItem loadAll(Path dir, @Nullable DirItem parent) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileNameSet.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileNameSet.java new file mode 100644 index 0000000000..d8ffaabec8 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileNameSet.java @@ -0,0 +1,96 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util; + +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jetbrains.annotations.NotNullByDefault; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Locale; +import java.util.function.Predicate; + +/// @author Glavo +@NotNullByDefault +public final class FileNameSet { + public static FileNameSet list(Path directory, @Nullable Predicate predicate) throws IOException { + try (var list = Files.list(directory)) { + boolean caseSensitive = OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS + || directory.getFileSystem() != FileSystems.getDefault(); + var set = new FileNameSet(caseSensitive); + + list.forEachOrdered(path -> { + if (predicate == null || predicate.test(path)) { + set.add(path.getFileName().toString()); + } + }); + + return set; + } + } + + private final boolean caseSensitive; + + private final HashSet set = new HashSet<>(); + + public FileNameSet(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public boolean add(String name) { + if (caseSensitive) { + return set.add(name); + } else { + return set.add(name.toLowerCase(Locale.ROOT)); + } + } + + public boolean contains(String name) { + if (set.isEmpty()) + return false; + + if (caseSensitive) { + return set.contains(name); + } else { + return set.contains(name.toLowerCase(Locale.ROOT)); + } + } + + public boolean notContains(String name) { + return !contains(name); + } + + @Override + public int hashCode() { + return Boolean.hashCode(caseSensitive) ^ set.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof FileNameSet that && this.caseSensitive == that.caseSensitive && this.set.equals(that.set); + } + + @Override + public String toString() { + return set.toString(); + } +}