FileUpload.java

com.mojang.realmsclient.client.FileUpload

信息

  • 全限定名:com.mojang.realmsclient.client.FileUpload
  • 类型:public class
  • 包:com.mojang.realmsclient.client
  • 源码路径:src/main/java/com/mojang/realmsclient/client/FileUpload.java
  • 起始行号:L31
  • 实现:AutoCloseable
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L32
    • 说明:

      TODO

  • MAX_RETRIES

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L33
    • 说明:

      TODO

  • UPLOAD_PATH

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L34
    • 说明:

      TODO

  • file

    • 类型: File
    • 修饰符: private final
    • 源码定位: L35
    • 说明:

      TODO

  • realmId

    • 类型: long
    • 修饰符: private final
    • 源码定位: L36
    • 说明:

      TODO

  • slotId

    • 类型: int
    • 修饰符: private final
    • 源码定位: L37
    • 说明:

      TODO

  • uploadInfo

    • 类型: UploadInfo
    • 修饰符: private final
    • 源码定位: L38
    • 说明:

      TODO

  • sessionId

    • 类型: String
    • 修饰符: private final
    • 源码定位: L39
    • 说明:

      TODO

  • username

    • 类型: String
    • 修饰符: private final
    • 源码定位: L40
    • 说明:

      TODO

  • clientVersion

    • 类型: String
    • 修饰符: private final
    • 源码定位: L41
    • 说明:

      TODO

  • worldVersion

    • 类型: String
    • 修饰符: private final
    • 源码定位: L42
    • 说明:

      TODO

  • uploadStatus

    • 类型: UploadStatus
    • 修饰符: private final
    • 源码定位: L43
    • 说明:

      TODO

  • client

    • 类型: HttpClient
    • 修饰符: private final
    • 源码定位: L44
    • 说明:

      TODO

内部类/嵌套类型

  • com.mojang.realmsclient.client.FileUpload.UploadCountingInputStream
    • 类型: class
    • 修饰符: private static
    • 源码定位: L152
    • 说明:

      TODO

构造器

public FileUpload(File file, long realmId, int slotId, UploadInfo uploadInfo, User user, String clientVersion, String worldVersion, UploadStatus uploadStatus) @ L46

  • 构造器名:FileUpload
  • 源码定位:L46
  • 修饰符:public

参数:

  • file: File
  • realmId: long
  • slotId: int
  • uploadInfo: UploadInfo
  • user: User
  • clientVersion: String
  • worldVersion: String
  • uploadStatus: UploadStatus

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void close() @ L61

  • 方法名:close
  • 源码定位:L61
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public CompletableFuture<UploadResult> startUpload() @ L66

  • 方法名:startUpload
  • 源码定位:L66
  • 返回类型:CompletableFuture
  • 修饰符:public

参数:

说明:

TODO

private CompletableFuture<UploadResult> requestUpload(int currentAttempt, long fileSize) @ L72

  • 方法名:requestUpload
  • 源码定位:L72
  • 返回类型:CompletableFuture
  • 修饰符:private

参数:

  • currentAttempt: int
  • fileSize: long

说明:

TODO

private static BodyPublisher inputStreamPublisherWithSize(Supplier<InputStream> inputStreamSupplier, long fileSize) @ L104

  • 方法名:inputStreamPublisherWithSize
  • 源码定位:L104
  • 返回类型:BodyPublisher
  • 修饰符:private static

参数:

  • inputStreamSupplier: Supplier
  • fileSize: long

说明:

TODO

private String uploadCookie() @ L108

  • 方法名:uploadCookie
  • 源码定位:L108
  • 返回类型:String
  • 修饰符:private

参数:

说明:

TODO

private UploadResult handleResponse(HttpResponse<String> response) @ L121

  • 方法名:handleResponse
  • 源码定位:L121
  • 返回类型:UploadResult
  • 修饰符:private

参数:

  • response: HttpResponse

说明:

TODO

private boolean shouldRetry(long retryDelaySeconds, int currentAttempt) @ L143

  • 方法名:shouldRetry
  • 源码定位:L143
  • 返回类型:boolean
  • 修饰符:private

参数:

  • retryDelaySeconds: long
  • currentAttempt: int

说明:

TODO

private long getRetryDelaySeconds(HttpResponse<?> response) @ L147

  • 方法名:getRetryDelaySeconds
  • 源码定位:L147
  • 返回类型:long
  • 修饰符:private

