DownloadedPackSource.java

net.minecraft.client.resources.server.DownloadedPackSource

信息

  • 全限定名:net.minecraft.client.resources.server.DownloadedPackSource
  • 类型:public class
  • 包:net.minecraft.client.resources.server
  • 源码路径:src/main/java/net/minecraft/client/resources/server/DownloadedPackSource.java
  • 起始行号:L52
  • 实现:AutoCloseable
  • 职责:

    TODO

字段/常量

  • SERVER_NAME

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

      TODO

  • SHA1

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

      TODO

  • LOGGER

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

      TODO

  • EMPTY_SOURCE

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

      TODO

  • DOWNLOADED_PACK_SELECTION

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

      TODO

  • LOG_ONLY_FEEDBACK

    • 类型: PackLoadFeedback
    • 修饰符: private static final public public
    • 源码定位: L58
    • 说明:

      TODO

  • minecraft

    • 类型: Minecraft
    • 修饰符: private final
    • 源码定位: L69
    • 说明:

      TODO

  • packSource

    • 类型: RepositorySource
    • 修饰符: private
    • 源码定位: L70
    • 说明:

      TODO

  • pendingReload

    • 类型: PackReloadConfig.Callbacks
    • 修饰符: private
    • 源码定位: L71
    • 说明:

      TODO

  • manager

    • 类型: ServerPackManager
    • 修饰符: private final
    • 源码定位: L72
    • 说明:

      TODO

  • downloadQueue

    • 类型: DownloadQueue
    • 修饰符: private final
    • 源码定位: L73
    • 说明:

      TODO

  • packType

    • 类型: PackSource
    • 修饰符: private
    • 源码定位: L74
    • 说明:

      TODO

  • packFeedback

    • 类型: PackLoadFeedback
    • 修饰符: private
    • 源码定位: L75
    • 说明:

      TODO

  • packIdSerialNumber

    • 类型: int
    • 修饰符: private
    • 源码定位: L76
    • 说明:

      TODO

内部类/嵌套类型

构造器

public DownloadedPackSource(Minecraft minecraft, Path packCache, GameConfig.UserData user) @ L78

  • 构造器名:DownloadedPackSource
  • 源码定位:L78
  • 修饰符:public

参数:

  • minecraft: Minecraft
  • packCache: Path
  • user: GameConfig.UserData

说明:

TODO

方法

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

private HttpUtil.DownloadProgressListener createDownloadNotifier(int totalCount) @ L105

  • 方法名:createDownloadNotifier
  • 源码定位:L105
  • 返回类型:HttpUtil.DownloadProgressListener
  • 修饰符:private

参数:

  • totalCount: int

说明:

TODO

private PackDownloader createDownloader(DownloadQueue downloadQueue, Executor mainThreadExecutor, User user, Proxy proxy) @ L180

  • 方法名:createDownloader
  • 源码定位:L180
  • 返回类型:PackDownloader
  • 修饰符:private

参数:

  • downloadQueue: DownloadQueue
  • mainThreadExecutor: Executor
  • user: User
  • proxy: Proxy

说明:

TODO

private Runnable createUpdateScheduler(Executor mainThreadExecutor) @ L224

  • 方法名:createUpdateScheduler
  • 源码定位:L224
  • 返回类型:Runnable
  • 修饰符:private

参数:

  • mainThreadExecutor: Executor

说明:

TODO

private PackReloadConfig createReloadConfig() @ L253

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

参数:

说明:

TODO

private List<Pack> loadRequestedPacks(List<PackReloadConfig.IdAndPath> packsToLoad) @ L257

  • 方法名:loadRequestedPacks
  • 源码定位:L257
  • 返回类型:List
  • 修饰符:private

参数:

  • packsToLoad: List<PackReloadConfig.IdAndPath>

说明:

TODO

public RepositorySource createRepositorySource() @ L278

  • 方法名:createRepositorySource
  • 源码定位:L278
  • 返回类型:RepositorySource
  • 修饰符:public

