280 lines
9.2 KiB
Java
280 lines
9.2 KiB
Java
package com.mojang.realmsclient.gui.screens;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.util.concurrent.RateLimiter;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.realmsclient.Unit;
|
|
import com.mojang.realmsclient.client.FileDownload;
|
|
import com.mojang.realmsclient.dto.WorldDownload;
|
|
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.client.GameNarrator;
|
|
import net.minecraft.client.gui.GuiGraphics;
|
|
import net.minecraft.client.gui.components.Button;
|
|
import net.minecraft.client.gui.screens.Screen;
|
|
import net.minecraft.network.chat.CommonComponents;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.realms.RealmsScreen;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class RealmsDownloadLatestWorldScreen extends RealmsScreen {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final ReentrantLock DOWNLOAD_LOCK = new ReentrantLock();
|
|
private static final int BAR_WIDTH = 200;
|
|
private static final int BAR_TOP = 80;
|
|
private static final int BAR_BOTTOM = 95;
|
|
private static final int BAR_BORDER = 1;
|
|
private final Screen lastScreen;
|
|
private final WorldDownload worldDownload;
|
|
private final Component downloadTitle;
|
|
private final RateLimiter narrationRateLimiter;
|
|
private Button cancelButton;
|
|
private final String worldName;
|
|
private final RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus;
|
|
@Nullable
|
|
private volatile Component errorMessage;
|
|
private volatile Component status = Component.translatable("mco.download.preparing");
|
|
@Nullable
|
|
private volatile String progress;
|
|
private volatile boolean cancelled;
|
|
private volatile boolean showDots = true;
|
|
private volatile boolean finished;
|
|
private volatile boolean extracting;
|
|
@Nullable
|
|
private Long previousWrittenBytes;
|
|
@Nullable
|
|
private Long previousTimeSnapshot;
|
|
private long bytesPersSecond;
|
|
private int animTick;
|
|
private static final String[] DOTS = new String[]{"", ".", ". .", ". . ."};
|
|
private int dotIndex;
|
|
private boolean checked;
|
|
private final BooleanConsumer callback;
|
|
|
|
public RealmsDownloadLatestWorldScreen(Screen lastScreen, WorldDownload worldDownload, String worldName, BooleanConsumer callback) {
|
|
super(GameNarrator.NO_TITLE);
|
|
this.callback = callback;
|
|
this.lastScreen = lastScreen;
|
|
this.worldName = worldName;
|
|
this.worldDownload = worldDownload;
|
|
this.downloadStatus = new RealmsDownloadLatestWorldScreen.DownloadStatus();
|
|
this.downloadTitle = Component.translatable("mco.download.title");
|
|
this.narrationRateLimiter = RateLimiter.create(0.1F);
|
|
}
|
|
|
|
@Override
|
|
public void init() {
|
|
this.cancelButton = this.addRenderableWidget(
|
|
Button.builder(CommonComponents.GUI_CANCEL, button -> this.onClose()).bounds((this.width - 200) / 2, this.height - 42, 200, 20).build()
|
|
);
|
|
this.checkDownloadSize();
|
|
}
|
|
|
|
private void checkDownloadSize() {
|
|
if (!this.finished && !this.checked) {
|
|
this.checked = true;
|
|
if (this.getContentLength(this.worldDownload.downloadLink) >= 5368709120L) {
|
|
Component component = Component.translatable("mco.download.confirmation.oversized", Unit.humanReadable(5368709120L));
|
|
this.minecraft.setScreen(RealmsPopups.warningAcknowledgePopupScreen(this, component, popupScreen -> {
|
|
this.minecraft.setScreen(this);
|
|
this.downloadSave();
|
|
}));
|
|
} else {
|
|
this.downloadSave();
|
|
}
|
|
}
|
|
}
|
|
|
|
private long getContentLength(String uri) {
|
|
FileDownload fileDownload = new FileDownload();
|
|
return fileDownload.contentLength(uri);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
this.animTick++;
|
|
if (this.status != null && this.narrationRateLimiter.tryAcquire(1)) {
|
|
Component component = this.createProgressNarrationMessage();
|
|
this.minecraft.getNarrator().sayNow(component);
|
|
}
|
|
}
|
|
|
|
private Component createProgressNarrationMessage() {
|
|
List<Component> list = Lists.<Component>newArrayList();
|
|
list.add(this.downloadTitle);
|
|
list.add(this.status);
|
|
if (this.progress != null) {
|
|
list.add(Component.translatable("mco.download.percent", this.progress));
|
|
list.add(Component.translatable("mco.download.speed.narration", Unit.humanReadable(this.bytesPersSecond)));
|
|
}
|
|
|
|
if (this.errorMessage != null) {
|
|
list.add(this.errorMessage);
|
|
}
|
|
|
|
return CommonComponents.joinLines(list);
|
|
}
|
|
|
|
@Override
|
|
public void onClose() {
|
|
this.cancelled = true;
|
|
if (this.finished && this.callback != null && this.errorMessage == null) {
|
|
this.callback.accept(true);
|
|
}
|
|
|
|
this.minecraft.setScreen(this.lastScreen);
|
|
}
|
|
|
|
@Override
|
|
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
|
guiGraphics.drawCenteredString(this.font, this.downloadTitle, this.width / 2, 20, -1);
|
|
guiGraphics.drawCenteredString(this.font, this.status, this.width / 2, 50, -1);
|
|
if (this.showDots) {
|
|
this.drawDots(guiGraphics);
|
|
}
|
|
|
|
if (this.downloadStatus.bytesWritten != 0L && !this.cancelled) {
|
|
this.drawProgressBar(guiGraphics);
|
|
this.drawDownloadSpeed(guiGraphics);
|
|
}
|
|
|
|
if (this.errorMessage != null) {
|
|
guiGraphics.drawCenteredString(this.font, this.errorMessage, this.width / 2, 110, -65536);
|
|
}
|
|
}
|
|
|
|
private void drawDots(GuiGraphics guiGraphics) {
|
|
int i = this.font.width(this.status);
|
|
if (this.animTick != 0 && this.animTick % 10 == 0) {
|
|
this.dotIndex++;
|
|
}
|
|
|
|
guiGraphics.drawString(this.font, DOTS[this.dotIndex % DOTS.length], this.width / 2 + i / 2 + 5, 50, -1);
|
|
}
|
|
|
|
private void drawProgressBar(GuiGraphics guiGraphics) {
|
|
double d = Math.min((double)this.downloadStatus.bytesWritten / this.downloadStatus.totalBytes, 1.0);
|
|
this.progress = String.format(Locale.ROOT, "%.1f", d * 100.0);
|
|
int i = (this.width - 200) / 2;
|
|
int j = i + (int)Math.round(200.0 * d);
|
|
guiGraphics.fill(i - 1, 79, j + 1, 96, -1);
|
|
guiGraphics.fill(i, 80, j, 95, -8355712);
|
|
guiGraphics.drawCenteredString(this.font, Component.translatable("mco.download.percent", this.progress), this.width / 2, 84, -1);
|
|
}
|
|
|
|
private void drawDownloadSpeed(GuiGraphics guiGraphics) {
|
|
if (this.animTick % 20 == 0) {
|
|
if (this.previousWrittenBytes != null) {
|
|
long l = Util.getMillis() - this.previousTimeSnapshot;
|
|
if (l == 0L) {
|
|
l = 1L;
|
|
}
|
|
|
|
this.bytesPersSecond = 1000L * (this.downloadStatus.bytesWritten - this.previousWrittenBytes) / l;
|
|
this.drawDownloadSpeed0(guiGraphics, this.bytesPersSecond);
|
|
}
|
|
|
|
this.previousWrittenBytes = this.downloadStatus.bytesWritten;
|
|
this.previousTimeSnapshot = Util.getMillis();
|
|
} else {
|
|
this.drawDownloadSpeed0(guiGraphics, this.bytesPersSecond);
|
|
}
|
|
}
|
|
|
|
private void drawDownloadSpeed0(GuiGraphics guiGraphics, long bytesPerSecond) {
|
|
if (bytesPerSecond > 0L) {
|
|
int i = this.font.width(this.progress);
|
|
guiGraphics.drawString(this.font, Component.translatable("mco.download.speed", Unit.humanReadable(bytesPerSecond)), this.width / 2 + i / 2 + 15, 84, -1);
|
|
}
|
|
}
|
|
|
|
private void downloadSave() {
|
|
new Thread(() -> {
|
|
try {
|
|
try {
|
|
if (!DOWNLOAD_LOCK.tryLock(1L, TimeUnit.SECONDS)) {
|
|
this.status = Component.translatable("mco.download.failed");
|
|
return;
|
|
}
|
|
|
|
if (this.cancelled) {
|
|
this.downloadCancelled();
|
|
return;
|
|
}
|
|
|
|
this.status = Component.translatable("mco.download.downloading", this.worldName);
|
|
FileDownload fileDownload = new FileDownload();
|
|
fileDownload.contentLength(this.worldDownload.downloadLink);
|
|
fileDownload.download(this.worldDownload, this.worldName, this.downloadStatus, this.minecraft.getLevelSource());
|
|
|
|
while (!fileDownload.isFinished()) {
|
|
if (fileDownload.isError()) {
|
|
fileDownload.cancel();
|
|
this.errorMessage = Component.translatable("mco.download.failed");
|
|
this.cancelButton.setMessage(CommonComponents.GUI_DONE);
|
|
return;
|
|
}
|
|
|
|
if (fileDownload.isExtracting()) {
|
|
if (!this.extracting) {
|
|
this.status = Component.translatable("mco.download.extracting");
|
|
}
|
|
|
|
this.extracting = true;
|
|
}
|
|
|
|
if (this.cancelled) {
|
|
fileDownload.cancel();
|
|
this.downloadCancelled();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Thread.sleep(500L);
|
|
} catch (InterruptedException var8) {
|
|
LOGGER.error("Failed to check Realms backup download status");
|
|
}
|
|
}
|
|
|
|
this.finished = true;
|
|
this.status = Component.translatable("mco.download.done");
|
|
this.cancelButton.setMessage(CommonComponents.GUI_DONE);
|
|
return;
|
|
} catch (InterruptedException var9) {
|
|
LOGGER.error("Could not acquire upload lock");
|
|
} catch (Exception var10) {
|
|
this.errorMessage = Component.translatable("mco.download.failed");
|
|
LOGGER.info("Exception while downloading world", (Throwable)var10);
|
|
}
|
|
} finally {
|
|
if (!DOWNLOAD_LOCK.isHeldByCurrentThread()) {
|
|
return;
|
|
} else {
|
|
DOWNLOAD_LOCK.unlock();
|
|
this.showDots = false;
|
|
this.finished = true;
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
private void downloadCancelled() {
|
|
this.status = Component.translatable("mco.download.cancelled");
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public static class DownloadStatus {
|
|
public volatile long bytesWritten;
|
|
public volatile long totalBytes;
|
|
}
|
|
}
|