// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.update;

import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.Codec;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.jar.JarFile;
import java.nio.file.LinkOption;
import com.hypixel.hytale.server.core.HytaleServerConfig;
import com.hypixel.hytale.common.util.java.ManifestUtil;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.auth.AuthConfig;
import java.io.OutputStream;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import java.util.HexFormat;
import java.nio.file.OpenOption;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
import java.nio.file.Files;
import java.nio.file.attribute.FileAttribute;
import java.util.concurrent.CancellationException;
import javax.annotation.Nullable;
import java.io.IOException;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.codec.EmptyExtraInfo;
import com.hypixel.hytale.codec.util.RawJsonReader;
import java.net.http.HttpResponse;
import java.net.URI;
import java.net.http.HttpRequest;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.auth.ServerAuthManager;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;
import java.net.http.HttpClient;
import java.nio.file.Path;
import java.time.Duration;
import com.hypixel.hytale.logger.HytaleLogger;

public class UpdateService
{
    private static final HytaleLogger LOGGER;
    private static final Duration REQUEST_TIMEOUT;
    private static final Duration DOWNLOAD_TIMEOUT;
    private static final Path STAGING_DIR;
    private static final Path BACKUP_DIR;
    private final HttpClient httpClient;
    private final String accountDataUrl;
    
    public UpdateService() {
        this.accountDataUrl = "https://account-data.hytale.com";
        this.httpClient = HttpClient.newBuilder().connectTimeout(UpdateService.REQUEST_TIMEOUT).followRedirects(HttpClient.Redirect.NORMAL).build();
    }
    
    @Nullable
    public CompletableFuture<VersionManifest> checkForUpdate(@Nonnull final String patchline) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                final ServerAuthManager authManager = ServerAuthManager.getInstance();
                final String accessToken = authManager.getOAuthAccessToken();
                if (accessToken == null) {
                    UpdateService.LOGGER.at(Level.WARNING).log("Cannot check for updates - not authenticated");
                    return null;
                }
                else {
                    final String manifestPath = String.format("version/%s.json", patchline);
                    final String signedUrl = this.getSignedUrl(accessToken, manifestPath);
                    if (signedUrl == null) {
                        UpdateService.LOGGER.at(Level.WARNING).log("Failed to get signed URL for version manifest");
                        return null;
                    }
                    else {
                        final HttpRequest manifestRequest = HttpRequest.newBuilder().uri(URI.create(signedUrl)).header("Accept", "application/json").timeout(UpdateService.REQUEST_TIMEOUT).GET().build();
                        final HttpResponse<String> response = this.httpClient.send(manifestRequest, HttpResponse.BodyHandlers.ofString());
                        if (response.statusCode() != 200) {
                            UpdateService.LOGGER.at(Level.WARNING).log("Failed to fetch version manifest: HTTP %d", response.statusCode());
                            return null;
                        }
                        else {
                            final VersionManifest manifest = VersionManifest.CODEC.decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY);
                            if (manifest == null || manifest.version == null) {
                                UpdateService.LOGGER.at(Level.WARNING).log("Invalid version manifest response");
                                return null;
                            }
                            else {
                                UpdateService.LOGGER.at(Level.INFO).log("Found version: %s", manifest.version);
                                return manifest;
                            }
                        }
                    }
                }
            }
            catch (final IOException e) {
                UpdateService.LOGGER.at(Level.WARNING).log("IO error checking for updates: %s", e.getMessage());
                return null;
            }
            catch (final InterruptedException e2) {
                Thread.currentThread().interrupt();
                UpdateService.LOGGER.at(Level.WARNING).log("Update check interrupted");
                return null;
            }
            catch (final Exception e3) {
                UpdateService.LOGGER.at(Level.WARNING).withCause(e3).log("Error checking for updates");
                return null;
            }
        });
    }
    
    public DownloadTask downloadUpdate(@Nonnull final VersionManifest manifest, @Nonnull final Path stagingDir, @Nullable final ProgressCallback progressCallback) {
        final CompletableFuture<Boolean> future = new CompletableFuture<Boolean>();
        final Thread thread = new Thread(() -> {
            try {
                final boolean result = this.performDownload(manifest, stagingDir, progressCallback);
                future.complete(result);
            }
            catch (final CancellationException e) {
                future.completeExceptionally(e);
            }
            catch (final InterruptedException e2) {
                Thread.currentThread().interrupt();
                future.completeExceptionally(new CancellationException("Update download interrupted"));
            }
            catch (final Exception e3) {
                UpdateService.LOGGER.at(Level.WARNING).withCause(e3).log("Error downloading update");
                future.complete(false);
            }
            return;
        }, "UpdateDownload");
        thread.setDaemon(true);
        thread.start();
        return new DownloadTask(future, thread);
    }
    
    private boolean performDownload(@Nonnull final VersionManifest manifest, @Nonnull final Path stagingDir, @Nullable final ProgressCallback progressCallback) throws IOException, InterruptedException {
        final ServerAuthManager authManager = ServerAuthManager.getInstance();
        final String accessToken = authManager.getOAuthAccessToken();
        if (accessToken == null) {
            UpdateService.LOGGER.at(Level.WARNING).log("Cannot download update - not authenticated");
            return false;
        }
        final String signedUrl = this.getSignedUrl(accessToken, manifest.downloadUrl);
        if (signedUrl == null) {
            UpdateService.LOGGER.at(Level.WARNING).log("Failed to get signed URL for download");
            return false;
        }
        final HttpRequest downloadRequest = HttpRequest.newBuilder().uri(URI.create(signedUrl)).timeout(UpdateService.DOWNLOAD_TIMEOUT).GET().build();
        final Path tempFile = Files.createTempFile("hytale-update-", ".zip", (FileAttribute<?>[])new FileAttribute[0]);
        try {
            final HttpResponse<InputStream> response = this.httpClient.send(downloadRequest, HttpResponse.BodyHandlers.ofInputStream());
            if (response.statusCode() != 200) {
                UpdateService.LOGGER.at(Level.WARNING).log("Failed to download update: HTTP %d", response.statusCode());
                return false;
            }
            final long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1L);
            MessageDigest digest;
            try {
                digest = MessageDigest.getInstance("SHA-256");
            }
            catch (final NoSuchAlgorithmException e) {
                UpdateService.LOGGER.at(Level.SEVERE).log("SHA-256 not available - this should never happen");
                return false;
            }
            try (final InputStream inputStream = response.body();
                 final OutputStream outputStream = Files.newOutputStream(tempFile, new OpenOption[0])) {
                final byte[] buffer = new byte[8192];
                long downloaded = 0L;
                int read;
                while ((read = inputStream.read(buffer)) != -1) {
                    if (Thread.currentThread().isInterrupted()) {
                        throw new CancellationException("Update download cancelled");
                    }
                    outputStream.write(buffer, 0, read);
                    digest.update(buffer, 0, read);
                    downloaded += read;
                    if (progressCallback == null || contentLength <= 0L) {
                        continue;
                    }
                    final int percent = (int)(downloaded * 100L / contentLength);
                    progressCallback.onProgress(percent, downloaded, contentLength);
                }
            }
            final String actualHash = HexFormat.of().formatHex(digest.digest());
            if (manifest.sha256 != null && !manifest.sha256.equalsIgnoreCase(actualHash)) {
                UpdateService.LOGGER.at(Level.WARNING).log("Checksum mismatch! Expected: %s, Got: %s", manifest.sha256, actualHash);
                return false;
            }
            if (!clearStagingDir(stagingDir)) {
                UpdateService.LOGGER.at(Level.WARNING).log("Failed to clear staging directory before extraction");
                return false;
            }
            Files.createDirectories(stagingDir, (FileAttribute<?>[])new FileAttribute[0]);
            if (Thread.currentThread().isInterrupted()) {
                throw new CancellationException("Update download cancelled");
            }
            FileUtil.extractZip(tempFile, stagingDir);
            UpdateService.LOGGER.at(Level.INFO).log("Update %s downloaded and extracted to staging", manifest.version);
            return true;
        }
        finally {
            Files.deleteIfExists(tempFile);
        }
    }
    
    @Nullable
    private String getSignedUrl(final String accessToken, final String path) throws IOException, InterruptedException {
        final String url = this.accountDataUrl + "/game-assets/" + path;
        final HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).header("Accept", "application/json").header("Authorization", "Bearer " + accessToken).header("User-Agent", AuthConfig.USER_AGENT).timeout(UpdateService.REQUEST_TIMEOUT).GET().build();
        final HttpResponse<String> response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 200) {
            UpdateService.LOGGER.at(Level.WARNING).log("Failed to get signed URL: HTTP %d - %s", response.statusCode(), response.body());
            return null;
        }
        final SignedUrlResponse signedResponse = SignedUrlResponse.CODEC.decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY);
        return (signedResponse != null) ? signedResponse.url : null;
    }
    
    @Nonnull
    public static String getEffectivePatchline() {
        final HytaleServerConfig.UpdateConfig config = HytaleServer.get().getConfig().getUpdateConfig();
        String patchline = config.getPatchline();
        if (patchline != null && !patchline.isEmpty()) {
            return patchline;
        }
        patchline = ManifestUtil.getPatchline();
        return (patchline != null) ? patchline : "release";
    }
    
    public static boolean isValidUpdateLayout() {
        final Path parent = Path.of("..", new String[0]).toAbsolutePath();
        return Files.exists(parent.resolve("Assets.zip"), new LinkOption[0]) && (Files.exists(parent.resolve("start.sh"), new LinkOption[0]) || Files.exists(parent.resolve("start.bat"), new LinkOption[0]));
    }
    
    @Nonnull
    public static Path getStagingDir() {
        return UpdateService.STAGING_DIR;
    }
    
    @Nonnull
    public static Path getBackupDir() {
        return UpdateService.BACKUP_DIR;
    }
    
    @Nullable
    public static String getStagedVersion() {
        final Path stagedJar = UpdateService.STAGING_DIR.resolve("Server").resolve("HytaleServer.jar");
        if (!Files.exists(stagedJar, new LinkOption[0])) {
            return null;
        }
        return readVersionFromJar(stagedJar);
    }
    
    public static boolean deleteStagedUpdate() {
        return safeDeleteUpdaterDir(UpdateService.STAGING_DIR, "staging");
    }
    
    public static boolean deleteBackupDir() {
        return safeDeleteUpdaterDir(UpdateService.BACKUP_DIR, "backup");
    }
    
    private static boolean clearStagingDir(@Nonnull final Path stagingDir) {
        if (!Files.exists(stagingDir, new LinkOption[0])) {
            return true;
        }
        if (stagingDir.toAbsolutePath().normalize().equals(UpdateService.STAGING_DIR.toAbsolutePath().normalize())) {
            return deleteStagedUpdate();
        }
        try {
            FileUtil.deleteDirectory(stagingDir);
            return true;
        }
        catch (final IOException e) {
            UpdateService.LOGGER.at(Level.WARNING).log("Failed to delete staging dir %s: %s", stagingDir, e.getMessage());
            return false;
        }
    }
    
    private static boolean safeDeleteUpdaterDir(final Path dir, final String expectedName) {
        try {
            if (!Files.exists(dir, new LinkOption[0])) {
                return true;
            }
            final Path absolute = dir.toAbsolutePath().normalize();
            final Path parent = absolute.getParent();
            if (parent == null || !parent.getFileName().toString().equals("updater")) {
                UpdateService.LOGGER.at(Level.SEVERE).log("Refusing to delete %s - not within updater/ directory", absolute);
                return false;
            }
            if (!absolute.getFileName().toString().equals(expectedName)) {
                UpdateService.LOGGER.at(Level.SEVERE).log("Refusing to delete %s - unexpected directory name", absolute);
                return false;
            }
            FileUtil.deleteDirectory(dir);
            return true;
        }
        catch (final IOException e) {
            UpdateService.LOGGER.at(Level.WARNING).log("Failed to delete %s: %s", dir, e.getMessage());
            return false;
        }
    }
    
    @Nullable
    public static String readVersionFromJar(@Nonnull final Path jarPath) {
        try (final JarFile jarFile = new JarFile(jarPath.toFile())) {
            final Manifest manifest = jarFile.getManifest();
            if (manifest == null) {
                final String s = null;
                jarFile.close();
                return s;
            }
            final Attributes attrs = manifest.getMainAttributes();
            final String vendorId = attrs.getValue("Implementation-Vendor-Id");
            if (!"com.hypixel.hytale".equals(vendorId)) {
                final String s2 = null;
                jarFile.close();
                return s2;
            }
            return attrs.getValue("Implementation-Version");
        }
        catch (final IOException e) {
            UpdateService.LOGGER.at(Level.WARNING).log("Failed to read version from JAR: %s", e.getMessage());
            return null;
        }
    }
    
    private static <T> KeyedCodec<T> externalKey(final String key, final Codec<T> codec) {
        return new KeyedCodec<T>(key, codec, false, true);
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        REQUEST_TIMEOUT = Duration.ofSeconds(30L);
        DOWNLOAD_TIMEOUT = Duration.ofMinutes(30L);
        STAGING_DIR = Path.of("..", new String[0]).resolve("updater").resolve("staging");
        BACKUP_DIR = Path.of("..", new String[0]).resolve("updater").resolve("backup");
    }
    
    record DownloadTask(CompletableFuture<Boolean> future, Thread thread) {}
    
    public static class VersionManifest
    {
        public String version;
        public String downloadUrl;
        public String sha256;
        public static final BuilderCodec<VersionManifest> CODEC;
        
        static {
            // 
            // This method could not be decompiled.
            // 
            // Original Bytecode:
            // 
            //     2: invokedynamic   BootstrapMethod #0, get:()Ljava/util/function/Supplier;
            //     7: invokestatic    com/hypixel/hytale/codec/builder/BuilderCodec.builder:(Ljava/lang/Class;Ljava/util/function/Supplier;)Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
            //    10: ldc             "version"
            //    12: getstatic       com/hypixel/hytale/codec/Codec.STRING:Lcom/hypixel/hytale/codec/codecs/simple/StringCodec;
            //    15: invokestatic    com/hypixel/hytale/server/core/update/UpdateService.externalKey:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)Lcom/hypixel/hytale/codec/KeyedCodec;
            //    18: invokedynamic   BootstrapMethod #1, accept:()Ljava/util/function/BiConsumer;
            //    23: invokedynamic   BootstrapMethod #2, apply:()Ljava/util/function/Function;
            //    28: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
            //    31: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
            //    34: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
            //    37: ldc             "download_url"
            //    39: getstatic       com/hypixel/hytale/codec/Codec.STRING:Lcom/hypixel/hytale/codec/codecs/simple/StringCodec;
            //    42: invokestatic    com/hypixel/hytale/server/core/update/UpdateService.externalKey:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)Lcom/hypixel/hytale/codec/KeyedCodec;
            //    45: invokedynamic   BootstrapMethod #3, accept:()Ljava/util/function/BiConsumer;
            //    50: invokedynamic   BootstrapMethod #4, apply:()Ljava/util/function/Function;
            //    55: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
            //    58: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
            //    61: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
            //    64: ldc             "sha256"
            //    66: getstatic       com/hypixel/hytale/codec/Codec.STRING:Lcom/hypixel/hytale/codec/codecs/simple/StringCodec;
            //    69: invokestatic    com/hypixel/hytale/server/core/update/UpdateService.externalKey:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)Lcom/hypixel/hytale/codec/KeyedCodec;
            //    72: invokedynamic   BootstrapMethod #5, accept:()Ljava/util/function/BiConsumer;
            //    77: invokedynamic   BootstrapMethod #6, apply:()Ljava/util/function/Function;
            //    82: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
            //    85: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
            //    88: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
            //    91: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.build:()Lcom/hypixel/hytale/codec/builder/BuilderCodec;
            //    94: putstatic       com/hypixel/hytale/server/core/update/UpdateService$VersionManifest.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
            //    97: return         
            // 
            // The error that occurred was:
            // 
            // java.lang.UnsupportedOperationException: The requested operation is not supported.
            //     at com.strobel.util.ContractUtils.unsupported(ContractUtils.java:27)
            //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:284)
            //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:279)
            //     at com.strobel.assembler.metadata.TypeReference.makeGenericType(TypeReference.java:154)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:225)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
            //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:211)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
            //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitMethod(TypeSubstitutionVisitor.java:314)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2611)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1510)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1510)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1083)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:684)
            //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:667)
            //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:373)
            //     at com.strobel.decompiler.ast.TypeAnalysis.run(TypeAnalysis.java:95)
            //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:344)
            //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:42)
            //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:206)
            //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:93)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethodBody(AstBuilder.java:868)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethod(AstBuilder.java:761)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:638)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:605)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:195)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:662)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:605)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:195)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createType(AstBuilder.java:162)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addType(AstBuilder.java:137)
            //     at com.strobel.decompiler.languages.java.JavaLanguage.buildAst(JavaLanguage.java:71)
            //     at com.strobel.decompiler.languages.java.JavaLanguage.decompileType(JavaLanguage.java:59)
            //     at com.strobel.decompiler.DecompilerDriver.decompileType(DecompilerDriver.java:333)
            //     at com.strobel.decompiler.DecompilerDriver.decompileJar(DecompilerDriver.java:254)
            //     at com.strobel.decompiler.DecompilerDriver.main(DecompilerDriver.java:129)
            // 
            throw new IllegalStateException("An error occurred while decompiling this method.");
        }
    }
    
    private static class SignedUrlResponse
    {
        public String url;
        public static final BuilderCodec<SignedUrlResponse> CODEC;
        
        static {
            CODEC = BuilderCodec.builder(SignedUrlResponse.class, SignedUrlResponse::new).append((KeyedCodec<String>)UpdateService.externalKey("url", (Codec<FieldType>)Codec.STRING), (r, v) -> r.url = v, r -> r.url).add().build();
        }
    }
    
    @FunctionalInterface
    public interface ProgressCallback
    {
        void onProgress(final int p0, final long p1, final long p2);
    }
}
