minecraft-src/net/minecraft/server/packs/DownloadQueue.java
2025-07-04 02:00:41 +03:00

144 lines
5.2 KiB
Java

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<DownloadQueue.LogEntry> 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<UUID, DownloadQueue.DownloadRequest> 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<String, DownloadQueue.FileInfoEntry> 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<DownloadQueue.BatchResult> downloadBatch(DownloadQueue.BatchConfig batchConfig, Map<UUID, DownloadQueue.DownloadRequest> 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<String, String> headers, Proxy proxy, DownloadProgressListener listener) {
}
public record BatchResult(Map<UUID, Path> downloaded, Set<UUID> 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<DownloadQueue.FileInfoEntry> 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<String> hash, Either<String, DownloadQueue.FileInfoEntry> errorOrFileInfo) {
public static final Codec<DownloadQueue.LogEntry> 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)
);
}
}