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
private static Builder createRequest(String downloadLink) @ L113
- 方法名:createRequest
- 源码定位:L113
- 返回类型:Builder
- 修饰符:private static
参数:
- downloadLink: String
说明:
TODO
public static OptionalLong contentLength(String downloadLink) @ L117
- 方法名: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();
}
}
}引用的其他类
-
- 引用位置:
参数
- 引用位置:
-
RealmsDefaultUncaughtExceptionHandler
- 引用位置:
构造调用 - 关联成员:
RealmsDefaultUncaughtExceptionHandler()
- 引用位置:
-
RealmsDownloadLatestWorldScreen
- 引用位置:
参数
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
Minecraft.getInstance()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
FileUtil.createDirectoriesSafe()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
Util.nonCriticalIoPool()
- 引用位置:
-
- 引用位置:
参数/字段/方法调用 - 关联成员:
File.createTempFile()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
Pattern.compile()
- 引用位置:
-
- 引用位置:
参数
- 引用位置: