-
Notifications
You must be signed in to change notification settings - Fork 860
支持清理游戏文件 #5814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
支持清理游戏文件 #5814
Changes from all commits
a8b40c4
d60bb2e
a29d193
dbdd6ff
bca62ef
007a103
7ed8f52
06dd28e
9cc49fb
4bb5305
7244990
1470dfa
60d137d
8588ac6
37b3d98
873501b
db5dcf3
e06f9c6
d213228
292f7f6
b60b52d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,9 @@ | |
| package org.jackhuang.hmcl.ui.versions; | ||
|
|
||
| import com.jfoenix.controls.JFXButton; | ||
| import com.jfoenix.controls.JFXSpinner; | ||
| import javafx.application.Platform; | ||
| import javafx.scene.layout.StackPane; | ||
| import javafx.stage.FileChooser; | ||
| import org.jackhuang.hmcl.auth.Account; | ||
| import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; | ||
|
|
@@ -45,18 +47,22 @@ | |
| import org.jackhuang.hmcl.ui.export.ExportWizardProvider; | ||
| import org.jackhuang.hmcl.util.StringUtils; | ||
| import org.jackhuang.hmcl.util.TaskCancellationAction; | ||
| import org.jackhuang.hmcl.util.i18n.I18n; | ||
| import org.jackhuang.hmcl.util.io.FileUtils; | ||
| import org.jackhuang.hmcl.util.platform.OperatingSystem; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.URI; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.List; | ||
| import java.util.*; | ||
| import java.util.concurrent.CancellationException; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.function.Consumer; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; | ||
| import static org.jackhuang.hmcl.util.i18n.I18n.i18n; | ||
| import static org.jackhuang.hmcl.util.logging.Logger.LOG; | ||
|
|
||
|
|
@@ -235,6 +241,126 @@ public static void updateGameAssets(Profile profile, String version) { | |
| executor.start(); | ||
| } | ||
|
|
||
| public static void cleanGameFiles(Profile profile) { | ||
| var dialogBuilder = new MessageDialogPane.Builder(i18n("game.clean.content", i18n("game.clean.loading")), i18n("message.question"), MessageDialogPane.MessageType.QUESTION); | ||
| var spinner = new JFXSpinner(); | ||
| spinner.getStyleClass().add("small-spinner"); | ||
|
|
||
| StackPane buttonPane = new StackPane(); | ||
|
|
||
| JFXButton okButton = new JFXButton(i18n("button.yes")); | ||
| okButton.getStyleClass().add("dialog-accept"); | ||
|
|
||
| dialogBuilder.addActionNoClose(buttonPane); | ||
| dialogBuilder.addCancel(null); | ||
|
|
||
| var dialog = dialogBuilder.build(); | ||
|
|
||
| Task.supplyAsync(() -> { | ||
| var repository = profile.getRepository(); | ||
| var versions = repository.getVersions(); | ||
|
|
||
| Set<String> activeAssets = versions.stream() | ||
| .filter(Objects::nonNull) | ||
| .map(Version::getAssetIndex) | ||
| .distinct() | ||
| .flatMap(idx -> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The .filter(Objects::nonNull)
.flatMap(idx -> { |
||
| try { | ||
| AssetIndex index = repository.getAssetIndex(null, idx.getId()); | ||
| return index.getObjects().values().stream().map(AssetObject::getLocation); | ||
| } catch (IOException e) { | ||
| LOG.warning("Failed to get asset index", e); | ||
| return Stream.empty(); | ||
|
Comment on lines
+272
to
+273
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If an asset index fails to load (e.g., due to a corrupted file or network issue), returning |
||
| } | ||
|
CiiLu marked this conversation as resolved.
|
||
|
|
||
| }) | ||
| .collect(Collectors.toSet()); | ||
|
|
||
| List<Path> unusedFiles = new ArrayList<>(); | ||
|
|
||
| unusedFiles.addAll(findUnlistedFiles(repository.getBaseDirectory().resolve("assets").resolve("objects"), activeAssets)); | ||
|
|
||
| Set<Path> unusedFolders = new HashSet<>(); | ||
|
|
||
| var uselessFolderNames = Set.of("logs", "crash-reports", "modernfix", "mods/.connector", "CustomSkinLoader/caches", ".fabric"); | ||
| for (String path : uselessFolderNames) { | ||
| unusedFolders.add(repository.getBaseDirectory().resolve(path)); | ||
| } | ||
|
|
||
| versions.stream().map(v -> repository.getRunDirectory(v.getId())).distinct().forEach(runDir -> { | ||
| try (var walker = Files.walk(runDir, 1)) { | ||
| unusedFolders.addAll(walker | ||
| .filter(it -> { | ||
| var name = it.getFileName().toString(); | ||
| return (name.startsWith("natives-") || name.endsWith("-natives") || uselessFolderNames.contains(name)) && Files.isDirectory(it); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check |
||
| }).toList()); | ||
| } catch (IOException ignored) { | ||
| } | ||
| }); | ||
|
|
||
| for (Path dir : unusedFolders) { | ||
| if (Files.exists(dir)) { | ||
| try (var s = Files.walk(dir)) { | ||
| s.filter(Files::isRegularFile).forEach(unusedFiles::add); | ||
|
CiiLu marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation only adds regular files to the |
||
| } catch (IOException ignored) { | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return unusedFiles; | ||
| }).thenApplyAsync((list) -> { | ||
| long totalSize = list.stream() | ||
| .mapToLong(path -> { | ||
| try { | ||
| return Files.size(path); | ||
| } catch (IOException e) { | ||
| return 0L; | ||
| } | ||
| }) | ||
| .sum(); | ||
|
|
||
| FXUtils.runInFX(() -> { | ||
| dialog.setText(i18n("game.clean.content", I18n.formatSize(totalSize))); | ||
| buttonPane.getChildren().setAll(okButton); | ||
| okButton.setDisable(totalSize == 0); | ||
|
|
||
| okButton.setOnAction(event -> { | ||
| onEscPressed(dialog, () -> { | ||
| }); | ||
| dialog.getCancelButton().setDisable(true); | ||
| buttonPane.getChildren().setAll(spinner); | ||
| Task.runAsync(() -> list.forEach(path -> { | ||
| try { | ||
| Files.deleteIfExists(path); | ||
| } catch (IOException e) { | ||
| LOG.warning("Failed to delete file " + path, e); | ||
| } | ||
|
CiiLu marked this conversation as resolved.
|
||
| })).thenRunAsync(Schedulers.javafx(), () -> dialog.fireEvent(new DialogCloseEvent())).start(); | ||
| }); | ||
| }); | ||
| return null; | ||
| }).start(); | ||
|
|
||
| buttonPane.getChildren().setAll(spinner); | ||
|
|
||
| Controllers.dialog(dialog); | ||
| } | ||
|
|
||
| private static List<Path> findUnlistedFiles(Path root, Set<String> activePaths) { | ||
| if (!Files.exists(root)) return List.of(); | ||
| try (var stream = Files.walk(root)) { | ||
| return stream | ||
| .filter(Files::isRegularFile) | ||
| .filter(path -> { | ||
| String relative = root.relativize(path).toString().replace("\\", "/"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return !activePaths.contains(relative); | ||
| }) | ||
| .toList(); | ||
| } catch (IOException e) { | ||
| return List.of(); | ||
| } | ||
| } | ||
|
|
||
| public static void cleanVersion(Profile profile, String id) { | ||
| try { | ||
| profile.getRepository().clean(id); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -375,6 +375,10 @@ download.javafx=正在下載必要的執行時元件 | |||||
| download.javafx.notes=正在透過網路下載 HMCL 必要的執行時元件。\n點擊「切換下載源」按鈕查看詳情以及選取下載源。點擊「取消」按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切換下載源。 | ||||||
| download.javafx.component=正在下載元件「%s」 | ||||||
| download.javafx.prepare=準備開始下載 | ||||||
| download.size.byte=%d B | ||||||
| download.size.kibibyte=%.1f KiB | ||||||
| download.size.megabyte=%.1f MiB | ||||||
| download.size.gigabyte=%.1f GiB | ||||||
| download.speed.byte_per_second=%d B/s | ||||||
| download.speed.kibibyte_per_second=%.1f KiB/s | ||||||
| download.speed.megabyte_per_second=%.1f MiB/s | ||||||
|
|
@@ -437,6 +441,9 @@ folder.screenshots=截圖目錄 | |||||
| folder.world=世界目錄 | ||||||
|
|
||||||
| game=遊戲 | ||||||
| game.clean=清理遊戲文件 | ||||||
| game.clean.loading=统计中... | ||||||
|
||||||
| game.clean.loading=统计中... | |
| game.clean.loading=統計中... |
Uh oh!
There was an error while loading. Please reload this page.