package net.minecraft.server.packs; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.mojang.datafixers.util.Either; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.io.IOException; import java.net.Proxy; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import net.minecraft.FileUtil; import net.minecraft.Util; import net.minecraft.core.UUIDUtil; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.HttpUtil; import net.minecraft.util.HttpUtil.DownloadProgressListener; import net.minecraft.util.eventlog.JsonEventLog; import net.minecraft.util.thread.ConsecutiveExecutor; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class DownloadQueue implements AutoCloseable { private static final Logger LOGGER = LogUtils.getLogger(); private static final int MAX_KEPT_PACKS = 20; private final Path cacheDir; private final JsonEventLog eventLog; private final ConsecutiveExecutor tasks = new ConsecutiveExecutor(Util.nonCriticalIoPool(), "download-queue"); public DownloadQueue(Path cacheDir) throws IOException { this.cacheDir = cacheDir; FileUtil.createDirectoriesSafe(cacheDir); this.eventLog = JsonEventLog.open(DownloadQueue.LogEntry.CODEC, cacheDir.resolve("log.json")); DownloadCacheCleaner.vacuumCacheDir(cacheDir, 20); } private DownloadQueue.BatchResult runDownload(DownloadQueue.BatchConfig batchConfig, Map downloads) { DownloadQueue.BatchResult batchResult = new DownloadQueue.BatchResult(); downloads.forEach( (uUID, downloadRequest) -> { Path path = this.cacheDir.resolve(uUID.toString()); Path path2 = null; try { path2 = HttpUtil.downloadFile( path, downloadRequest.url, batchConfig.headers, batchConfig.hashFunction, downloadRequest.hash, batchConfig.maxSize, batchConfig.proxy, batchConfig.listener ); batchResult.downloaded.put(uUID, path2); } catch (Exception var9) { LOGGER.error("Failed to download {}", downloadRequest.url, var9); batchResult.failed.add(uUID); } try { this.eventLog .write( new DownloadQueue.LogEntry( uUID, downloadRequest.url.toString(), Instant.now(), Optional.ofNullable(downloadRequest.hash).map(HashCode::toString), path2 != null ? this.getFileInfo(path2) : Either.left("download_failed") ) ); } catch (Exception var8) { LOGGER.error("Failed to log download of {}", downloadRequest.url, var8); } } ); return batchResult; } private Either getFileInfo(Path path) { try { long l = Files.size(path); Path path2 = this.cacheDir.relativize(path); return Either.right(new DownloadQueue.FileInfoEntry(path2.toString(), l)); } catch (IOException var5) { LOGGER.error("Failed to get file size of {}", path, var5); return Either.left("no_access"); } } public CompletableFuture downloadBatch(DownloadQueue.BatchConfig batchConfig, Map downloads) { return CompletableFuture.supplyAsync(() -> this.runDownload(batchConfig, downloads), this.tasks::schedule); } public void close() throws IOException { this.tasks.close(); this.eventLog.close(); } public record BatchConfig(HashFunction hashFunction, int maxSize, Map headers, Proxy proxy, DownloadProgressListener listener) { } public record BatchResult(Map downloaded, Set failed) { public BatchResult() { this(new HashMap(), new HashSet()); } } public record DownloadRequest(URL url, @Nullable HashCode hash) { } record FileInfoEntry(String name, long size) { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( Codec.STRING.fieldOf("name").forGetter(DownloadQueue.FileInfoEntry::name), Codec.LONG.fieldOf("size").forGetter(DownloadQueue.FileInfoEntry::size) ) .apply(instance, DownloadQueue.FileInfoEntry::new) ); } record LogEntry(UUID id, String url, Instant time, Optional hash, Either errorOrFileInfo) { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( UUIDUtil.STRING_CODEC.fieldOf("id").forGetter(DownloadQueue.LogEntry::id), Codec.STRING.fieldOf("url").forGetter(DownloadQueue.LogEntry::url), ExtraCodecs.INSTANT_ISO8601.fieldOf("time").forGetter(DownloadQueue.LogEntry::time), Codec.STRING.optionalFieldOf("hash").forGetter(DownloadQueue.LogEntry::hash), Codec.mapEither(Codec.STRING.fieldOf("error"), DownloadQueue.FileInfoEntry.CODEC.fieldOf("file")).forGetter(DownloadQueue.LogEntry::errorOrFileInfo) ) .apply(instance, DownloadQueue.LogEntry::new) ); } }