211 lines
8.7 KiB
Java
211 lines
8.7 KiB
Java
package com.mojang.realmsclient.client;
|
|
|
|
import org.apache.http.util.Args;
|
|
import java.io.OutputStream;
|
|
import org.apache.http.entity.InputStreamEntity;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.http.Header;
|
|
import java.time.Duration;
|
|
import java.util.function.Function;
|
|
import com.google.gson.JsonElement;
|
|
import java.util.Optional;
|
|
import com.google.gson.JsonParser;
|
|
import org.apache.http.util.EntityUtils;
|
|
import java.io.FileNotFoundException;
|
|
import org.apache.http.HttpEntity;
|
|
import java.io.InputStream;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import org.apache.http.HttpResponse;
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
import org.apache.http.client.methods.HttpUriRequest;
|
|
import org.apache.http.impl.client.HttpClientBuilder;
|
|
import org.apache.http.client.methods.HttpPost;
|
|
import java.util.function.Consumer;
|
|
import java.util.concurrent.TimeUnit;
|
|
import net.minecraft.client.User;
|
|
import org.apache.http.client.config.RequestConfig;
|
|
import com.mojang.realmsclient.gui.screens.UploadResult;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import com.mojang.realmsclient.dto.UploadInfo;
|
|
import java.io.File;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
public class FileUpload {
|
|
private static final Logger LOGGER;
|
|
private final File file;
|
|
private final long worldId;
|
|
private final int slotId;
|
|
private final UploadInfo uploadInfo;
|
|
private final String sessionId;
|
|
private final String username;
|
|
private final String clientVersion;
|
|
private final UploadStatus uploadStatus;
|
|
private final AtomicBoolean cancelled;
|
|
private CompletableFuture<UploadResult> uploadTask;
|
|
private final RequestConfig requestConfig;
|
|
|
|
public FileUpload(final File file, final long long2, final int integer, final UploadInfo dja, final User dml, final String string, final UploadStatus die) {
|
|
this.cancelled = new AtomicBoolean(false);
|
|
this.requestConfig = RequestConfig.custom().setSocketTimeout((int)TimeUnit.MINUTES.toMillis(10L)).setConnectTimeout((int)TimeUnit.SECONDS.toMillis(15L)).build();
|
|
this.file = file;
|
|
this.worldId = long2;
|
|
this.slotId = integer;
|
|
this.uploadInfo = dja;
|
|
this.sessionId = dml.getSessionId();
|
|
this.username = dml.getName();
|
|
this.clientVersion = string;
|
|
this.uploadStatus = die;
|
|
}
|
|
|
|
public void upload(final Consumer<UploadResult> consumer) {
|
|
if (this.uploadTask != null) {
|
|
return;
|
|
}
|
|
(this.uploadTask = CompletableFuture.<UploadResult>supplyAsync(() -> this.requestUpload(0))).thenAccept(consumer);
|
|
}
|
|
|
|
public void cancel() {
|
|
this.cancelled.set(true);
|
|
if (this.uploadTask != null) {
|
|
this.uploadTask.cancel(false);
|
|
this.uploadTask = null;
|
|
}
|
|
}
|
|
|
|
private UploadResult requestUpload(final int integer) {
|
|
final UploadResult.Builder a3 = new UploadResult.Builder();
|
|
if (this.cancelled.get()) {
|
|
return a3.build();
|
|
}
|
|
this.uploadStatus.totalBytes = this.file.length();
|
|
final HttpPost httpPost4 = new HttpPost("http://" + this.uploadInfo.getUploadEndpoint() + ":" + this.uploadInfo.getPort() + "/upload" + "/" + this.worldId + "/" + this.slotId);
|
|
final CloseableHttpClient closeableHttpClient5 = HttpClientBuilder.create().setDefaultRequestConfig(this.requestConfig).build();
|
|
try {
|
|
this.setupRequest(httpPost4);
|
|
final HttpResponse httpResponse6 = (HttpResponse)closeableHttpClient5.execute((HttpUriRequest)httpPost4);
|
|
final long long7 = this.getRetryDelaySeconds(httpResponse6);
|
|
if (this.shouldRetry(long7, integer)) {
|
|
return this.retryUploadAfter(long7, integer);
|
|
}
|
|
this.handleResponse(httpResponse6, a3);
|
|
}
|
|
catch (Exception exception6) {
|
|
if (!this.cancelled.get()) {
|
|
FileUpload.LOGGER.error("Caught exception while uploading: ", (Throwable)exception6);
|
|
}
|
|
}
|
|
finally {
|
|
this.cleanup(httpPost4, closeableHttpClient5);
|
|
}
|
|
return a3.build();
|
|
}
|
|
|
|
private void cleanup(final HttpPost httpPost, final CloseableHttpClient closeableHttpClient) {
|
|
httpPost.releaseConnection();
|
|
if (closeableHttpClient != null) {
|
|
try {
|
|
closeableHttpClient.close();
|
|
}
|
|
catch (IOException iOException4) {
|
|
FileUpload.LOGGER.error("Failed to close Realms upload client");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setupRequest(final HttpPost httpPost) throws FileNotFoundException {
|
|
httpPost.setHeader("Cookie", "sid=" + this.sessionId + ";token=" + this.uploadInfo.getToken() + ";user=" + this.username + ";version=" + this.clientVersion);
|
|
final CustomInputStreamEntity a3 = new CustomInputStreamEntity(new FileInputStream(this.file), this.file.length(), this.uploadStatus);
|
|
a3.setContentType("application/octet-stream");
|
|
httpPost.setEntity((HttpEntity)a3);
|
|
}
|
|
|
|
private void handleResponse(final HttpResponse httpResponse, final UploadResult.Builder a) throws IOException {
|
|
final int integer4 = httpResponse.getStatusLine().getStatusCode();
|
|
if (integer4 == 401) {
|
|
FileUpload.LOGGER.debug("Realms server returned 401: " + httpResponse.getFirstHeader("WWW-Authenticate"));
|
|
}
|
|
a.withStatusCode(integer4);
|
|
if (httpResponse.getEntity() != null) {
|
|
final String string5 = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
|
|
if (string5 != null) {
|
|
try {
|
|
final JsonParser jsonParser6 = new JsonParser();
|
|
final JsonElement jsonElement7 = jsonParser6.parse(string5).getAsJsonObject().get("errorMsg");
|
|
final Optional<String> optional8 = Optional.<JsonElement>ofNullable(jsonElement7).<String>map(JsonElement::getAsString);
|
|
a.withErrorMessage(optional8.orElse(null));
|
|
}
|
|
catch (Exception ex) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean shouldRetry(final long long1, final int integer) {
|
|
return long1 > 0L && integer + 1 < 5;
|
|
}
|
|
|
|
private UploadResult retryUploadAfter(final long long1, final int integer) throws InterruptedException {
|
|
Thread.sleep(Duration.ofSeconds(long1).toMillis());
|
|
return this.requestUpload(integer + 1);
|
|
}
|
|
|
|
private long getRetryDelaySeconds(final HttpResponse httpResponse) {
|
|
return Optional.<Header>ofNullable(httpResponse.getFirstHeader("Retry-After")).map(Header::getValue).<Long>map(Long::valueOf).orElse(0L);
|
|
}
|
|
|
|
public boolean isFinished() {
|
|
return this.uploadTask.isDone() || this.uploadTask.isCancelled();
|
|
}
|
|
|
|
static {
|
|
LOGGER = LogManager.getLogger();
|
|
}
|
|
|
|
static class CustomInputStreamEntity extends InputStreamEntity {
|
|
private final long length;
|
|
private final InputStream content;
|
|
private final UploadStatus uploadStatus;
|
|
|
|
public CustomInputStreamEntity(final InputStream inputStream, final long long2, final UploadStatus die) {
|
|
super(inputStream);
|
|
this.content = inputStream;
|
|
this.length = long2;
|
|
this.uploadStatus = die;
|
|
}
|
|
|
|
public void writeTo(final OutputStream outputStream) throws IOException {
|
|
Args.notNull(outputStream, "Output stream");
|
|
final InputStream inputStream3 = this.content;
|
|
try {
|
|
final byte[] arr4 = new byte[4096];
|
|
if (this.length < 0L) {
|
|
int integer5;
|
|
while ((integer5 = inputStream3.read(arr4)) != -1) {
|
|
outputStream.write(arr4, 0, integer5);
|
|
final UploadStatus uploadStatus = this.uploadStatus;
|
|
uploadStatus.bytesWritten += (Long)integer5;
|
|
}
|
|
}
|
|
else {
|
|
long long6 = this.length;
|
|
while (long6 > 0L) {
|
|
final int integer5 = inputStream3.read(arr4, 0, (int)Math.min(4096L, long6));
|
|
if (integer5 == -1) {
|
|
break;
|
|
}
|
|
outputStream.write(arr4, 0, integer5);
|
|
final UploadStatus uploadStatus2 = this.uploadStatus;
|
|
uploadStatus2.bytesWritten += (Long)integer5;
|
|
long6 -= integer5;
|
|
outputStream.flush();
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
inputStream3.close();
|
|
}
|
|
}
|
|
}
|
|
}
|