参数:

说明:

TODO

private static RepositorySource configureSource(List<Pack> packs) @ L282

  • 方法名:configureSource
  • 源码定位:L282
  • 返回类型:RepositorySource
  • 修饰符:private static

参数:

  • packs: List

说明:

TODO

private void startReload(PackReloadConfig.Callbacks callbacks) @ L286

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

参数:

  • callbacks: PackReloadConfig.Callbacks

说明:

TODO

public void onRecovery() @ L304

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

参数:

说明:

TODO

public void onRecoveryFailure() @ L317

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

参数:

说明:

TODO

public void onReloadSuccess() @ L325

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

参数:

说明:

TODO

private static HashCode tryParseSha1Hash(String hash) @ L332

  • 方法名:tryParseSha1Hash
  • 源码定位:L332
  • 返回类型:HashCode
  • 修饰符:private static

参数:

  • hash: String

说明:

TODO

public void pushPack(UUID id, URL url, String hash) @ L336

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

参数:

  • id: UUID
  • url: URL
  • hash: String

说明:

TODO

public void pushLocalPack(UUID id, Path path) @ L341

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

参数:

  • id: UUID
  • path: Path

说明:

TODO

public void popPack(UUID id) @ L345

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

参数:

  • id: UUID

说明:

TODO

public void popAll() @ L349

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

参数:

说明:

TODO

private static PackLoadFeedback createPackResponseSender(Connection connection) @ L353

  • 方法名:createPackResponseSender
  • 源码定位:L353
  • 返回类型:PackLoadFeedback
  • 修饰符:private static

参数:

  • connection: Connection

说明:

TODO

public void configureForServerControl(Connection connection, ServerPackManager.PackPromptStatus packPromptStatus) @ L382

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

参数:

  • connection: Connection
  • packPromptStatus: ServerPackManager.PackPromptStatus

说明:

TODO

public void configureForLocalWorld() @ L397

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

参数:

说明:

TODO

public void allowServerPacks() @ L403

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

参数:

说明:

TODO

public void rejectServerPacks() @ L407

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

参数:

说明:

TODO

public CompletableFuture<Void> waitForPackFeedback(UUID packId) @ L411

  • 方法名:waitForPackFeedback
  • 源码定位:L411
  • 返回类型:CompletableFuture
  • 修饰符:public

参数:

  • packId: UUID

说明:

TODO

public void cleanupAfterDisconnect() @ L441

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

参数:

说明:

TODO

public void close() @ L447

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