参数:

  • response: HttpResponse<?>

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class FileUpload implements AutoCloseable {
    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;
    private final HttpClient client;
 
    public FileUpload(
        File file, long realmId, int slotId, UploadInfo uploadInfo, User user, String clientVersion, 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 = clientVersion;
        this.worldVersion = worldVersion;
        this.uploadStatus = uploadStatus;
        this.client = HttpClient.newBuilder().executor(Util.nonCriticalIoPool()).connectTimeout(Duration.ofSeconds(15L)).build();
    }
 
    @Override
    public void close() {
        this.client.close();
    }
 
    public CompletableFuture<UploadResult> startUpload() {
        long fileSize = this.file.length();
        this.uploadStatus.setTotalBytes(fileSize);
        return this.requestUpload(0, fileSize);
    }
 
    private CompletableFuture<UploadResult> requestUpload(int currentAttempt, long fileSize) {
        BodyPublisher publisher = inputStreamPublisherWithSize(() -> {
            try {
                return new FileUpload.UploadCountingInputStream(new FileInputStream(this.file), this.uploadStatus);
            } catch (IOException var2) {
                LOGGER.warn("Failed to open file {}", this.file, var2);
                return null;
            }
        }, fileSize);
        HttpRequest request = HttpRequest.newBuilder(this.uploadInfo.uploadEndpoint().resolve("/upload/" + this.realmId + "/" + this.slotId))
            .timeout(Duration.ofMinutes(10L))
            .setHeader("Cookie", this.uploadCookie())
            .setHeader("Content-Type", "application/octet-stream")
            .POST(publisher)
            .build();
        return this.client.sendAsync(request, BodyHandlers.ofString(StandardCharsets.UTF_8)).thenCompose(response -> {
            long retryDelaySeconds = this.getRetryDelaySeconds((HttpResponse<?>)response);
            if (this.shouldRetry(retryDelaySeconds, currentAttempt)) {
                this.uploadStatus.restart();
 
                try {
                    Thread.sleep(Duration.ofSeconds(retryDelaySeconds));
                } catch (InterruptedException var8) {
                }
 
                return this.requestUpload(currentAttempt + 1, fileSize);
            } else {
                return CompletableFuture.completedFuture(this.handleResponse((HttpResponse<String>)response));
            }
        });
    }
 
    private static BodyPublisher inputStreamPublisherWithSize(Supplier<@Nullable InputStream> inputStreamSupplier, long fileSize) {
        return BodyPublishers.fromPublisher(BodyPublishers.ofInputStream(inputStreamSupplier), fileSize);
    }
 
    private String uploadCookie() {
        return "sid="
            + this.sessionId
            + ";token="
            + this.uploadInfo.token()
            + ";user="
            + this.username
            + ";version="
            + this.clientVersion
            + ";worldVersion="
            + this.worldVersion;
    }
 
    private UploadResult handleResponse(HttpResponse<String> response) {
        int statusCode = response.statusCode();
        if (statusCode == 401) {
            LOGGER.debug("Realms server returned 401: {}", response.headers().firstValue("WWW-Authenticate"));
        }
 
        String errorMessage = null;
        String body = response.body();
        if (body != null && !body.isBlank()) {
            try {
                JsonElement errorMsgElement = LenientJsonParser.parse(body).getAsJsonObject().get("errorMsg");
                if (errorMsgElement != null) {
                    errorMessage = errorMsgElement.getAsString();
                }
            } catch (Exception var6) {
                LOGGER.warn("Failed to parse response {}", body, var6);
            }
        }
 
        return new UploadResult(statusCode, errorMessage);
    }
 
    private boolean shouldRetry(long retryDelaySeconds, int currentAttempt) {
        return retryDelaySeconds > 0L && currentAttempt + 1 < 5;
    }
 
    private long getRetryDelaySeconds(HttpResponse<?> response) {
        return response.headers().firstValueAsLong("Retry-After").orElse(0L);
    }
 
    @OnlyIn(Dist.CLIENT)
    private static class UploadCountingInputStream extends CountingInputStream {
        private final UploadStatus uploadStatus;
 
        private UploadCountingInputStream(InputStream proxy, UploadStatus uploadStatus) {
            super(proxy);
            this.uploadStatus = uploadStatus;
        }
 
        @Override
        protected void afterRead(int n) throws IOException {
            super.afterRead(n);
            this.uploadStatus.onWrite(this.getByteCount());
        }
    }
}

引用的其他类

  • UploadStatus

    • 引用位置: 参数/字段
  • UploadInfo

    • 引用位置: 参数/字段
  • UploadResult

    • 引用位置: 构造调用/返回值
    • 关联成员: UploadResult()
  • User

    • 引用位置: 参数
  • LenientJsonParser

    • 引用位置: 方法调用
    • 关联成员: LenientJsonParser.parse()
  • Util

    • 引用位置: 方法调用
    • 关联成员: Util.nonCriticalIoPool()
  • EventLogDirectory

    • 引用位置: 参数/字段