SignedUrlResponse class
Пакет: com.hypixel.hytale.server.core.update
Файл: com/hypixel/hytale/server/core/update/UpdateService.java
Поля (2)
| Модификаторы | Тип | Имя |
|---|---|---|
|
public static final BuilderCodec<UpdateService.SignedUrlResponse> |
CODEC |
|
public String |
url |
Исходный код
Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.update;
class="kw">import com.hypixel.hytale.codec.Codec;
class="kw">import com.hypixel.hytale.codec.EmptyExtraInfo;
class="kw">import com.hypixel.hytale.codec.KeyedCodec;
class="kw">import com.hypixel.hytale.codec.builder.BuilderCodec;
class="kw">import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
class="kw">import com.hypixel.hytale.codec.util.RawJsonReader;
class="kw">import com.hypixel.hytale.common.util.java.ManifestUtil;
class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.HytaleServerConfig;
class="kw">import com.hypixel.hytale.server.core.auth.AuthConfig;
class="kw">import com.hypixel.hytale.server.core.auth.EncryptedAuthCredentialStore;
class="kw">import com.hypixel.hytale.server.core.auth.ServerAuthManager;
class="kw">import com.hypixel.hytale.server.core.config.UpdateConfig;
class="kw">import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
class="kw">import com.hypixel.hytale.server.core.util.io.FileUtil;
class="kw">import java.io.IOException;
class="kw">import java.io.InputStream;
class="kw">import java.io.OutputStream;
class="kw">import java.net.URI;
class="kw">import java.net.http.HttpClient;
class="kw">import java.net.http.HttpRequest;
class="kw">import java.net.http.HttpResponse;
class="kw">import java.net.http.HttpClient.Redirect;
class="kw">import java.net.http.HttpResponse.BodyHandlers;
class="kw">import java.nio.file.AccessDeniedException;
class="kw">import java.nio.file.Files;
class="kw">import java.nio.file.Path;
class="kw">import java.nio.file.StandardCopyOption;
class="kw">import java.security.CodeSource;
class="kw">import java.security.MessageDigest;
class="kw">import java.security.NoSuchAlgorithmException;
class="kw">import java.time.Duration;
class="kw">import java.util.HexFormat;
class="kw">import java.util.Locale;
class="kw">import java.util.concurrent.CancellationException;
class="kw">import java.util.concurrent.CompletableFuture;
class="kw">import java.util.jar.Attributes;
class="kw">import java.util.jar.JarFile;
class="kw">import java.util.jar.Manifest;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;
class="kw">public class UpdateService {
class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
class="kw">private class="kw">static class="kw">final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30L);
class="kw">private class="kw">static class="kw">final Duration DOWNLOAD_TIMEOUT = Duration.ofMinutes(30L);
class="kw">private class="kw">static class="kw">final Path STAGING_DIR = Path.of("..").resolve("updater").resolve("staging");
class="kw">private class="kw">static class="kw">final Path BACKUP_DIR = Path.of("..").resolve("updater").resolve("backup");
class="kw">private class="kw">final HttpClient httpClient;
class="kw">private class="kw">final String accountDataUrl = "https://account-data.hytale.com";
class="kw">public UpdateService() {
this.httpClient = ServiceHttpClientFactory.newBuilder(REQUEST_TIMEOUT).followRedirects(Redirect.NORMAL).build();
}
@Nullable
class="kw">public CompletableFuture<UpdateService.VersionManifest> checkForUpdate(@Nonnull String var1) {
class="kw">return CompletableFuture.supplyAsync(
() -> {
try {
ServerAuthManager var2 = ServerAuthManager.getInstance();
String var3 = var2.getOAuthAccessToken();
if (var3 == null) {
LOGGER.at(Level.WARNING).log("Cannot check for updates - not authenticated");
class="kw">return null;
} else {
String var4 = String.format("version/%s.json", var1);
String var5 = this.getSignedUrl(var3, var4);
if (var5 == null) {
LOGGER.at(Level.WARNING).log("Failed to get signed URL for version manifest");
class="kw">return null;
} else {
HttpRequest var6 = HttpRequest.newBuilder()
.uri(URI.create(var5))
.header("Accept", "application/json")
.timeout(REQUEST_TIMEOUT)
.GET()
.build();
HttpResponse var7 = this.httpClient.send(var6, BodyHandlers.ofString());
if (var7.statusCode() != 200) {
LOGGER.at(Level.WARNING).log("Failed to fetch version manifest: HTTP %d", var7.statusCode());
class="kw">return null;
} else {
UpdateService.VersionManifest var8 = UpdateService.VersionManifest.CODEC
.decodeJson(new RawJsonReader(var7.body().toCharArray()), EmptyExtraInfo.EMPTY);
if (var8 != null && var8.version != null) {
LOGGER.at(Level.INFO).log("Found version: %s", var8.version);
class="kw">return var8;
} else {
LOGGER.at(Level.WARNING).log("Invalid version manifest response");
class="kw">return null;
}
}
}
}
} catch (IOException var9) {
LOGGER.at(Level.WARNING).log("IO error checking for updates: %s", var9.getMessage());
class="kw">return null;
} catch (InterruptedException var10) {
Thread.currentThread().interrupt();
LOGGER.at(Level.WARNING).log("Update check interrupted");
class="kw">return null;
} catch (Exception var11) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var11)).log("Error checking for updates");
class="kw">return null;
}
}
);
}
class="kw">public UpdateService.DownloadTask downloadUpdate(
@Nonnull UpdateService.VersionManifest var1, @Nonnull Path var2, @Nullable UpdateService.ProgressCallback var3
) {
CompletableFuture var4 = new CompletableFuture<>();
Thread var5 = new Thread(() -> {
try {
boolean var5 = this.performDownload(var1, var2, var3);
var4.complete(var5);
} catch (CancellationException var6) {
var4.completeExceptionally(var6);
} catch (InterruptedException var7) {
Thread.currentThread().interrupt();
var4.completeExceptionally(new CancellationException("Update download interrupted"));
} catch (Exception var8) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("Error downloading update");
var4.complete(false);
}
}, "UpdateDownload");
var5.setDaemon(true);
var5.start();
class="kw">return new UpdateService.DownloadTask(var4, var5);
}
class="kw">private boolean performDownload(@Nonnull UpdateService.VersionManifest var1, @Nonnull Path var2, @Nullable UpdateService.ProgressCallback var3) class="kw">throws IOException, InterruptedException {
Path var4 = this.downloadAndVerifyZip(var1, var3);
if (var4 == null) {
class="kw">return false;
}
try {
if (!clearStagingDir(var2)) {
LOGGER.at(Level.WARNING).log("Failed to clear staging directory before extraction");
class="kw">return false;
}
Files.createDirectories(var2);
if (Thread.currentThread().isInterrupted()) {
throw new CancellationException("Update download cancelled");
}
FileUtil.extractZip(var4, var2);
LOGGER.at(Level.INFO).log("Update %s downloaded and extracted to staging", var1.version);
class="kw">return true;
} class="kw">finally {
Files.deleteIfExists(var4);
}
}
@Nullable
class="kw">private Path downloadAndVerifyZip(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2) class="kw">throws IOException, InterruptedException {
ServerAuthManager var3 = ServerAuthManager.getInstance();
String var4 = var3.getOAuthAccessToken();
if (var4 == null) {
LOGGER.at(Level.WARNING).log("Cannot download update - not authenticated");
class="kw">return null;
}
String var5 = this.getSignedUrl(var4, var1.downloadUrl);
if (var5 == null) {
LOGGER.at(Level.WARNING).log("Failed to get signed URL for download");
class="kw">return null;
}
HttpRequest var6 = HttpRequest.newBuilder().uri(URI.create(var5)).timeout(DOWNLOAD_TIMEOUT).GET().build();
Path var7 = Files.createTempFile("hytale-update-", ".zip");
boolean var8 = false;
try {
HttpResponse var9 = this.httpClient.send(var6, BodyHandlers.ofInputStream());
if (var9.statusCode() != 200) {
LOGGER.at(Level.WARNING).log("Failed to download update: HTTP %d", var9.statusCode());
class="kw">return null;
}
long var10 = var9.headers().firstValueAsLong("Content-Length").orElse(-1L);
MessageDigest var12;
try {
var12 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException var29) {
LOGGER.at(Level.SEVERE).log("SHA-256 not available - this should never happen");
class="kw">return null;
}
try (
InputStream var13 = var9.body();
OutputStream var14 = Files.newOutputStream(var7);
) {
byte[] var15 = new byte[8192];
long var16 = 0L;
int var18;
while ((var18 = var13.read(var15)) != -1) {
if (Thread.currentThread().isInterrupted()) {
throw new CancellationException("Update download cancelled");
}
var14.write(var15, 0, var18);
var12.update(var15, 0, var18);
var16 += var18;
if (var2 != null && var10 > 0L) {
int var19 = (int)(var16 * 100L / var10);
var2.onProgress(var19, var16, var10);
}
}
}
String var33 = HexFormat.of().formatHex(var12.digest());
if (var1.sha256 != null && !var1.sha256.equalsIgnoreCase(var33)) {
LOGGER.at(Level.WARNING).log("Checksum mismatch! Expected: %s, Got: %s", var1.sha256, var33);
class="kw">return null;
} else {
var8 = true;
class="kw">return var7;
}
} class="kw">finally {
if (!var8) {
Files.deleteIfExists(var7);
}
}
}
class="kw">public UpdateService.DownloadTask performBootstrapInstall(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2) {
CompletableFuture var3 = new CompletableFuture<>();
Thread var4 = new Thread(() -> {
try {
var3.complete(this.performBootstrapInstallSync(var1, var2));
} catch (CancellationException var5) {
var3.completeExceptionally(var5);
} catch (InterruptedException var6) {
Thread.currentThread().interrupt();
var3.completeExceptionally(new CancellationException("Bootstrap install interrupted"));
} catch (Exception var7) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var7)).log("Error performing bootstrap install");
var3.complete(false);
}
}, "BootstrapInstall");
var4.setDaemon(true);
var4.start();
class="kw">return new UpdateService.DownloadTask(var3, var4);
}
class="kw">private boolean performBootstrapInstallSync(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2) class="kw">throws IOException, InterruptedException {
Path var3 = this.downloadAndVerifyZip(var1, var2);
if (var3 == null) {
class="kw">return false;
}
try {
Path var4 = Path.of(".").toAbsolutePath().normalize();
if (Thread.currentThread().isInterrupted()) {
throw new CancellationException("Bootstrap install cancelled");
}
FileUtil.extractZip(var3, var4);
if (!System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) {
Path var5 = var4.resolve("start.sh");
if (Files.exists(var5) && !var5.toFile().setExecutable(true, false)) {
LOGGER.at(Level.WARNING).log("Failed to set executable bit on %s", var5);
}
}
Path var10 = var4.resolve("Server");
if (Files.isDirectory(var10)) {
Path var6 = var4.resolve("auth.enc");
moveIntoServerDir(var6, var10);
moveIntoServerDir(EncryptedAuthCredentialStore.keyFilePath(var6), var10);
moveIntoServerDir(var4.resolve(HytaleServerConfig.PATH), var10);
}
deleteRunningJarIfPossible(var4);
LOGGER.at(Level.INFO).log("Bootstrap install %s extracted to %s", var1.version, var4);
class="kw">return true;
} class="kw">finally {
Files.deleteIfExists(var3);
}
}
class="kw">private class="kw">static void moveIntoServerDir(@Nonnull Path var0, @Nonnull Path var1) {
if (Files.exists(var0)) {
Path var2 = var1.resolve(var0.getFileName());
try {
Files.move(var0, var2, StandardCopyOption.REPLACE_EXISTING);
LOGGER.at(Level.INFO).log("Moved %s to %s", var0.getFileName(), var2);
} catch (IOException var4) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to move %s into Server/", var0.getFileName());
}
}
}
class="kw">private class="kw">static void deleteRunningJarIfPossible(@Nonnull Path var0) {
if (ManifestUtil.isJar()) {
try {
CodeSource var1 = UpdateService.class.getProtectionDomain().getCodeSource();
if (var1 == null) {
class="kw">return;
}
Path var2 = Path.of(var1.getLocation().toURI());
if (!var2.getParent().toAbsolutePath().normalize().equals(var0)) {
LOGGER.at(Level.INFO).log("Installer JAR %s is outside cwd %s - skipping cleanup", var2, var0);
class="kw">return;
}
Files.delete(var2);
LOGGER.at(Level.INFO).log("Deleted installer JAR %s", var2);
} catch (AccessDeniedException var3) {
LOGGER.at(Level.INFO).log("Could not delete installer JAR (Windows locks the running JAR) - delete it manually after shutdown");
} catch (Exception var4) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to delete installer JAR");
}
}
}
@Nullable
class="kw">private String getSignedUrl(String var1, String var2) class="kw">throws IOException, InterruptedException {
String var3 = this.accountDataUrl + "/game-assets/" + var2;
HttpRequest var4 = HttpRequest.newBuilder()
.uri(URI.create(var3))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + var1)
.header("User-Agent", AuthConfig.USER_AGENT)
.timeout(REQUEST_TIMEOUT)
.GET()
.build();
HttpResponse var5 = this.httpClient.send(var4, BodyHandlers.ofString());
if (var5.statusCode() != 200) {
LOGGER.at(Level.WARNING).log("Failed to get signed URL: HTTP %d - %s", var5.statusCode(), var5.body());
class="kw">return null;
} else {
UpdateService.SignedUrlResponse var6 = UpdateService.SignedUrlResponse.CODEC
.decodeJson(new RawJsonReader(var5.body().toCharArray()), EmptyExtraInfo.EMPTY);
class="kw">return var6 != null ? var6.url : null;
}
}
@Nonnull
class="kw">public class="kw">static String getEffectivePatchline() {
UpdateConfig var0 = HytaleServer.get().getConfig().getUpdateConfig();
String var1 = var0.getPatchline();
if (var1 != null && !var1.isEmpty()) {
class="kw">return var1;
}
var1 = ManifestUtil.getPatchline();
class="kw">return var1 != null ? var1 : "release";
}
class="kw">public class="kw">static boolean isValidUpdateLayout() {
Path var0 = Path.of("..").toAbsolutePath();
class="kw">return Files.exists(var0.resolve("Assets.zip")) && (Files.exists(var0.resolve("start.sh")) || Files.exists(var0.resolve("start.bat")));
}
@Nonnull
class="kw">public class="kw">static Path getStagingDir() {
class="kw">return STAGING_DIR;
}
@Nonnull
class="kw">public class="kw">static Path getBackupDir() {
class="kw">return BACKUP_DIR;
}
@Nullable
class="kw">public class="kw">static String getStagedVersion() {
Path var0 = STAGING_DIR.resolve("Server").resolve("HytaleServer.jar");
class="kw">return !Files.exists(var0) ? null : readVersionFromJar(var0);
}
class="kw">public class="kw">static boolean deleteStagedUpdate() {
class="kw">return safeDeleteUpdaterDir(STAGING_DIR, "staging");
}
class="kw">public class="kw">static boolean deleteBackupDir() {
class="kw">return safeDeleteUpdaterDir(BACKUP_DIR, "backup");
}
class="kw">private class="kw">static boolean clearStagingDir(@Nonnull Path var0) {
if (!Files.exists(var0)) {
class="kw">return true;
}
if (var0.toAbsolutePath().normalize().equals(STAGING_DIR.toAbsolutePath().normalize())) {
class="kw">return deleteStagedUpdate();
}
try {
FileUtil.deleteDirectory(var0);
class="kw">return true;
} catch (IOException var2) {
LOGGER.at(Level.WARNING).log("Failed to delete staging dir %s: %s", var0, var2.getMessage());
class="kw">return false;
}
}
class="kw">private class="kw">static boolean safeDeleteUpdaterDir(Path var0, String var1) {
try {
if (!Files.exists(var0)) {
class="kw">return true;
} else {
Path var2 = var0.toAbsolutePath().normalize();
Path var3 = var2.getParent();
if (var3 == null || !var3.getFileName().toString().equals("updater")) {
LOGGER.at(Level.SEVERE).log("Refusing to delete %s - not within updater/ directory", var2);
class="kw">return false;
} else if (!var2.getFileName().toString().equals(var1)) {
LOGGER.at(Level.SEVERE).log("Refusing to delete %s - unexpected directory name", var2);
class="kw">return false;
} else {
FileUtil.deleteDirectory(var0);
class="kw">return true;
}
}
} catch (IOException var4) {
LOGGER.at(Level.WARNING).log("Failed to delete %s: %s", var0, var4.getMessage());
class="kw">return false;
}
}
@Nullable
class="kw">public class="kw">static String readVersionFromJar(@Nonnull Path var0) {
try (JarFile var1 = new JarFile(var0.toFile())) {
Manifest var2 = var1.getManifest();
if (var2 == null) {
class="kw">return null;
}
Attributes var3 = var2.getMainAttributes();
String var4 = var3.getValue("Implementation-Vendor-Id");
class="kw">return !"com.hypixel.hytale".equals(var4) ? null : var3.getValue("Implementation-Version");
} catch (IOException var8) {
LOGGER.at(Level.WARNING).log("Failed to read version from JAR: %s", var8.getMessage());
class="kw">return null;
}
}
@Nullable
class="kw">public UpdateService.PatchlineSummary[] fetchPatchlines(@Nonnull String var1) class="kw">throws IOException, InterruptedException {
String var2 = this.accountDataUrl + "/my-account/get-patchlines";
HttpRequest var3 = HttpRequest.newBuilder()
.uri(URI.create(var2))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + var1)
.header("User-Agent", AuthConfig.USER_AGENT)
.timeout(REQUEST_TIMEOUT)
.GET()
.build();
HttpResponse var4 = this.httpClient.send(var3, BodyHandlers.ofString());
if (var4.statusCode() != 200) {
LOGGER.at(Level.WARNING).log("Failed to fetch patchlines: HTTP %d", var4.statusCode());
class="kw">return null;
} else {
UpdateService.GetPatchlinesResponse var5 = UpdateService.GetPatchlinesResponse.CODEC
.decodeJson(new RawJsonReader(var4.body().toCharArray()), EmptyExtraInfo.EMPTY);
class="kw">return var5 != null ? var5.patchlines : null;
}
}
class="kw">private class="kw">static <T> KeyedCodec<T> externalKey(String var0, Codec<T> var1) {
class="kw">return new KeyedCodec<>(var0, var1, false, true);
}
class="kw">public record DownloadTask(CompletableFuture<Boolean> future, Thread thread) {
class="kw">public DownloadTask {
}
}
class="kw">public class="kw">static class GetPatchlinesResponse {
class="kw">public UpdateService.PatchlineSummary[] patchlines;
class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.GetPatchlinesResponse> CODEC = BuilderCodec.builder(
UpdateService.GetPatchlinesResponse.class, UpdateService.GetPatchlinesResponse::new
)
.append(
UpdateService.externalKey("patchlines", new ArrayCodec<>(UpdateService.PatchlineSummary.CODEC, UpdateService.PatchlineSummary[]::new)),
(var0, var1) -> var0.patchlines = var1,
var0 -> var0.patchlines
)
.add()
.build();
class="kw">public GetPatchlinesResponse() {
}
}
class="kw">public class="kw">static class PatchlineSummary {
class="kw">public String name;
class="kw">public long expiresAt;
class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.PatchlineSummary> CODEC = BuilderCodec.builder(
UpdateService.PatchlineSummary.class, UpdateService.PatchlineSummary::new
)
.append(UpdateService.externalKey("name", Codec.STRING), (var0, var1) -> var0.name = var1, var0 -> var0.name)
.add()
.append(UpdateService.externalKey("expiresAt", Codec.LONG), (var0, var1) -> var0.expiresAt = var1, var0 -> var0.expiresAt)
.add()
.build();
class="kw">public PatchlineSummary() {
}
}
@FunctionalInterface
class="kw">public class="kw">interface ProgressCallback {
void onProgress(int var1, long var2, long var4);
}
class="kw">private class="kw">static class SignedUrlResponse {
class="kw">public String url;
class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.SignedUrlResponse> CODEC = BuilderCodec.builder(
UpdateService.SignedUrlResponse.class, UpdateService.SignedUrlResponse::new
)
.append(UpdateService.externalKey("url", Codec.STRING), (var0, var1) -> var0.url = var1, var0 -> var0.url)
.add()
.build();
class="kw">private SignedUrlResponse() {
}
}
class="kw">public class="kw">static class VersionManifest {
class="kw">public String version;
class="kw">public String downloadUrl;
class="kw">public String sha256;
class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.VersionManifest> CODEC = BuilderCodec.builder(
UpdateService.VersionManifest.class, UpdateService.VersionManifest::new
)
.append(UpdateService.externalKey("version", Codec.STRING), (var0, var1) -> var0.version = var1, var0 -> var0.version)
.add()
.append(UpdateService.externalKey("download_url", Codec.STRING), (var0, var1) -> var0.downloadUrl = var1, var0 -> var0.downloadUrl)
.add()
.append(UpdateService.externalKey("sha256", Codec.STRING), (var0, var1) -> var0.sha256 = var1, var0 -> var0.sha256)
.add()
.build();
class="kw">public VersionManifest() {
}
}
}