参数:

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class DownloadedPackSource implements AutoCloseable {
    private static final Component SERVER_NAME = Component.translatable("resourcePack.server.name");
    private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$");
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final RepositorySource EMPTY_SOURCE = result -> {};
    private static final PackSelectionConfig DOWNLOADED_PACK_SELECTION = new PackSelectionConfig(true, Pack.Position.TOP, true);
    private static final PackLoadFeedback LOG_ONLY_FEEDBACK = new PackLoadFeedback() {
        @Override
        public void reportUpdate(UUID id, PackLoadFeedback.Update update) {
            DownloadedPackSource.LOGGER.debug("Downloaded pack {} changed state to {}", id, update);
        }
 
        @Override
        public void reportFinalResult(UUID id, PackLoadFeedback.FinalResult result) {
            DownloadedPackSource.LOGGER.debug("Downloaded pack {} finished with state {}", id, result);
        }
    };
    private final Minecraft minecraft;
    private RepositorySource packSource = EMPTY_SOURCE;
    private PackReloadConfig.@Nullable Callbacks pendingReload;
    private final ServerPackManager manager;
    private final DownloadQueue downloadQueue;
    private PackSource packType = PackSource.SERVER;
    private PackLoadFeedback packFeedback = LOG_ONLY_FEEDBACK;
    private int packIdSerialNumber;
 
    public DownloadedPackSource(Minecraft minecraft, Path packCache, GameConfig.UserData user) {
        this.minecraft = minecraft;
 
        try {
            this.downloadQueue = new DownloadQueue(packCache);
        } catch (IOException var5) {
            throw new UncheckedIOException("Failed to open download queue in directory " + packCache, var5);
        }
 
        Executor executor = minecraft::schedule;
        this.manager = new ServerPackManager(this.createDownloader(this.downloadQueue, executor, user.user, user.proxy), new PackLoadFeedback() {
            {
                Objects.requireNonNull(DownloadedPackSource.this);
            }
 
            @Override
            public void reportUpdate(UUID id, PackLoadFeedback.Update result) {
                DownloadedPackSource.this.packFeedback.reportUpdate(id, result);
            }
 
            @Override
            public void reportFinalResult(UUID id, PackLoadFeedback.FinalResult result) {
                DownloadedPackSource.this.packFeedback.reportFinalResult(id, result);
            }
        }, this.createReloadConfig(), this.createUpdateScheduler(executor), ServerPackManager.PackPromptStatus.PENDING);
    }
 
    private HttpUtil.DownloadProgressListener createDownloadNotifier(int totalCount) {
        return new HttpUtil.DownloadProgressListener() {
            private final SystemToast.SystemToastId toastId;
            private Component title;
            private @Nullable Component message;
            private int count;
            private int failCount;
            private OptionalLong totalBytes;
 
            {
                Objects.requireNonNull(DownloadedPackSource.this);
                this.toastId = new SystemToast.SystemToastId();
                this.title = Component.empty();
                this.message = null;
                this.totalBytes = OptionalLong.empty();
            }
 
            private void updateToast() {
                DownloadedPackSource.this.minecraft
                    .execute(() -> SystemToast.addOrUpdate(DownloadedPackSource.this.minecraft.getToastManager(), this.toastId, this.title, this.message));
            }
 
            private void updateProgress(long bytesSoFar) {
                if (this.totalBytes.isPresent()) {
                    this.message = Component.translatable("download.pack.progress.percent", bytesSoFar * 100L / this.totalBytes.getAsLong());
                } else {
                    this.message = Component.translatable("download.pack.progress.bytes", Unit.humanReadable(bytesSoFar));
                }
 
                this.updateToast();
            }
 
            @Override
            public void requestStart() {
                this.count++;
                this.title = Component.translatable("download.pack.title", this.count, totalCount);
                this.updateToast();
                DownloadedPackSource.LOGGER.debug("Starting pack {}/{} download", this.count, totalCount);
            }
 
            @Override
            public void downloadStart(OptionalLong sizeBytes) {
                DownloadedPackSource.LOGGER.debug("File size = {} bytes", sizeBytes);
                this.totalBytes = sizeBytes;
                this.updateProgress(0L);
            }
 
            @Override
            public void downloadedBytes(long bytesSoFar) {
                DownloadedPackSource.LOGGER.debug("Progress for pack {}: {} bytes", this.count, bytesSoFar);
                this.updateProgress(bytesSoFar);
            }
 
            @Override
            public void requestFinished(boolean success) {
                if (!success) {
                    DownloadedPackSource.LOGGER.info("Pack {} failed to download", this.count);
                    this.failCount++;
                } else {
                    DownloadedPackSource.LOGGER.debug("Download ended for pack {}", this.count);
                }
 
                if (this.count == totalCount) {
                    if (this.failCount > 0) {
                        this.title = Component.translatable("download.pack.failed", this.failCount, totalCount);
                        this.message = null;
                        this.updateToast();
                    } else {
                        SystemToast.forceHide(DownloadedPackSource.this.minecraft.getToastManager(), this.toastId);
                    }
                }
            }
        };
    }
 
    private PackDownloader createDownloader(DownloadQueue downloadQueue, Executor mainThreadExecutor, User user, Proxy proxy) {
        return new PackDownloader() {
            private static final int MAX_PACK_SIZE_BYTES = 262144000;
            private static final HashFunction CACHE_HASHING_FUNCTION = Hashing.sha1();
 
            {
                Objects.requireNonNull(DownloadedPackSource.this);
            }
 
            private Map<String, String> createDownloadHeaders() {
                WorldVersion version = SharedConstants.getCurrentVersion();
                return Map.of(
                    "X-Minecraft-Username",
                    user.getName(),
                    "X-Minecraft-UUID",
                    UndashedUuid.toString(user.getProfileId()),
                    "X-Minecraft-Version",
                    version.name(),
                    "X-Minecraft-Version-ID",
                    version.id(),
                    "X-Minecraft-Pack-Format",
                    String.valueOf(version.packVersion(PackType.CLIENT_RESOURCES)),
                    "User-Agent",
                    "Minecraft Java/" + version.name()
                );
            }
 
            @Override
            public void download(Map<UUID, DownloadQueue.DownloadRequest> requests, Consumer<DownloadQueue.BatchResult> output) {
                downloadQueue.downloadBatch(
                        new DownloadQueue.BatchConfig(
                            CACHE_HASHING_FUNCTION,
                            262144000,
                            this.createDownloadHeaders(),
                            proxy,
                            DownloadedPackSource.this.createDownloadNotifier(requests.size())
                        ),
                        requests
                    )
                    .thenAcceptAsync(output, mainThreadExecutor);
            }
        };
    }
 
    private Runnable createUpdateScheduler(Executor mainThreadExecutor) {
        return new Runnable() {
            private boolean scheduledInMainExecutor;
            private boolean hasUpdates;
 
            {
                Objects.requireNonNull(DownloadedPackSource.this);
            }
 
            @Override
            public void run() {
                this.hasUpdates = true;
                if (!this.scheduledInMainExecutor) {
                    this.scheduledInMainExecutor = true;
                    mainThreadExecutor.execute(this::runAllUpdates);
                }
            }
 
            private void runAllUpdates() {
                while (this.hasUpdates) {
                    this.hasUpdates = false;
                    DownloadedPackSource.this.manager.tick();
                }
 
                this.scheduledInMainExecutor = false;
            }
        };
    }
 
    private PackReloadConfig createReloadConfig() {
        return this::startReload;
    }
 
    private @Nullable List<Pack> loadRequestedPacks(List<PackReloadConfig.IdAndPath> packsToLoad) {
        List<Pack> packs = new ArrayList<>(packsToLoad.size());
 
        for (PackReloadConfig.IdAndPath idAndPath : Lists.reverse(packsToLoad)) {
            String name = String.format(Locale.ROOT, "server/%08X/%s", this.packIdSerialNumber++, idAndPath.id());
            Path path = idAndPath.path();
            PackLocationInfo packLocationInfo = new PackLocationInfo(name, SERVER_NAME, this.packType, Optional.empty());
            Pack.ResourcesSupplier resources = new FilePackResources.FileResourcesSupplier(path);
            PackFormat currentPackVersion = SharedConstants.getCurrentVersion().packVersion(PackType.CLIENT_RESOURCES);
            Pack.Metadata metadata = Pack.readPackMetadata(packLocationInfo, resources, currentPackVersion, PackType.CLIENT_RESOURCES);
            if (metadata == null) {
                LOGGER.warn("Invalid pack metadata in {}, ignoring all", path);
                return null;
            }
 
            packs.add(new Pack(packLocationInfo, resources, metadata, DOWNLOADED_PACK_SELECTION));
        }
 
        return packs;
    }
 
    public RepositorySource createRepositorySource() {
        return output -> this.packSource.loadPacks(output);
    }
 
    private static RepositorySource configureSource(List<Pack> packs) {
        return packs.isEmpty() ? EMPTY_SOURCE : packs::forEach;
    }
 
    private void startReload(PackReloadConfig.Callbacks callbacks) {
        this.pendingReload = callbacks;
        List<PackReloadConfig.IdAndPath> normalPacks = callbacks.packsToLoad();
        List<Pack> packs = this.loadRequestedPacks(normalPacks);
        if (packs == null) {
            callbacks.onFailure(false);
            List<PackReloadConfig.IdAndPath> recoveryPacks = callbacks.packsToLoad();
            packs = this.loadRequestedPacks(recoveryPacks);
            if (packs == null) {
                LOGGER.warn("Double failure in loading server packs");
                packs = List.of();
            }
        }
 
        this.packSource = configureSource(packs);
        this.minecraft.reloadResourcePacks();
    }
 
    public void onRecovery() {
        if (this.pendingReload != null) {
            this.pendingReload.onFailure(false);
            List<Pack> packs = this.loadRequestedPacks(this.pendingReload.packsToLoad());
            if (packs == null) {
                LOGGER.warn("Double failure in loading server packs");
                packs = List.of();
            }
 
            this.packSource = configureSource(packs);
        }
    }
 
    public void onRecoveryFailure() {
        if (this.pendingReload != null) {
            this.pendingReload.onFailure(true);
            this.pendingReload = null;
            this.packSource = EMPTY_SOURCE;
        }
    }
 
    public void onReloadSuccess() {
        if (this.pendingReload != null) {
            this.pendingReload.onSuccess();
            this.pendingReload = null;
        }
    }
 
    private static @Nullable HashCode tryParseSha1Hash(@Nullable String hash) {
        return hash != null && SHA1.matcher(hash).matches() ? HashCode.fromString(hash.toLowerCase(Locale.ROOT)) : null;
    }
 
    public void pushPack(UUID id, URL url, @Nullable String hash) {
        HashCode parsedHash = tryParseSha1Hash(hash);
        this.manager.pushPack(id, url, parsedHash);
    }
 
    public void pushLocalPack(UUID id, Path path) {
        this.manager.pushLocalPack(id, path);
    }
 
    public void popPack(UUID id) {
        this.manager.popPack(id);
    }
 
    public void popAll() {
        this.manager.popAll();
    }
 
    private static PackLoadFeedback createPackResponseSender(Connection connection) {
        return new PackLoadFeedback() {
            @Override
            public void reportUpdate(UUID id, PackLoadFeedback.Update result) {
                DownloadedPackSource.LOGGER.debug("Pack {} changed status to {}", id, result);
 
                ServerboundResourcePackPacket.Action response = switch (result) {
                    case ACCEPTED -> ServerboundResourcePackPacket.Action.ACCEPTED;
                    case DOWNLOADED -> ServerboundResourcePackPacket.Action.DOWNLOADED;
                };
                connection.send(new ServerboundResourcePackPacket(id, response));
            }
 
            @Override
            public void reportFinalResult(UUID id, PackLoadFeedback.FinalResult result) {
                DownloadedPackSource.LOGGER.debug("Pack {} changed status to {}", id, result);
 
                ServerboundResourcePackPacket.Action response = switch (result) {
                    case APPLIED -> ServerboundResourcePackPacket.Action.SUCCESSFULLY_LOADED;
                    case DOWNLOAD_FAILED -> ServerboundResourcePackPacket.Action.FAILED_DOWNLOAD;
                    case DECLINED -> ServerboundResourcePackPacket.Action.DECLINED;
                    case DISCARDED -> ServerboundResourcePackPacket.Action.DISCARDED;
                    case ACTIVATION_FAILED -> ServerboundResourcePackPacket.Action.FAILED_RELOAD;
                };
                connection.send(new ServerboundResourcePackPacket(id, response));
            }
        };
    }
 
    public void configureForServerControl(Connection connection, ServerPackManager.PackPromptStatus packPromptStatus) {
        this.packType = PackSource.SERVER;
        this.packFeedback = createPackResponseSender(connection);
        switch (packPromptStatus) {
            case ALLOWED:
                this.manager.allowServerPacks();
                break;
            case DECLINED:
                this.manager.rejectServerPacks();
                break;
            case PENDING:
                this.manager.resetPromptStatus();
        }
    }
 
    public void configureForLocalWorld() {
        this.packType = PackSource.WORLD;
        this.packFeedback = LOG_ONLY_FEEDBACK;
        this.manager.allowServerPacks();
    }
 
    public void allowServerPacks() {
        this.manager.allowServerPacks();
    }
 
    public void rejectServerPacks() {
        this.manager.rejectServerPacks();
    }
 
    public CompletableFuture<Void> waitForPackFeedback(UUID packId) {
        final CompletableFuture<Void> result = new CompletableFuture<>();
        final PackLoadFeedback original = this.packFeedback;
        this.packFeedback = new PackLoadFeedback() {
            {
                Objects.requireNonNull(DownloadedPackSource.this);
            }
 
            @Override
            public void reportUpdate(UUID id, PackLoadFeedback.Update result) {
                original.reportUpdate(id, result);
            }
 
            @Override
            public void reportFinalResult(UUID id, PackLoadFeedback.FinalResult status) {
                if (packId.equals(id)) {
                    DownloadedPackSource.this.packFeedback = original;
                    if (status == PackLoadFeedback.FinalResult.APPLIED) {
                        result.complete(null);
                    } else {
                        result.completeExceptionally(new IllegalStateException("Failed to apply pack " + id + ", reason: " + status));
                    }
                }
 
                original.reportFinalResult(id, status);
            }
        };
        return result;
    }
 
    public void cleanupAfterDisconnect() {
        this.manager.popAll();
        this.packFeedback = LOG_ONLY_FEEDBACK;
        this.manager.resetPromptStatus();
    }
 
    @Override
    public void close() throws IOException {
        this.downloadQueue.close();
    }
}

