FileDownload.java

com.mojang.realmsclient.client.FileDownload

信息

  • 全限定名:com.mojang.realmsclient.client.FileDownload
  • 类型:public class
  • 包:com.mojang.realmsclient.client
  • 源码路径:src/main/java/com/mojang/realmsclient/client/FileDownload.java
  • 起始行号:L54
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L55
    • 说明:

      TODO

  • cancelled

    • 类型: boolean
    • 修饰符: private volatile
    • 源码定位: L56
    • 说明:

      TODO

  • finished

    • 类型: boolean
    • 修饰符: private volatile
    • 源码定位: L57
    • 说明:

      TODO

  • error

    • 类型: boolean
    • 修饰符: private volatile
    • 源码定位: L58
    • 说明:

      TODO

  • extracting

    • 类型: boolean
    • 修饰符: private volatile
    • 源码定位: L59
    • 说明:

      TODO

  • tempFile

    • 类型: File
    • 修饰符: private volatile
    • 源码定位: L60
    • 说明:

      TODO

  • resourcePackPath

    • 类型: File
    • 修饰符: private volatile
    • 源码定位: L61
    • 说明:

      TODO

  • pendingRequest

    • 类型: CompletableFuture<?>
    • 修饰符: private volatile
    • 源码定位: L62
    • 说明:

      TODO

  • currentThread

    • 类型: Thread
    • 修饰符: private
    • 源码定位: L63
    • 说明:

      TODO

  • INVALID_FILE_NAMES

    • 类型: String[]
    • 修饰符: private static final
    • 源码定位: L64
    • 说明:

      TODO

内部类/嵌套类型

  • com.mojang.realmsclient.client.FileDownload.DownloadCountingOutputStream
    • 类型: class
    • 修饰符: private static
    • 源码定位: L394
    • 说明:

      TODO

构造器

方法

下面的方法块按源码顺序生成。

private <T> T joinCancellableRequest(CompletableFuture<T> pendingRequest) @ L91

  • 方法名:joinCancellableRequest
  • 源码定位:L91
  • 返回类型: T
  • 修饰符:private

参数:

  • pendingRequest: CompletableFuture

说明:

TODO

private static HttpClient createClient() @ L109

  • 方法名:createClient
  • 源码定位:L109
  • 返回类型:HttpClient
  • 修饰符:private static

参数:

说明:

TODO

  • 方法名:createRequest
  • 源码定位:L113
  • 返回类型:Builder
  • 修饰符:private static

参数:

  • downloadLink: String

说明:

TODO

  • 方法名:contentLength
  • 源码定位:L117
  • 返回类型:OptionalLong
  • 修饰符:public static

参数:

  • downloadLink: String

说明:

TODO

public void download(WorldDownload worldDownload, String worldName, RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, LevelStorageSource levelStorageSource) @ L133

  • 方法名:download
  • 源码定位:L133
  • 返回类型:void
  • 修饰符:public

参数:

  • worldDownload: WorldDownload
  • worldName: String
  • downloadStatus: RealmsDownloadLatestWorldScreen.DownloadStatus
  • levelStorageSource: LevelStorageSource

说明:

TODO

private void download(RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, HttpClient client, String url, File target) @ L186

  • 方法名:download
  • 源码定位:L186
  • 返回类型:void
  • 修饰符:private

参数:

  • downloadStatus: RealmsDownloadLatestWorldScreen.DownloadStatus
  • client: HttpClient
  • url: String
  • target: File

说明:

TODO

public void cancel() @ L216

  • 方法名:cancel
  • 源码定位:L216
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public boolean isFinished() @ L229

  • 方法名:isFinished
  • 源码定位:L229
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

public boolean isError() @ L233

  • 方法名:isError
  • 源码定位:L233
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

public boolean isExtracting() @ L237

  • 方法名:isExtracting
  • 源码定位:L237
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

public static String findAvailableFolderName(String folder) @ L241

  • 方法名:findAvailableFolderName
  • 源码定位:L241
  • 返回类型:String
  • 修饰符:public static

参数:

  • folder: String

说明:

TODO

private void untarGzipArchive(String name, File file, LevelStorageSource levelStorageSource) @ L253

  • 方法名:untarGzipArchive
  • 源码定位:L253
  • 返回类型:void
  • 修饰符:private

