457 lines
16 KiB
Java
457 lines
16 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.RealmsMainScreen;
|
|
import com.mojang.realmsclient.Unit;
|
|
import com.mojang.realmsclient.client.FileUpload;
|
|
import com.mojang.realmsclient.client.RealmsClient;
|
|
import com.mojang.realmsclient.client.UploadStatus;
|
|
import com.mojang.realmsclient.dto.UploadInfo;
|
|
import com.mojang.realmsclient.exception.RealmsServiceException;
|
|
import com.mojang.realmsclient.exception.RetryCallException;
|
|
import com.mojang.realmsclient.util.UploadTokenCache;
|
|
import com.mojang.realmsclient.util.task.LongRunningTask;
|
|
import com.mojang.realmsclient.util.task.RealmCreationTask;
|
|
import com.mojang.realmsclient.util.task.SwitchSlotTask;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import java.util.zip.GZIPOutputStream;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.client.GameNarrator;
|
|
import net.minecraft.client.gui.GuiGraphics;
|
|
import net.minecraft.client.gui.components.AbstractWidget;
|
|
import net.minecraft.client.gui.components.Button;
|
|
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
|
|
import net.minecraft.client.gui.screens.TitleScreen;
|
|
import net.minecraft.network.chat.CommonComponents;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.realms.RealmsScreen;
|
|
import net.minecraft.world.level.storage.LevelSummary;
|
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
|
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class RealmsUploadScreen extends RealmsScreen {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final ReentrantLock UPLOAD_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 static final String[] DOTS = new String[]{"", ".", ". .", ". . ."};
|
|
private static final Component VERIFYING_TEXT = Component.translatable("mco.upload.verifying");
|
|
private final RealmsResetWorldScreen lastScreen;
|
|
private final LevelSummary selectedLevel;
|
|
@Nullable
|
|
private final RealmCreationTask realmCreationTask;
|
|
private final long realmId;
|
|
private final int slotId;
|
|
private final UploadStatus uploadStatus;
|
|
private final RateLimiter narrationRateLimiter;
|
|
@Nullable
|
|
private volatile Component[] errorMessage;
|
|
private volatile Component status = Component.translatable("mco.upload.preparing");
|
|
@Nullable
|
|
private volatile String progress;
|
|
private volatile boolean cancelled;
|
|
private volatile boolean uploadFinished;
|
|
private volatile boolean showDots = true;
|
|
private volatile boolean uploadStarted;
|
|
@Nullable
|
|
private Button backButton;
|
|
@Nullable
|
|
private Button cancelButton;
|
|
private int tickCount;
|
|
@Nullable
|
|
private Long previousWrittenBytes;
|
|
@Nullable
|
|
private Long previousTimeSnapshot;
|
|
private long bytesPersSecond;
|
|
private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this);
|
|
|
|
public RealmsUploadScreen(
|
|
@Nullable RealmCreationTask realmCreationTask, long realmId, int slotId, RealmsResetWorldScreen lastScreen, LevelSummary selectedLevel
|
|
) {
|
|
super(GameNarrator.NO_TITLE);
|
|
this.realmCreationTask = realmCreationTask;
|
|
this.realmId = realmId;
|
|
this.slotId = slotId;
|
|
this.lastScreen = lastScreen;
|
|
this.selectedLevel = selectedLevel;
|
|
this.uploadStatus = new UploadStatus();
|
|
this.narrationRateLimiter = RateLimiter.create(0.1F);
|
|
}
|
|
|
|
@Override
|
|
public void init() {
|
|
this.backButton = this.layout.addToFooter(Button.builder(CommonComponents.GUI_BACK, button -> this.onBack()).build());
|
|
this.backButton.visible = false;
|
|
this.cancelButton = this.layout.addToFooter(Button.builder(CommonComponents.GUI_CANCEL, button -> this.onCancel()).build());
|
|
if (!this.uploadStarted) {
|
|
if (this.lastScreen.slot == -1) {
|
|
this.uploadStarted = true;
|
|
this.upload();
|
|
} else {
|
|
List<LongRunningTask> list = new ArrayList();
|
|
if (this.realmCreationTask != null) {
|
|
list.add(this.realmCreationTask);
|
|
}
|
|
|
|
list.add(new SwitchSlotTask(this.realmId, this.lastScreen.slot, () -> {
|
|
if (!this.uploadStarted) {
|
|
this.uploadStarted = true;
|
|
this.minecraft.execute(() -> {
|
|
this.minecraft.setScreen(this);
|
|
this.upload();
|
|
});
|
|
}
|
|
}));
|
|
this.minecraft.setScreen(new RealmsLongRunningMcoTaskScreen(this.lastScreen, (LongRunningTask[])list.toArray(new LongRunningTask[0])));
|
|
}
|
|
}
|
|
|
|
this.layout.visitWidgets(guiEventListener -> {
|
|
AbstractWidget var10000 = this.addRenderableWidget(guiEventListener);
|
|
});
|
|
this.repositionElements();
|
|
}
|
|
|
|
@Override
|
|
protected void repositionElements() {
|
|
this.layout.arrangeElements();
|
|
}
|
|
|
|
private void onBack() {
|
|
this.minecraft.setScreen(new RealmsConfigureWorldScreen(new RealmsMainScreen(new TitleScreen()), this.realmId));
|
|
}
|
|
|
|
private void onCancel() {
|
|
this.cancelled = true;
|
|
this.minecraft.setScreen(this.lastScreen);
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
|
if (keyCode == 256) {
|
|
if (this.showDots) {
|
|
this.onCancel();
|
|
} else {
|
|
this.onBack();
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return super.keyPressed(keyCode, scanCode, modifiers);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
|
if (!this.uploadFinished
|
|
&& this.uploadStatus.bytesWritten != 0L
|
|
&& this.uploadStatus.bytesWritten == this.uploadStatus.totalBytes
|
|
&& this.cancelButton != null) {
|
|
this.status = VERIFYING_TEXT;
|
|
this.cancelButton.active = false;
|
|
}
|
|
|
|
guiGraphics.drawCenteredString(this.font, this.status, this.width / 2, 50, -1);
|
|
if (this.showDots) {
|
|
guiGraphics.drawString(this.font, DOTS[this.tickCount / 10 % DOTS.length], this.width / 2 + this.font.width(this.status) / 2 + 5, 50, -1, false);
|
|
}
|
|
|
|
if (this.uploadStatus.bytesWritten != 0L && !this.cancelled) {
|
|
this.drawProgressBar(guiGraphics);
|
|
this.drawUploadSpeed(guiGraphics);
|
|
}
|
|
|
|
Component[] components = this.errorMessage;
|
|
if (components != null) {
|
|
for (int i = 0; i < components.length; i++) {
|
|
guiGraphics.drawCenteredString(this.font, components[i], this.width / 2, 110 + 12 * i, -65536);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void drawProgressBar(GuiGraphics guiGraphics) {
|
|
double d = Math.min((double)this.uploadStatus.bytesWritten / this.uploadStatus.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.upload.percent", this.progress), this.width / 2, 84, -1);
|
|
}
|
|
|
|
private void drawUploadSpeed(GuiGraphics guiGraphics) {
|
|
if (this.tickCount % 20 == 0) {
|
|
if (this.previousWrittenBytes != null && this.previousTimeSnapshot != null) {
|
|
long l = Util.getMillis() - this.previousTimeSnapshot;
|
|
if (l == 0L) {
|
|
l = 1L;
|
|
}
|
|
|
|
this.bytesPersSecond = 1000L * (this.uploadStatus.bytesWritten - this.previousWrittenBytes) / l;
|
|
this.drawUploadSpeed0(guiGraphics, this.bytesPersSecond);
|
|
}
|
|
|
|
this.previousWrittenBytes = this.uploadStatus.bytesWritten;
|
|
this.previousTimeSnapshot = Util.getMillis();
|
|
} else {
|
|
this.drawUploadSpeed0(guiGraphics, this.bytesPersSecond);
|
|
}
|
|
}
|
|
|
|
private void drawUploadSpeed0(GuiGraphics guiGraphics, long bytesPerSecond) {
|
|
String string = this.progress;
|
|
if (bytesPerSecond > 0L && string != null) {
|
|
int i = this.font.width(string);
|
|
String string2 = "(" + Unit.humanReadable(bytesPerSecond) + "/s)";
|
|
guiGraphics.drawString(this.font, string2, this.width / 2 + i / 2 + 15, 84, -1, false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
this.tickCount++;
|
|
if (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.status);
|
|
if (this.progress != null) {
|
|
list.add(Component.translatable("mco.upload.percent", this.progress));
|
|
}
|
|
|
|
Component[] components = this.errorMessage;
|
|
if (components != null) {
|
|
list.addAll(Arrays.asList(components));
|
|
}
|
|
|
|
return CommonComponents.joinLines(list);
|
|
}
|
|
|
|
private void upload() {
|
|
new Thread(
|
|
() -> {
|
|
File file = null;
|
|
RealmsClient realmsClient = RealmsClient.create();
|
|
|
|
try {
|
|
if (!UPLOAD_LOCK.tryLock(1L, TimeUnit.SECONDS)) {
|
|
this.status = Component.translatable("mco.upload.close.failure");
|
|
} else {
|
|
UploadInfo uploadInfo = null;
|
|
|
|
for (int i = 0; i < 20; i++) {
|
|
try {
|
|
if (this.cancelled) {
|
|
this.uploadCancelled();
|
|
return;
|
|
}
|
|
|
|
uploadInfo = realmsClient.requestUploadInfo(this.realmId, UploadTokenCache.get(this.realmId));
|
|
if (uploadInfo != null) {
|
|
break;
|
|
}
|
|
} catch (RetryCallException var18) {
|
|
Thread.sleep(var18.delaySeconds * 1000);
|
|
}
|
|
}
|
|
|
|
if (uploadInfo == null) {
|
|
this.status = Component.translatable("mco.upload.close.failure");
|
|
} else {
|
|
UploadTokenCache.put(this.realmId, uploadInfo.getToken());
|
|
if (!uploadInfo.isWorldClosed()) {
|
|
this.status = Component.translatable("mco.upload.close.failure");
|
|
} else if (this.cancelled) {
|
|
this.uploadCancelled();
|
|
} else {
|
|
File file2 = new File(this.minecraft.gameDirectory.getAbsolutePath(), "saves");
|
|
file = this.tarGzipArchive(new File(file2, this.selectedLevel.getLevelId()));
|
|
if (this.cancelled) {
|
|
this.uploadCancelled();
|
|
} else if (this.verify(file)) {
|
|
this.status = Component.translatable("mco.upload.uploading", this.selectedLevel.getLevelName());
|
|
FileUpload fileUpload = new FileUpload(
|
|
file,
|
|
this.realmId,
|
|
this.slotId,
|
|
uploadInfo,
|
|
this.minecraft.getUser(),
|
|
SharedConstants.getCurrentVersion().getName(),
|
|
this.selectedLevel.levelVersion().minecraftVersionName(),
|
|
this.uploadStatus
|
|
);
|
|
fileUpload.upload(uploadResult -> {
|
|
if (uploadResult.statusCode >= 200 && uploadResult.statusCode < 300) {
|
|
this.uploadFinished = true;
|
|
this.status = Component.translatable("mco.upload.done");
|
|
if (this.backButton != null) {
|
|
this.backButton.setMessage(CommonComponents.GUI_DONE);
|
|
}
|
|
|
|
UploadTokenCache.invalidate(this.realmId);
|
|
} else if (uploadResult.statusCode == 400 && uploadResult.errorMessage != null) {
|
|
this.setErrorMessage(Component.translatable("mco.upload.failed", uploadResult.errorMessage));
|
|
} else {
|
|
this.setErrorMessage(Component.translatable("mco.upload.failed", uploadResult.statusCode));
|
|
}
|
|
});
|
|
|
|
while (!fileUpload.isFinished()) {
|
|
if (this.cancelled) {
|
|
fileUpload.cancel();
|
|
this.uploadCancelled();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Thread.sleep(500L);
|
|
} catch (InterruptedException var17) {
|
|
LOGGER.error("Failed to check Realms file upload status");
|
|
}
|
|
}
|
|
} else {
|
|
long l = file.length();
|
|
Unit unit = Unit.getLargest(l);
|
|
Unit unit2 = Unit.getLargest(5368709120L);
|
|
if (Unit.humanReadable(l, unit).equals(Unit.humanReadable(5368709120L, unit2)) && unit != Unit.B) {
|
|
Unit unit3 = Unit.values()[unit.ordinal() - 1];
|
|
this.setErrorMessage(
|
|
Component.translatable("mco.upload.size.failure.line1", this.selectedLevel.getLevelName()),
|
|
Component.translatable("mco.upload.size.failure.line2", Unit.humanReadable(l, unit3), Unit.humanReadable(5368709120L, unit3))
|
|
);
|
|
} else {
|
|
this.setErrorMessage(
|
|
Component.translatable("mco.upload.size.failure.line1", this.selectedLevel.getLevelName()),
|
|
Component.translatable("mco.upload.size.failure.line2", Unit.humanReadable(l, unit), Unit.humanReadable(5368709120L, unit2))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException var19) {
|
|
this.setErrorMessage(Component.translatable("mco.upload.failed", var19.getMessage()));
|
|
} catch (RealmsServiceException var20) {
|
|
this.setErrorMessage(Component.translatable("mco.upload.failed", var20.realmsError.errorMessage()));
|
|
} catch (InterruptedException var21) {
|
|
LOGGER.error("Could not acquire upload lock");
|
|
} finally {
|
|
this.uploadFinished = true;
|
|
if (UPLOAD_LOCK.isHeldByCurrentThread()) {
|
|
UPLOAD_LOCK.unlock();
|
|
this.showDots = false;
|
|
if (this.backButton != null) {
|
|
this.backButton.visible = true;
|
|
}
|
|
|
|
if (this.cancelButton != null) {
|
|
this.cancelButton.visible = false;
|
|
}
|
|
|
|
if (file != null) {
|
|
LOGGER.debug("Deleting file {}", file.getAbsolutePath());
|
|
file.delete();
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
)
|
|
.start();
|
|
}
|
|
|
|
private void setErrorMessage(Component... errorMessage) {
|
|
this.errorMessage = errorMessage;
|
|
}
|
|
|
|
private void uploadCancelled() {
|
|
this.status = Component.translatable("mco.upload.cancelled");
|
|
LOGGER.debug("Upload was cancelled");
|
|
}
|
|
|
|
private boolean verify(File file) {
|
|
return file.length() < 5368709120L;
|
|
}
|
|
|
|
private File tarGzipArchive(File file) throws IOException {
|
|
TarArchiveOutputStream tarArchiveOutputStream = null;
|
|
|
|
File var4;
|
|
try {
|
|
File file2 = File.createTempFile("realms-upload-file", ".tar.gz");
|
|
tarArchiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(file2)));
|
|
tarArchiveOutputStream.setLongFileMode(3);
|
|
this.addFileToTarGz(tarArchiveOutputStream, file.getAbsolutePath(), "world", true);
|
|
tarArchiveOutputStream.finish();
|
|
var4 = file2;
|
|
} finally {
|
|
if (tarArchiveOutputStream != null) {
|
|
tarArchiveOutputStream.close();
|
|
}
|
|
}
|
|
|
|
return var4;
|
|
}
|
|
|
|
private void addFileToTarGz(TarArchiveOutputStream tarArchiveOutputStream, String pathname, String name, boolean rootDirectory) throws IOException {
|
|
if (!this.cancelled) {
|
|
File file = new File(pathname);
|
|
String string = rootDirectory ? name : name + file.getName();
|
|
TarArchiveEntry tarArchiveEntry = new TarArchiveEntry(file, string);
|
|
tarArchiveOutputStream.putArchiveEntry(tarArchiveEntry);
|
|
if (file.isFile()) {
|
|
InputStream inputStream = new FileInputStream(file);
|
|
|
|
try {
|
|
inputStream.transferTo(tarArchiveOutputStream);
|
|
} catch (Throwable var14) {
|
|
try {
|
|
inputStream.close();
|
|
} catch (Throwable var13) {
|
|
var14.addSuppressed(var13);
|
|
}
|
|
|
|
throw var14;
|
|
}
|
|
|
|
inputStream.close();
|
|
tarArchiveOutputStream.closeArchiveEntry();
|
|
} else {
|
|
tarArchiveOutputStream.closeArchiveEntry();
|
|
File[] files = file.listFiles();
|
|
if (files != null) {
|
|
for (File file2 : files) {
|
|
this.addFileToTarGz(tarArchiveOutputStream, file2.getAbsolutePath(), string + "/", false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|