263 lines
8 KiB
Java
263 lines
8 KiB
Java
package com.mojang.realmsclient.client;
|
|
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonParser;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.realmsclient.client.worldupload.RealmsUploadCanceledException;
|
|
import com.mojang.realmsclient.dto.UploadInfo;
|
|
import com.mojang.realmsclient.gui.screens.UploadResult;
|
|
import com.mojang.realmsclient.gui.screens.UploadResult.Builder;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.time.Duration;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.client.User;
|
|
import org.apache.http.HttpResponse;
|
|
import org.apache.http.NameValuePair;
|
|
import org.apache.http.client.config.RequestConfig;
|
|
import org.apache.http.client.methods.HttpPost;
|
|
import org.apache.http.entity.InputStreamEntity;
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
import org.apache.http.impl.client.HttpClientBuilder;
|
|
import org.apache.http.util.Args;
|
|
import org.apache.http.util.EntityUtils;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class FileUpload {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int MAX_RETRIES = 5;
|
|
private static final String UPLOAD_PATH = "/upload";
|
|
private final File file;
|
|
private final long realmId;
|
|
private final int slotId;
|
|
private final UploadInfo uploadInfo;
|
|
private final String sessionId;
|
|
private final String username;
|
|
private final String clientVersion;
|
|
private final String worldVersion;
|
|
private final UploadStatus uploadStatus;
|
|
final AtomicBoolean cancelled = new AtomicBoolean(false);
|
|
@Nullable
|
|
private CompletableFuture<UploadResult> uploadTask;
|
|
private final RequestConfig requestConfig = RequestConfig.custom()
|
|
.setSocketTimeout((int)TimeUnit.MINUTES.toMillis(10L))
|
|
.setConnectTimeout((int)TimeUnit.SECONDS.toMillis(15L))
|
|
.build();
|
|
|
|
public FileUpload(File file, long realmId, int slotId, UploadInfo uploadInfo, User user, String clientVersiob, String worldVersion, UploadStatus uploadStatus) {
|
|
this.file = file;
|
|
this.realmId = realmId;
|
|
this.slotId = slotId;
|
|
this.uploadInfo = uploadInfo;
|
|
this.sessionId = user.getSessionId();
|
|
this.username = user.getName();
|
|
this.clientVersion = clientVersiob;
|
|
this.worldVersion = worldVersion;
|
|
this.uploadStatus = uploadStatus;
|
|
}
|
|
|
|
public UploadResult upload() {
|
|
if (this.uploadTask != null) {
|
|
return new Builder().build();
|
|
} else {
|
|
this.uploadTask = CompletableFuture.supplyAsync(() -> this.requestUpload(0), Util.backgroundExecutor());
|
|
if (this.cancelled.get()) {
|
|
this.cancel();
|
|
return new Builder().build();
|
|
} else {
|
|
return (UploadResult)this.uploadTask.join();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void cancel() {
|
|
this.cancelled.set(true);
|
|
}
|
|
|
|
/**
|
|
* @param retries The number of times this upload has already been attempted
|
|
*/
|
|
private UploadResult requestUpload(int retries) {
|
|
Builder builder = new Builder();
|
|
if (this.cancelled.get()) {
|
|
return builder.build();
|
|
} else {
|
|
this.uploadStatus.setTotalBytes(this.file.length());
|
|
HttpPost httpPost = new HttpPost(this.uploadInfo.getUploadEndpoint().resolve("/upload/" + this.realmId + "/" + this.slotId));
|
|
CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(this.requestConfig).build();
|
|
|
|
UploadResult var8;
|
|
try {
|
|
this.setupRequest(httpPost);
|
|
HttpResponse httpResponse = closeableHttpClient.execute(httpPost);
|
|
long l = this.getRetryDelaySeconds(httpResponse);
|
|
if (!this.shouldRetry(l, retries)) {
|
|
this.handleResponse(httpResponse, builder);
|
|
return builder.build();
|
|
}
|
|
|
|
var8 = this.retryUploadAfter(l, retries);
|
|
} catch (Exception var12) {
|
|
if (!this.cancelled.get()) {
|
|
LOGGER.error("Caught exception while uploading: ", (Throwable)var12);
|
|
return builder.build();
|
|
}
|
|
|
|
throw new RealmsUploadCanceledException();
|
|
} finally {
|
|
this.cleanup(httpPost, closeableHttpClient);
|
|
}
|
|
|
|
return var8;
|
|
}
|
|
}
|
|
|
|
private void cleanup(HttpPost post, @Nullable CloseableHttpClient httpClient) {
|
|
post.releaseConnection();
|
|
if (httpClient != null) {
|
|
try {
|
|
httpClient.close();
|
|
} catch (IOException var4) {
|
|
LOGGER.error("Failed to close Realms upload client");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setupRequest(HttpPost post) throws FileNotFoundException {
|
|
post.setHeader(
|
|
"Cookie",
|
|
"sid="
|
|
+ this.sessionId
|
|
+ ";token="
|
|
+ this.uploadInfo.getToken()
|
|
+ ";user="
|
|
+ this.username
|
|
+ ";version="
|
|
+ this.clientVersion
|
|
+ ";worldVersion="
|
|
+ this.worldVersion
|
|
);
|
|
FileUpload.CustomInputStreamEntity customInputStreamEntity = new FileUpload.CustomInputStreamEntity(
|
|
new FileInputStream(this.file), this.file.length(), this.uploadStatus
|
|
);
|
|
customInputStreamEntity.setContentType("application/octet-stream");
|
|
post.setEntity(customInputStreamEntity);
|
|
}
|
|
|
|
private void handleResponse(HttpResponse response, Builder uploadResult) throws IOException {
|
|
int i = response.getStatusLine().getStatusCode();
|
|
if (i == 401) {
|
|
LOGGER.debug("Realms server returned 401: {}", response.getFirstHeader("WWW-Authenticate"));
|
|
}
|
|
|
|
uploadResult.withStatusCode(i);
|
|
if (response.getEntity() != null) {
|
|
String string = EntityUtils.toString(response.getEntity(), "UTF-8");
|
|
if (string != null) {
|
|
try {
|
|
JsonParser jsonParser = new JsonParser();
|
|
JsonElement jsonElement = jsonParser.parse(string).getAsJsonObject().get("errorMsg");
|
|
Optional<String> optional = Optional.ofNullable(jsonElement).map(JsonElement::getAsString);
|
|
uploadResult.withErrorMessage((String)optional.orElse(null));
|
|
} catch (Exception var8) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean shouldRetry(long retryDelaySeconds, int retries) {
|
|
return retryDelaySeconds > 0L && retries + 1 < 5;
|
|
}
|
|
|
|
private UploadResult retryUploadAfter(long seconds, int retries) throws InterruptedException {
|
|
Thread.sleep(Duration.ofSeconds(seconds).toMillis());
|
|
return this.requestUpload(retries + 1);
|
|
}
|
|
|
|
private long getRetryDelaySeconds(HttpResponse httpResponse) {
|
|
return (Long)Optional.ofNullable(httpResponse.getFirstHeader("Retry-After")).map(NameValuePair::getValue).map(Long::valueOf).orElse(0L);
|
|
}
|
|
|
|
public boolean isFinished() {
|
|
return this.uploadTask.isDone() || this.uploadTask.isCancelled();
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
class CustomInputStreamEntity extends InputStreamEntity {
|
|
private final long length;
|
|
private final InputStream content;
|
|
private final UploadStatus uploadStatus;
|
|
|
|
public CustomInputStreamEntity(final InputStream content, final long length, final UploadStatus uploadStatus) {
|
|
super(content);
|
|
this.content = content;
|
|
this.length = length;
|
|
this.uploadStatus = uploadStatus;
|
|
}
|
|
|
|
@Override
|
|
public void writeTo(OutputStream outputStream) throws IOException {
|
|
Args.notNull(outputStream, "Output stream");
|
|
InputStream inputStream = this.content;
|
|
|
|
try {
|
|
byte[] bs = new byte[4096];
|
|
int i;
|
|
if (this.length < 0L) {
|
|
while ((i = inputStream.read(bs)) != -1) {
|
|
if (FileUpload.this.cancelled.get()) {
|
|
throw new RealmsUploadCanceledException();
|
|
}
|
|
|
|
outputStream.write(bs, 0, i);
|
|
this.uploadStatus.onWrite(i);
|
|
}
|
|
} else {
|
|
long l = this.length;
|
|
|
|
while (l > 0L) {
|
|
i = inputStream.read(bs, 0, (int)Math.min(4096L, l));
|
|
if (i == -1) {
|
|
break;
|
|
}
|
|
|
|
if (FileUpload.this.cancelled.get()) {
|
|
throw new RealmsUploadCanceledException();
|
|
}
|
|
|
|
outputStream.write(bs, 0, i);
|
|
this.uploadStatus.onWrite(i);
|
|
l -= i;
|
|
outputStream.flush();
|
|
}
|
|
}
|
|
} catch (Throwable var8) {
|
|
if (inputStream != null) {
|
|
try {
|
|
inputStream.close();
|
|
} catch (Throwable var7) {
|
|
var8.addSuppressed(var7);
|
|
}
|
|
}
|
|
|
|
throw var8;
|
|
}
|
|
|
|
if (inputStream != null) {
|
|
inputStream.close();
|
|
}
|
|
}
|
|
}
|
|
}
|