参数:

  • name: String
  • file: File
  • levelStorageSource: LevelStorageSource

说明:

TODO

private void finishWorldDownload(String worldName, File tempFile, LevelStorageSource levelStorageSource, RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus) @ L360

  • 方法名:finishWorldDownload
  • 源码定位:L360
  • 返回类型:void
  • 修饰符:private

参数:

  • worldName: String
  • tempFile: File
  • levelStorageSource: LevelStorageSource
  • downloadStatus: RealmsDownloadLatestWorldScreen.DownloadStatus

说明:

TODO

private void finishResourcePackDownload(RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, File tempFile, WorldDownload worldDownload) @ L374

  • 方法名:finishResourcePackDownload
  • 源码定位:L374
  • 返回类型:void
  • 修饰符:private

参数:

  • downloadStatus: RealmsDownloadLatestWorldScreen.DownloadStatus
  • tempFile: File
  • worldDownload: WorldDownload

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class FileDownload {
    private static final Logger LOGGER = LogUtils.getLogger();
    private volatile boolean cancelled;
    private volatile boolean finished;
    private volatile boolean error;
    private volatile boolean extracting;
    private volatile @Nullable File tempFile;
    private volatile File resourcePackPath;
    private volatile @Nullable CompletableFuture<?> pendingRequest;
    private @Nullable Thread currentThread;
    private static final String[] INVALID_FILE_NAMES = new String[]{
        "CON",
        "COM",
        "PRN",
        "AUX",
        "CLOCK$",
        "NUL",
        "COM1",
        "COM2",
        "COM3",
        "COM4",
        "COM5",
        "COM6",
        "COM7",
        "COM8",
        "COM9",
        "LPT1",
        "LPT2",
        "LPT3",
        "LPT4",
        "LPT5",
        "LPT6",
        "LPT7",
        "LPT8",
        "LPT9"
    };
 
    private <T> @Nullable T joinCancellableRequest(CompletableFuture<T> pendingRequest) throws Throwable {
        this.pendingRequest = pendingRequest;
        if (this.cancelled) {
            pendingRequest.cancel(true);
            return null;
        } else {
            try {
                try {
                    return pendingRequest.join();
                } catch (CompletionException var3) {
                    throw var3.getCause();
                }
            } catch (CancellationException var4) {
                return null;
            }
        }
    }
 
    private static HttpClient createClient() {
        return HttpClient.newBuilder().executor(Util.nonCriticalIoPool()).connectTimeout(Duration.ofMinutes(2L)).build();
    }
 
    private static Builder createRequest(String downloadLink) {
        return HttpRequest.newBuilder(URI.create(downloadLink)).timeout(Duration.ofMinutes(2L));
    }
 
    @CheckReturnValue
    public static OptionalLong contentLength(String downloadLink) {
        try {
            OptionalLong var3;
            try (HttpClient client = createClient()) {
                HttpResponse<Void> response = client.send(createRequest(downloadLink).HEAD().build(), BodyHandlers.discarding());
                var3 = response.headers().firstValueAsLong("Content-Length");
            }
 
            return var3;
        } catch (Exception var6) {
            LOGGER.error("Unable to get content length for download");
            return OptionalLong.empty();
        }
    }
 
    public void download(
        WorldDownload worldDownload, String worldName, RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, LevelStorageSource levelStorageSource
    ) {
        if (this.currentThread == null) {
            this.currentThread = new Thread(() -> {
                try (HttpClient client = createClient()) {
                    try {
                        this.tempFile = File.createTempFile("backup", ".tar.gz");
                        this.download(downloadStatus, client, worldDownload.downloadLink(), this.tempFile);
                        this.finishWorldDownload(worldName.trim(), this.tempFile, levelStorageSource, downloadStatus);
                    } catch (Exception var23) {
                        LOGGER.error("Caught exception while downloading world", (Throwable)var23);
                        this.error = true;
                    } finally {
                        this.pendingRequest = null;
                        if (this.tempFile != null) {
                            this.tempFile.delete();
                        }
 
                        this.tempFile = null;
                    }
 
                    if (this.error) {
                        return;
                    }
 
                    String resourcePackLink = worldDownload.resourcePackUrl();
                    if (!resourcePackLink.isEmpty() && !worldDownload.resourcePackHash().isEmpty()) {
                        try {
                            this.tempFile = File.createTempFile("resources", ".tar.gz");
                            this.download(downloadStatus, client, resourcePackLink, this.tempFile);
                            this.finishResourcePackDownload(downloadStatus, this.tempFile, worldDownload);
                        } catch (Exception var22) {
                            LOGGER.error("Caught exception while downloading resource pack", (Throwable)var22);
                            this.error = true;
                        } finally {
                            this.pendingRequest = null;
                            if (this.tempFile != null) {
                                this.tempFile.delete();
                            }
 
                            this.tempFile = null;
                        }
                    }
 
                    this.finished = true;
                }
            });
            this.currentThread.setUncaughtExceptionHandler(new RealmsDefaultUncaughtExceptionHandler(LOGGER));
            this.currentThread.start();
        }
    }
 
    private void download(RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, HttpClient client, String url, File target) throws IOException {
        HttpRequest request = createRequest(url).GET().build();
 
        HttpResponse<InputStream> response;
        try {
            response = this.joinCancellableRequest(client.sendAsync(request, BodyHandlers.ofInputStream()));
        } catch (Error var14) {
            throw var14;
        } catch (Throwable var15) {
            LOGGER.error("Failed to download {}", url, var15);
            this.error = true;
            return;
        }
 
        if (response != null && !this.cancelled) {
            if (response.statusCode() != 200) {
                this.error = true;
            } else {
                downloadStatus.totalBytes = response.headers().firstValueAsLong("Content-Length").orElse(0L);
 
                try (
                    InputStream is = response.body();
                    OutputStream os = new FileOutputStream(target);
                ) {
                    is.transferTo(new FileDownload.DownloadCountingOutputStream(os, downloadStatus));
                }
            }
        }
    }
 
    public void cancel() {
        if (this.tempFile != null) {
            this.tempFile.delete();
            this.tempFile = null;
        }
 
        this.cancelled = true;
        CompletableFuture<?> pendingRequest = this.pendingRequest;
        if (pendingRequest != null) {
            pendingRequest.cancel(true);
        }
    }
 
    public boolean isFinished() {
        return this.finished;
    }
 
    public boolean isError() {
        return this.error;
    }
 
    public boolean isExtracting() {
        return this.extracting;
    }
 
    public static String findAvailableFolderName(String folder) {
        folder = folder.replaceAll("[\\./\"]", "_");
 
        for (String invalidName : INVALID_FILE_NAMES) {
            if (folder.equalsIgnoreCase(invalidName)) {
                folder = "_" + folder + "_";
            }
        }
 
        return folder;
    }
 
    private void untarGzipArchive(String name, @Nullable File file, LevelStorageSource levelStorageSource) throws IOException {
        Pattern namePattern = Pattern.compile(".*-([0-9]+)$");
        int number = 1;
 
        for (char replacer : SharedConstants.ILLEGAL_FILE_CHARACTERS) {
            name = name.replace(replacer, '_');
        }
 
        if (StringUtils.isEmpty(name)) {
            name = "Realm";
        }
 
        name = findAvailableFolderName(name);
 
        try {
            for (LevelStorageSource.LevelDirectory level : levelStorageSource.findLevelCandidates()) {
                String levelId = level.directoryName();
                if (levelId.toLowerCase(Locale.ROOT).startsWith(name.toLowerCase(Locale.ROOT))) {
                    Matcher matcher = namePattern.matcher(levelId);
                    if (matcher.matches()) {
                        int parsedNumber = Integer.parseInt(matcher.group(1));
                        if (parsedNumber > number) {
                            number = parsedNumber;
                        }
                    } else {
                        number++;
                    }
                }
            }
        } catch (Exception var44) {
            LOGGER.error("Error getting level list", (Throwable)var44);
            this.error = true;
            return;
        }
 
        String finalName;
        if (levelStorageSource.isNewLevelIdAcceptable(name) && number <= 1) {
            finalName = name;
        } else {
            finalName = name + (number == 1 ? "" : "-" + number);
            if (!levelStorageSource.isNewLevelIdAcceptable(finalName)) {
                boolean foundName = false;
 
                while (!foundName) {
                    number++;
                    finalName = name + (number == 1 ? "" : "-" + number);
                    if (levelStorageSource.isNewLevelIdAcceptable(finalName)) {
                        foundName = true;
                    }
                }
            }
        }
 
        TarArchiveInputStream tarIn = null;
        Path worldPath = Minecraft.getInstance().getLevelSource().getLevelPath(finalName).normalize();
 
        try {
            FileUtil.createDirectoriesSafe(worldPath);
            tarIn = new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(new FileInputStream(file))));
            TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
 
            while (tarEntry != null) {
                Path destPath = worldPath.resolve(Path.of("world").relativize(Path.of(tarEntry.getName()))).normalize();
                if (!destPath.startsWith(worldPath)) {
                    LOGGER.warn("Unexpected entry in Realms world download: {}", tarEntry.getName());
                    tarEntry = tarIn.getNextTarEntry();
                } else {
                    if (tarEntry.isDirectory()) {
                        FileUtil.createDirectoriesSafe(destPath);
                    } else {
                        Path parent = destPath.getParent();
                        if (parent != null) {
                            FileUtil.createDirectoriesSafe(parent);
                        }
 
                        try (FileOutputStream output = new FileOutputStream(destPath.toFile())) {
                            IOUtils.copy(tarIn, output);
                        }
                    }
 
                    tarEntry = tarIn.getNextTarEntry();
                }
            }
        } catch (Exception var42) {
            LOGGER.error("Error extracting world", (Throwable)var42);
            this.error = true;
        } finally {
            if (tarIn != null) {
                tarIn.close();
            }
 
            if (file != null) {
                file.delete();
            }
 
            try (LevelStorageSource.LevelStorageAccess access = levelStorageSource.validateAndCreateAccess(finalName)) {
                access.renameAndDropPlayer(finalName);
            } catch (NbtException | ReportedNbtException | IOException var40) {
                LOGGER.error("Failed to modify unpacked realms level {}", finalName, var40);
            } catch (ContentValidationException var41) {
                LOGGER.warn("Failed to download file", (Throwable)var41);
            }
 
            this.resourcePackPath = worldPath.resolve(LevelResource.MAP_RESOURCE_FILE.id()).toFile();
        }
    }
 
    private void finishWorldDownload(
        String worldName, File tempFile, LevelStorageSource levelStorageSource, RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus
    ) {
        if (downloadStatus.bytesWritten >= downloadStatus.totalBytes && !this.cancelled && !this.error) {
            try {
                this.extracting = true;
                this.untarGzipArchive(worldName, tempFile, levelStorageSource);
            } catch (IOException var6) {
                LOGGER.error("Error extracting archive", (Throwable)var6);
                this.error = true;
            }
        }
    }
 
    private void finishResourcePackDownload(RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, File tempFile, WorldDownload worldDownload) {
        if (downloadStatus.bytesWritten >= downloadStatus.totalBytes && !this.cancelled) {
            try {
                String actualHash = Hashing.sha1().hashBytes(Files.toByteArray(tempFile)).toString();
                if (actualHash.equals(worldDownload.resourcePackHash())) {
                    FileUtils.copyFile(tempFile, this.resourcePackPath);
                    this.finished = true;
                } else {
                    LOGGER.error("Resourcepack had wrong hash (expected {}, found {}). Deleting it.", worldDownload.resourcePackHash(), actualHash);
                    FileUtils.deleteQuietly(tempFile);
                    this.error = true;
                }
            } catch (IOException var5) {
                LOGGER.error("Error copying resourcepack file: {}", var5.getMessage());
                this.error = true;
            }
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private static class DownloadCountingOutputStream extends CountingOutputStream {
        private final RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus;
 
        public DownloadCountingOutputStream(OutputStream out, RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus) {
            super(out);
            this.downloadStatus = downloadStatus;
        }
 
        @Override
        protected void afterWrite(int n) throws IOException {
            super.afterWrite(n);
            this.downloadStatus.bytesWritten = this.getByteCount();
        }
    }
}

引用的其他类