From cd6e6e13260dd493baba4821b31d622aceebde52 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 21 Apr 2025 19:30:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=E5=AF=B9=20Git?= =?UTF-8?q?Lab=20=E7=9A=84=E8=B5=84=E6=BA=90=E5=8C=85=E6=89=98=E7=AE=A1?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../advancement/BukkitAdvancementManager.java | 4 + .../core/pack/host/ResourcePackHosts.java | 2 + .../core/pack/host/impl/GitLabHost.java | 182 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java index 92026d457..c1ed78270 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java @@ -26,6 +26,10 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager { this.advancementParser = new AdvancementParser(); } + public void unload() { + advancements.clear(); + } + @Override public ConfigSectionParser parser() { return this.advancementParser; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index c6207c254..821cf8302 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -19,6 +19,7 @@ public class ResourcePackHosts { public static final Key ALIST = Key.of("craftengine:alist"); public static final Key DROPBOX = Key.of("craftengine:dropbox"); public static final Key ONEDRIVE = Key.of("craftengine:onedrive"); + public static final Key GITLAB = Key.of("craftengine:gitlab"); static { register(NONE, NoneHost.FACTORY); @@ -29,6 +30,7 @@ public class ResourcePackHosts { register(ALIST, AlistHost.FACTORY); register(DROPBOX, DropboxHost.FACTORY); register(ONEDRIVE, OneDriveHost.FACTORY); + register(GITLAB, GitLabHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java new file mode 100644 index 000000000..e1e47cd87 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java @@ -0,0 +1,182 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import com.google.gson.reflect.TypeToken; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.HashUtils; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class GitLabHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final String gitlabUrl; + private final String accessToken; + private final String projectId; + private final ProxySelector proxy; + + private String url; + private String sha1; + private UUID uuid; + + public GitLabHost(String gitlabUrl, String accessToken, String projectId, ProxySelector proxy) { + this.gitlabUrl = gitlabUrl; + this.accessToken = accessToken; + this.projectId = projectId; + this.proxy = proxy; + this.readCacheFromDisk(); + } + + public void readCacheFromDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("gitlab.cache"); + if (!Files.exists(cachePath)) return; + + try (InputStream is = Files.newInputStream(cachePath)) { + Map cache = GsonHelper.get().fromJson( + new InputStreamReader(is), + new TypeToken>(){}.getType() + ); + + this.url = cache.get("url"); + this.sha1 = cache.get("sha1"); + + String uuidString = cache.get("uuid"); + if (uuidString != null && !uuidString.isEmpty()) { + this.uuid = UUID.fromString(uuidString); + } + + CraftEngine.instance().logger().info("[GitLab] Loaded cached resource pack info"); + } catch (Exception e) { + CraftEngine.instance().logger().warn( + "[GitLab] Failed to read cache file: " + e.getMessage()); + } + } + + public void saveCacheToDisk() { + Map cache = new HashMap<>(); + cache.put("url", this.url); + cache.put("sha1", this.sha1); + cache.put("uuid", this.uuid != null ? this.uuid.toString() : ""); + + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("gitlab.cache"); + try { + Files.writeString( + cachePath, + GsonHelper.get().toJson(cache), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + CraftEngine.instance().logger().warn( + "[GitLab] Failed to save cache: " + e.getMessage()); + } + } + + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.GITLAB; + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + if (url == null) return CompletableFuture.completedFuture(Collections.emptyList()); + return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(this.url, this.uuid, this.sha1))); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + this.sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + this.uuid = UUID.nameUUIDFromBytes(this.sha1.getBytes(StandardCharsets.UTF_8)); + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.gitlabUrl + "/api/v4/projects/" + this.projectId + "/uploads")) + .header("PRIVATE-TOKEN", this.accessToken) + .POST(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) + .build(); + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info( + "[GitLab] Initiating resource pack upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + long uploadTime = System.currentTimeMillis() - uploadStart; + CraftEngine.instance().logger().info( + "[GitLab] Upload request completed in " + uploadTime + "ms"); + if (response.statusCode() == 200 || response.statusCode() == 201) { + Map json = GsonHelper.parseJsonToMap(response.body()); + if (json.containsKey("full_path")) { + this.url = (String) json.get("full_path"); + future.complete(null); + saveCacheToDisk(); + return; + } + } + CraftEngine.instance().logger().warn("[GitLab] Upload failed: " + response.body()); + future.completeExceptionally(new RuntimeException("Upload failed: " + response.body())); + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn( + "[GitLab] Upload error: " + ex.getMessage()); + future.completeExceptionally(ex); + return null; + }); + } catch (FileNotFoundException e) { + CraftEngine.instance().logger().warn( + "[GitLab] Failed to upload resource pack: " + e.getMessage()); + } + }); + return future; + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); + String gitlabUrl = (String) arguments.get("gitlab-url"); + if (gitlabUrl == null || gitlabUrl.isEmpty()) { + throw new IllegalArgumentException("'gitlab-url' cannot be empty for GitLab host"); + } + if (gitlabUrl.endsWith("/")) { + gitlabUrl = gitlabUrl.substring(0, gitlabUrl.length() - 1); + } + String accessToken = useEnv ? System.getenv("CE_GITLAB_ACCESS_TOKEN") : (String) arguments.get("access-token"); + if (accessToken == null || accessToken.isEmpty()) { + throw new IllegalArgumentException("Missing required 'access-token' configuration"); + } + String projectId = (String) arguments.get("project-id"); + if (projectId == null || projectId.isEmpty()) { + throw new IllegalArgumentException("Missing required 'project-id' configuration"); + } + projectId = URLEncoder.encode(projectId, StandardCharsets.UTF_8).replace("/", "%2F"); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + return new GitLabHost(gitlabUrl, accessToken, projectId, proxy); + } + } +}