mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-06-28 12:08:09 +03:00
[FEATURE] Implement TextureUploadExtension
This commit is contained in:
parent
2470780591
commit
a03de56dde
5 changed files with 124 additions and 5 deletions
|
@ -8,6 +8,7 @@
|
|||
import pro.gravit.launcher.core.api.LauncherAPIHolder;
|
||||
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
|
||||
import pro.gravit.launcher.core.backend.LauncherBackendAPIHolder;
|
||||
import pro.gravit.launcher.runtime.backend.LauncherBackendImpl;
|
||||
|
@ -256,7 +257,8 @@ public void start(String... args) throws Throwable {
|
|||
return new LauncherAPI(Map.of(
|
||||
AuthFeatureAPI.class, impl,
|
||||
UserFeatureAPI.class, impl,
|
||||
ProfileFeatureAPI.class, impl));
|
||||
ProfileFeatureAPI.class, impl,
|
||||
TextureUploadFeatureAPI.class, impl));
|
||||
});
|
||||
LauncherBackendAPIHolder.setApi(new LauncherBackendImpl());
|
||||
//
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.method.AuthMethod;
|
||||
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
|
||||
import pro.gravit.launcher.core.api.model.SelfUser;
|
||||
|
@ -15,6 +16,7 @@
|
|||
import pro.gravit.launcher.core.backend.UserSettings;
|
||||
import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException;
|
||||
import pro.gravit.launcher.core.backend.extensions.Extension;
|
||||
import pro.gravit.launcher.core.backend.extensions.TextureUploadExtension;
|
||||
import pro.gravit.launcher.runtime.NewLauncherSettings;
|
||||
import pro.gravit.launcher.runtime.client.DirBridge;
|
||||
import pro.gravit.launcher.runtime.client.ServerPinger;
|
||||
|
@ -39,7 +41,7 @@
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class LauncherBackendImpl implements LauncherBackendAPI {
|
||||
public class LauncherBackendImpl implements LauncherBackendAPI, TextureUploadExtension {
|
||||
private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this);
|
||||
private volatile MainCallback callback;
|
||||
ExecutorService executorService;
|
||||
|
@ -285,8 +287,14 @@ public boolean isTestMode() {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends Extension> T getExtension(Class<T> clazz) {
|
||||
if(clazz == TextureUploadExtension.class) {
|
||||
if(authMethod != null && authMethod.getFeatures().contains(TextureUploadFeatureAPI.FEATURE_NAME)) {
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -303,4 +311,14 @@ public void shutdown() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TextureUploadFeatureAPI.TextureUploadInfo> fetchTextureUploadInfo() {
|
||||
return LauncherAPIHolder.get().get(TextureUploadFeatureAPI.class).fetchInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Texture> uploadTexture(String name, byte[] bytes, TextureUploadFeatureAPI.UploadSettings settings) {
|
||||
return LauncherAPIHolder.get().get(TextureUploadFeatureAPI.class).upload(name, bytes, settings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package pro.gravit.launcher.base.events.request;
|
||||
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class AssetUploadInfoRequestEvent extends RequestEvent {
|
||||
public class AssetUploadInfoRequestEvent extends RequestEvent implements TextureUploadFeatureAPI.TextureUploadInfo {
|
||||
public Set<String> available;
|
||||
public SlimSupportConf slimSupportConf;
|
||||
|
||||
|
@ -18,6 +19,16 @@ public String getType() {
|
|||
return "assetUploadInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequireManualSlimSkinSelect() {
|
||||
return slimSupportConf == SlimSupportConf.USER;
|
||||
}
|
||||
|
||||
public enum SlimSupportConf {
|
||||
UNSUPPORTED, USER, SERVER
|
||||
}
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launcher.base.request.auth.*;
|
||||
import pro.gravit.launcher.base.request.auth.password.*;
|
||||
import pro.gravit.launcher.base.request.cabinet.AssetUploadInfoRequest;
|
||||
import pro.gravit.launcher.base.request.cabinet.GetAssetUploadUrl;
|
||||
import pro.gravit.launcher.base.request.update.ProfilesRequest;
|
||||
import pro.gravit.launcher.base.request.update.UpdateRequest;
|
||||
import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest;
|
||||
import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest;
|
||||
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
|
||||
|
@ -17,19 +21,27 @@
|
|||
import pro.gravit.launcher.core.api.method.password.AuthPlainPassword;
|
||||
import pro.gravit.launcher.core.api.method.password.AuthTotpPassword;
|
||||
import pro.gravit.launcher.core.api.model.SelfUser;
|
||||
import pro.gravit.launcher.core.api.model.Texture;
|
||||
import pro.gravit.launcher.core.api.model.User;
|
||||
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI {
|
||||
public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI, TextureUploadFeatureAPI {
|
||||
private final RequestService request;
|
||||
private final String authId;
|
||||
private final HttpClient client = HttpClient.newBuilder().build();
|
||||
|
||||
public RequestFeatureAPIImpl(RequestService request, String authId) {
|
||||
this.request = request;
|
||||
|
@ -177,6 +189,66 @@ public CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName) {
|
|||
return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TextureUploadInfo> fetchInfo() {
|
||||
return request.request(new AssetUploadInfoRequest()).thenApply(response -> response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Texture> upload(String name, byte[] bytes, UploadSettings settings) {
|
||||
return request.request(new GetAssetUploadUrl(name)).thenCompose((response) -> {
|
||||
String accessToken = response.token == null ? Request.getAccessToken() : response.token.accessToken;
|
||||
String boundary = SecurityHelper.toHex(SecurityHelper.randomBytes(32));
|
||||
String jsonOptions = settings == null ? "{}" : Launcher.gsonManager.gson.toJson(new TextureUploadOptions(settings.slim()));
|
||||
byte[] preFileData;
|
||||
try(ByteArrayOutputStream output = new ByteArrayOutputStream(256)) {
|
||||
output.write("--".getBytes(StandardCharsets.UTF_8));
|
||||
output.write(boundary.getBytes(StandardCharsets.UTF_8));
|
||||
output.write("\r\nContent-Disposition: form-data; name=\"options\"\r\nContent-Type: application/json\r\n\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
output.write(jsonOptions.getBytes(StandardCharsets.UTF_8));
|
||||
output.write("\r\n--".getBytes(StandardCharsets.UTF_8));
|
||||
output.write(boundary.getBytes(StandardCharsets.UTF_8));
|
||||
output.write("\r\nContent-Disposition: form-data; name=\"file\"; filename=\"file\"\r\nContent-Type: image/png\r\n\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
preFileData = output.toByteArray();
|
||||
} catch (IOException ex) {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
byte[] postFileData;
|
||||
try(ByteArrayOutputStream output = new ByteArrayOutputStream(128)) {
|
||||
output.write("\r\n--".getBytes(StandardCharsets.UTF_8));
|
||||
output.write(boundary.getBytes(StandardCharsets.UTF_8));
|
||||
output.write("--\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
postFileData = output.toByteArray();
|
||||
} catch (IOException ex) {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
return client.sendAsync(HttpRequest.newBuilder()
|
||||
.uri(URI.create(response.url))
|
||||
.POST(HttpRequest.BodyPublishers.concat(HttpRequest.BodyPublishers.ofByteArray(preFileData),
|
||||
HttpRequest.BodyPublishers.ofByteArray(bytes),
|
||||
HttpRequest.BodyPublishers.ofByteArray(postFileData)))
|
||||
.header("Authorization", "Bearer "+accessToken)
|
||||
.header("Content-Type", "multipart/form-data; boundary=\""+boundary+"\"")
|
||||
.header("Accept", "application/json")
|
||||
.build(), HttpResponse.BodyHandlers.ofByteArray());
|
||||
}).thenCompose((response) -> {
|
||||
if(response.statusCode() >= 200 && response.statusCode() < 300) {
|
||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(response.body()))) {
|
||||
return CompletableFuture.completedFuture(Launcher.gsonManager.gson.fromJson(reader, UserTexture.class).toLauncherTexture());
|
||||
} catch (Throwable e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
} else {
|
||||
try(Reader reader = new InputStreamReader(new ByteArrayInputStream(response.body()))) {
|
||||
UploadError error = Launcher.gsonManager.gson.fromJson(reader, UploadError.class);
|
||||
return CompletableFuture.failedFuture(new RequestException(error.error()));
|
||||
} catch (Exception ex) {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {
|
||||
@Override
|
||||
public HashedDir getHashedDir() {
|
||||
|
@ -188,4 +260,19 @@ public String getUrl() {
|
|||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
public record TextureUploadOptions(boolean modelSlim) {
|
||||
|
||||
}
|
||||
|
||||
public record UserTexture(@LauncherNetworkAPI String url, @LauncherNetworkAPI String digest, @LauncherNetworkAPI Map<String, String> metadata) {
|
||||
|
||||
Texture toLauncherTexture() {
|
||||
return new pro.gravit.launcher.base.profiles.Texture(url, SecurityHelper.fromHex(digest), metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public record UploadError(@LauncherNetworkAPI String error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface TextureUploadFeatureAPI {
|
||||
public interface TextureUploadFeatureAPI extends FeatureAPI {
|
||||
String FEATURE_NAME = "assetupload";
|
||||
CompletableFuture<TextureUploadInfo> fetchInfo();
|
||||
CompletableFuture<Texture> upload(String name, byte[] bytes, UploadSettings settings);
|
||||
|
||||
|
|
Loading…
Reference in a new issue