引用的其他类

  • Unit

    • 引用位置: 方法调用
    • 关联成员: Unit.humanReadable()
  • SharedConstants

    • 引用位置: 方法调用
    • 关联成员: SharedConstants.getCurrentVersion()
  • Minecraft

    • 引用位置: 参数/字段
  • User

    • 引用位置: 参数
  • SystemToast

    • 引用位置: 方法调用/构造调用
    • 关联成员: SystemToast.SystemToastId(), SystemToast.addOrUpdate(), SystemToast.forceHide(), SystemToastId()
  • GameConfig

    • 引用位置: 参数
  • PackDownloader

    • 引用位置: 构造调用/返回值
    • 关联成员: PackDownloader()
  • PackLoadFeedback

    • 引用位置: 字段/构造调用/返回值
    • 关联成员: PackLoadFeedback()
  • PackReloadConfig

    • 引用位置: 参数/字段/返回值
  • ServerPackManager

    • 引用位置: 参数/字段/构造调用
    • 关联成员: ServerPackManager()
  • Connection

    • 引用位置: 参数
  • Component

    • 引用位置: 字段/方法调用
    • 关联成员: Component.empty(), Component.translatable()
  • ServerboundResourcePackPacket

    • 引用位置: 构造调用
    • 关联成员: ServerboundResourcePackPacket()
  • DownloadQueue

    • 引用位置: 参数/字段/方法调用/构造调用
    • 关联成员: BatchConfig(), DownloadQueue(), DownloadQueue.BatchConfig()
  • FilePackResources

    • 引用位置: 方法调用/构造调用
    • 关联成员: FilePackResources.FileResourcesSupplier(), FileResourcesSupplier()
  • PackLocationInfo

    • 引用位置: 构造调用
    • 关联成员: PackLocationInfo()
  • PackSelectionConfig

    • 引用位置: 字段/构造调用
    • 关联成员: PackSelectionConfig()
  • Pack

    • 引用位置: 参数/方法调用/构造调用/返回值
    • 关联成员: Pack(), Pack.readPackMetadata()
  • PackSource

    • 引用位置: 字段
  • RepositorySource

    • 引用位置: 字段/返回值
  • HttpUtil

    • 引用位置: 方法调用/构造调用/返回值
    • 关联成员: DownloadProgressListener(), HttpUtil.DownloadProgressListener()
  • TropicalFish

    • 引用位置: 字段/方法调用
    • 关联成员: Pattern.compile()