From 3a12666255000670208ed3474229f37fdaf937ad Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 17 May 2026 13:17:00 +0800 Subject: [PATCH 1/2] background cache --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 29 ---------------- .../ui/decorator/DecoratorController.java | 34 +++++++++++++++++-- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index f26abe296e..bf1ef40fc4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -62,7 +62,6 @@ import javafx.util.Callback; import javafx.util.Duration; import javafx.util.StringConverter; -import org.glavo.url.WebURL; import org.jackhuang.hmcl.setting.StyleSheets; import org.jackhuang.hmcl.task.CacheFileTask; import org.jackhuang.hmcl.task.Schedulers; @@ -100,10 +99,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.WeakReference; -import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLConnection; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -1167,32 +1164,6 @@ public static Image loadImage(Path path, } } - public static Image loadImage(WebURL url) throws Exception { - URLConnection connection = NetworkUtils.createConnection(url); - if (connection instanceof HttpURLConnection httpConnection) - connection = NetworkUtils.resolveConnection(httpConnection); - - try (BufferedInputStream input = new BufferedInputStream(connection.getInputStream())) { - String contentType = Objects.requireNonNull(connection.getContentType(), ""); - Matcher matcher = ImageUtils.CONTENT_TYPE_PATTERN.matcher(contentType); - if (matcher.find()) - contentType = matcher.group("type"); - - ImageLoader loader = ImageUtils.CONTENT_TYPE_TO_LOADER.get(contentType); - if (loader == null && !ImageUtils.DEFAULT_CONTENT_TYPES.contains(contentType)) { - input.mark(ImageUtils.HEADER_BUFFER_SIZE); - byte[] headerBuffer = input.readNBytes(ImageUtils.HEADER_BUFFER_SIZE); - input.reset(); - loader = ImageUtils.guessLoader(headerBuffer); - } - - if (loader == null) - loader = ImageUtils.DEFAULT; - - return loader.load(input, 0, 0, false, false); - } - } - /** * Suppress IllegalArgumentException since the url is supposed to be correct definitely. * diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 4f3b060c41..542954a857 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -34,10 +34,10 @@ import javafx.scene.paint.Paint; import javafx.stage.Stage; import javafx.util.Duration; -import org.glavo.url.WebURL; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD; import org.jackhuang.hmcl.setting.EnumBackgroundImage; +import org.jackhuang.hmcl.task.CacheFileTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -52,7 +52,9 @@ import org.jackhuang.hmcl.ui.wizard.Refreshable; import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.util.MathUtils; +import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -71,6 +73,8 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class DecoratorController { + private static final Path remoteBgCachePath = Metadata.HMCL_CURRENT_DIRECTORY.resolve("bg.png"); + private final Decorator decorator; private final Navigator navigator; @@ -169,13 +173,15 @@ public Decorator getDecorator() { @SuppressWarnings("FieldCanBeLocal") // Strong reference private final InvalidationListener changeBackgroundListener; + private volatile boolean remoteFetched = false; + private void updateBackground() { final int currentCount = ++this.changeBackgroundCount; Task.supplyAsync(Schedulers.io(), this::getBackground) .setName("Update background") .whenComplete(Schedulers.javafx(), (background, exception) -> { if (exception == null) { - if (this.changeBackgroundCount == currentCount) + if (this.changeBackgroundCount == currentCount && !remoteFetched) decorator.setContentBackground(background); } else { LOG.warning("Failed to update background", exception); @@ -184,6 +190,7 @@ private void updateBackground() { } private Background getBackground() { + remoteFetched = false; EnumBackgroundImage imageType = config().getBackgroundImageType(); if (imageType == null) imageType = EnumBackgroundImage.DEFAULT; @@ -206,7 +213,8 @@ private Background getBackground() { String backgroundImageUrl = config().getBackgroundImageUrl(); if (backgroundImageUrl != null) { try { - image = FXUtils.loadImage(WebURL.parseBrowserInput(backgroundImageUrl)); + asyncFetchRemoteImage(backgroundImageUrl); + image = tryLoadImage(remoteBgCachePath); } catch (Exception e) { LOG.warning("Couldn't load background image", e); } @@ -338,6 +346,26 @@ private Image loadDefaultBackgroundImage() { } } + private void asyncFetchRemoteImage(@NotNull String backgroundImageUrl) { + final int currentCount = this.changeBackgroundCount; + new CacheFileTask(backgroundImageUrl) + .setExecutor(Schedulers.io()) + .thenApplyAsync(Schedulers.io(), path -> { + if (this.changeBackgroundCount == currentCount) FileUtils.copyFile(path, remoteBgCachePath); + return FXUtils.loadImage(path); + }) + .whenComplete(Schedulers.javafx(), ((image, exception) -> { + if (exception == null) { + if (this.changeBackgroundCount == currentCount) { + decorator.setContentBackground(createBackgroundWithOpacity(image, config().getBackgroundImageOpacity())); + remoteFetched = true; + } + } else { + LOG.warning("Failed to load network background image from " + backgroundImageUrl, exception); + } + })).start(); + } + // ==== Navigation ==== public void navigate(Node node, AnimationProducer animationProducer, Duration duration, Interpolator interpolator) { From 75d3b4ceda3d999d1c6d75e4c1d58542e2050d9a Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 19 May 2026 22:12:08 +0800 Subject: [PATCH 2/2] update --- .../ui/decorator/DecoratorController.java | 31 ++++++++++++------- .../jackhuang/hmcl/task/FileDownloadTask.java | 4 +-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 542954a857..678852c987 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -34,10 +34,11 @@ import javafx.scene.paint.Paint; import javafx.stage.Stage; import javafx.util.Duration; +import org.glavo.url.WebURL; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD; import org.jackhuang.hmcl.setting.EnumBackgroundImage; -import org.jackhuang.hmcl.task.CacheFileTask; +import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -51,6 +52,8 @@ import org.jackhuang.hmcl.ui.construct.Navigator; import org.jackhuang.hmcl.ui.wizard.Refreshable; import org.jackhuang.hmcl.ui.wizard.WizardProvider; +import org.jackhuang.hmcl.util.CacheRepository; +import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.MathUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -58,9 +61,11 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HexFormat; import java.util.Locale; import java.util.Random; import java.util.stream.Collectors; @@ -73,7 +78,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class DecoratorController { - private static final Path remoteBgCachePath = Metadata.HMCL_CURRENT_DIRECTORY.resolve("bg.png"); + private static final Path remoteBgCacheDir = Metadata.HMCL_CURRENT_DIRECTORY.resolve("bg_cache"); private final Decorator decorator; private final Navigator navigator; @@ -214,7 +219,7 @@ private Background getBackground() { if (backgroundImageUrl != null) { try { asyncFetchRemoteImage(backgroundImageUrl); - image = tryLoadImage(remoteBgCachePath); + image = tryLoadImage(remoteBgCacheDir.resolve(generateFileName(backgroundImageUrl))); } catch (Exception e) { LOG.warning("Couldn't load background image", e); } @@ -347,16 +352,14 @@ private Image loadDefaultBackgroundImage() { } private void asyncFetchRemoteImage(@NotNull String backgroundImageUrl) { - final int currentCount = this.changeBackgroundCount; - new CacheFileTask(backgroundImageUrl) - .setExecutor(Schedulers.io()) - .thenApplyAsync(Schedulers.io(), path -> { - if (this.changeBackgroundCount == currentCount) FileUtils.copyFile(path, remoteBgCachePath); - return FXUtils.loadImage(path); - }) + final int[] currentCount = new int[1]; + final Path path = remoteBgCacheDir.resolve(generateFileName(backgroundImageUrl)); + Task.runAsync(Schedulers.javafx(), () -> currentCount[0] = this.changeBackgroundCount) + .thenComposeAsync(new FileDownloadTask(backgroundImageUrl, path).setExecutor(Schedulers.io())) + .thenApplyAsync(Schedulers.io(), (__) -> tryLoadImage(path)) .whenComplete(Schedulers.javafx(), ((image, exception) -> { - if (exception == null) { - if (this.changeBackgroundCount == currentCount) { + if (exception == null && image != null) { + if (this.changeBackgroundCount == currentCount[0]) { decorator.setContentBackground(createBackgroundWithOpacity(image, config().getBackgroundImageOpacity())); remoteFetched = true; } @@ -366,6 +369,10 @@ private void asyncFetchRemoteImage(@NotNull String backgroundImageUrl) { })).start(); } + private static String generateFileName(@NotNull String url) { + return HexFormat.of().formatHex(DigestUtils.digest(CacheRepository.SHA1, url.getBytes(StandardCharsets.UTF_8))) + "." + FileUtils.getExtension(WebURL.parse(url).getPath()); + } + // ==== Navigation ==== public void navigate(Node node, AnimationProducer animationProducer, Duration duration, Interpolator interpolator) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java index ecc141a732..c341ff38f3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -162,9 +162,9 @@ protected EnumCheckETag shouldCheckETag() { LOG.warning("Failed to copy cache files", e); } } - return EnumCheckETag.NOT_CHECK_E_TAG; - } else { return EnumCheckETag.CHECK_E_TAG; + } else { + return EnumCheckETag.NOT_CHECK_E_TAG; } }