mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-01-24 16:19:23 +03:00
[FEATURE][EXPERIMENTAL] New API: Launcher Backend
This commit is contained in:
parent
44e840734c
commit
6b873b5072
18 changed files with 1013 additions and 13 deletions
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launcher.runtime;
|
package pro.gravit.launcher.runtime;
|
||||||
|
|
||||||
import pro.gravit.launcher.runtime.client.UserSettings;
|
import pro.gravit.launcher.core.backend.UserSettings;
|
||||||
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package pro.gravit.launcher.runtime.backend;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||||
|
import pro.gravit.launcher.core.backend.UserSettings;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class BackendSettings extends UserSettings {
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public AuthorizationData auth;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public Map<UUID, ProfileSettingsImpl> settings;
|
||||||
|
public static class AuthorizationData {
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public String accessToken;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public String refreshToken;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public long expireIn;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
package pro.gravit.launcher.runtime.backend;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.Downloader;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
import pro.gravit.launcher.base.profiles.optional.OptionalView;
|
||||||
|
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
|
||||||
|
import pro.gravit.launcher.base.profiles.optional.actions.OptionalActionFile;
|
||||||
|
import pro.gravit.launcher.core.api.LauncherAPIHolder;
|
||||||
|
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||||
|
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
|
||||||
|
import pro.gravit.launcher.core.hasher.FileNameMatcher;
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedEntry;
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedFile;
|
||||||
|
import pro.gravit.launcher.runtime.client.DirBridge;
|
||||||
|
import pro.gravit.launcher.runtime.utils.AssetIndexHelper;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ClientDownloadImpl {
|
||||||
|
private LauncherBackendImpl backend;
|
||||||
|
|
||||||
|
ClientDownloadImpl(LauncherBackendImpl backend) {
|
||||||
|
this.backend = backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<LauncherBackendAPI.ReadyProfile> downloadProfile(ClientProfile profile, ProfileSettingsImpl settings, LauncherBackendAPI.DownloadCallback callback) {
|
||||||
|
AtomicReference<DownloadedDir> clientRef = new AtomicReference<>();
|
||||||
|
AtomicReference<DownloadedDir> assetRef = new AtomicReference<>();
|
||||||
|
AtomicReference<DownloadedDir> javaRef = new AtomicReference<>();
|
||||||
|
return downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback).thenCompose((clientDir -> {
|
||||||
|
clientRef.set(clientDir);
|
||||||
|
return downloadAsset(profile.getAssetDir(), profile.getAssetUpdateMatcher(), profile.getAssetIndex(), callback);
|
||||||
|
})).thenCompose(assetDir -> {
|
||||||
|
assetRef.set(assetDir);
|
||||||
|
return CompletableFuture.completedFuture((DownloadedDir)null); // TODO Custom Java
|
||||||
|
}).thenCompose(javaDir -> {
|
||||||
|
javaRef.set(javaDir);
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}).thenApply(v -> {
|
||||||
|
return new ReadyProfileImpl(backend, profile, settings, clientRef.get(), assetRef.get(), javaRef.get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<DownloadedDir> downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) {
|
||||||
|
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
|
||||||
|
Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString);
|
||||||
|
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName).thenComposeAsync((response) -> {
|
||||||
|
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_ASSET_VERIFY);
|
||||||
|
return verifyAssetIndex(assetIndexString, response, assetIndexPath, targetDir);
|
||||||
|
}, backend.executorService)
|
||||||
|
.thenApply(assetData -> {
|
||||||
|
HashedDir dir = assetData.updateInfo.getHashedDir();
|
||||||
|
AssetIndexHelper.modifyHashedDir(assetData.index, dir);
|
||||||
|
return new VirtualUpdateInfo(dir, assetData.updateInfo.getUrl());
|
||||||
|
})
|
||||||
|
.thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<AssetData> verifyAssetIndex(String assetIndexString, ProfileFeatureAPI.UpdateInfo response, Path assetIndexPath, Path targetDir) {
|
||||||
|
var assetIndexRelPath = String.format("indexes/%s.json", assetIndexString);
|
||||||
|
var assetIndexHash = response.getHashedDir().findRecursive(assetIndexRelPath);
|
||||||
|
if(!(assetIndexHash.entry instanceof HashedFile assetIndexHashFile)) {
|
||||||
|
return CompletableFuture.failedFuture(new FileNotFoundException(String.format("Asset Index %s not found in the server response", assetIndexString)));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if(Files.exists(assetIndexPath) && assetIndexHashFile.isSame(assetIndexPath, true)) {
|
||||||
|
var assetIndex = AssetIndexHelper.parse(assetIndexPath);
|
||||||
|
return CompletableFuture.completedFuture(new AssetData(response, assetIndex));
|
||||||
|
} else {
|
||||||
|
var downloader = Downloader.newDownloader(backend.executorService);
|
||||||
|
var list = new LinkedList<Downloader.SizedFile>();
|
||||||
|
list.add(new Downloader.SizedFile(assetIndexRelPath, assetIndexRelPath, assetIndexHashFile.size));
|
||||||
|
return downloader.downloadFiles(list, response.getUrl(), targetDir, null, backend.executorService, 1).thenComposeAsync(v -> {
|
||||||
|
try {
|
||||||
|
var assetIndex = AssetIndexHelper.parse(assetIndexPath);
|
||||||
|
return CompletableFuture.completedFuture(new AssetData(response, assetIndex));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return CompletableFuture.failedFuture(e);
|
||||||
|
}
|
||||||
|
}, backend.executorService);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return CompletableFuture.failedFuture(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<DownloadedDir> downloadDir(String dirName, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback) {
|
||||||
|
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
|
||||||
|
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName)
|
||||||
|
.thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e));
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<DownloadedDir> downloadDir(String dirName, FileNameMatcher matcher, OptionalView view, LauncherBackendAPI.DownloadCallback callback) {
|
||||||
|
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
|
||||||
|
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName)
|
||||||
|
.thenCompose(response -> {
|
||||||
|
var hashedDir = response.getHashedDir();
|
||||||
|
var remap = applyOptionalMods(view, hashedDir);
|
||||||
|
return downloadDir(targetDir, new VirtualUpdateInfo(hashedDir, response.getUrl()), matcher, callback, makePathRemapperFunction(remap));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<DownloadedDir> downloadDir(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback, Function<String, String> remap) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING);
|
||||||
|
HashedDir realFiles = new HashedDir(targetDir, matcher, false, true);
|
||||||
|
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF);
|
||||||
|
return realFiles.diff(updateInfo.getHashedDir(), matcher);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, backend.executorService).thenComposeAsync((diff) -> {
|
||||||
|
return downloadFiles(targetDir, updateInfo, callback, diff, remap);
|
||||||
|
}, backend.executorService).thenApply(v -> new DownloadedDir(updateInfo.getHashedDir(), targetDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<HashedDir.Diff> downloadFiles(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, LauncherBackendAPI.DownloadCallback callback, HashedDir.Diff diff, Function<String, String> remap) {
|
||||||
|
Downloader downloader = Downloader.newDownloader(backend.executorService);
|
||||||
|
try {
|
||||||
|
var files = collectFilesAndCreateDirectories(targetDir, diff.mismatch, remap);
|
||||||
|
long total = 0;
|
||||||
|
for(var e : files) {
|
||||||
|
total += e.size;
|
||||||
|
}
|
||||||
|
callback.onTotalDownload(total);
|
||||||
|
callback.onCanCancel(downloader::cancel);
|
||||||
|
return downloader.downloadFiles(files, updateInfo.getUrl(), targetDir, new Downloader.DownloadCallback() {
|
||||||
|
@Override
|
||||||
|
public void apply(long fullDiff) {
|
||||||
|
callback.onCurrentDownloaded(fullDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(Path path) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}, backend.executorService, 4).thenComposeAsync(v -> {
|
||||||
|
callback.onCanCancel(null);
|
||||||
|
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DELETE_EXTRA);
|
||||||
|
try {
|
||||||
|
deleteExtraDir(targetDir, diff.extra, diff.extra.flag);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return CompletableFuture.failedFuture(ex);
|
||||||
|
}
|
||||||
|
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DONE_PART);
|
||||||
|
return CompletableFuture.completedFuture(diff);
|
||||||
|
}, backend.executorService);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return CompletableFuture.failedFuture(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Downloader.SizedFile> collectFilesAndCreateDirectories(Path dir, HashedDir mismatch, Function<String, String> pathRemapper) throws IOException {
|
||||||
|
List<Downloader.SizedFile> files = new ArrayList<>();
|
||||||
|
mismatch.walk(File.separator, (path, name, entry) -> {
|
||||||
|
if(entry.getType() == HashedEntry.Type.DIR) {
|
||||||
|
Files.createDirectory(dir.resolve(path));
|
||||||
|
return HashedDir.WalkAction.CONTINUE;
|
||||||
|
}
|
||||||
|
String pathFixed = path.replace(File.separatorChar, '/');
|
||||||
|
files.add(new Downloader.SizedFile(pathFixed, pathRemapper.apply(pathFixed), entry.size()));
|
||||||
|
return HashedDir.WalkAction.CONTINUE;
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean deleteDir) throws IOException {
|
||||||
|
for (Map.Entry<String, HashedEntry> mapEntry : subHDir.map().entrySet()) {
|
||||||
|
String name = mapEntry.getKey();
|
||||||
|
Path path = subDir.resolve(name);
|
||||||
|
|
||||||
|
// Delete list and dirs based on type
|
||||||
|
HashedEntry entry = mapEntry.getValue();
|
||||||
|
HashedEntry.Type entryType = entry.getType();
|
||||||
|
switch (entryType) {
|
||||||
|
case FILE -> Files.delete(path);
|
||||||
|
case DIR -> deleteExtraDir(path, (HashedDir) entry, deleteDir || entry.flag);
|
||||||
|
default -> throw new AssertionError("Unsupported hashed entry type: " + entryType.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete!
|
||||||
|
if (deleteDir) {
|
||||||
|
Files.delete(subDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<String, String> makePathRemapperFunction(LinkedList<PathRemapperData> map) {
|
||||||
|
return (path) -> {
|
||||||
|
for(var e : map) {
|
||||||
|
if(path.startsWith(e.key)) {
|
||||||
|
return e.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedList<PathRemapperData> applyOptionalMods(OptionalView view, HashedDir hdir) {
|
||||||
|
for (OptionalAction action : view.getDisabledActions()) {
|
||||||
|
if (action instanceof OptionalActionFile optionalActionFile) {
|
||||||
|
optionalActionFile.disableInHashedDir(hdir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LinkedList<PathRemapperData> pathRemapper = new LinkedList<>();
|
||||||
|
Set<OptionalActionFile> fileActions = view.getActionsByClass(OptionalActionFile.class);
|
||||||
|
for (OptionalActionFile file : fileActions) {
|
||||||
|
file.injectToHashedDir(hdir);
|
||||||
|
file.files.forEach((k, v) -> {
|
||||||
|
if (v == null || v.isEmpty()) return;
|
||||||
|
pathRemapper.add(new PathRemapperData(v, k)); //reverse (!)
|
||||||
|
LogHelper.dev("Remap prepare %s to %s", v, k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pathRemapper.sort(Comparator.comparingInt(c -> -c.key.length())); // Support deep remap
|
||||||
|
return pathRemapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PathRemapperData(String key, String value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
record AssetData(ProfileFeatureAPI.UpdateInfo updateInfo, AssetIndexHelper.AssetIndex index) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
record DownloadedDir(HashedDir dir, Path path) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
record VirtualUpdateInfo(HashedDir dir, String url) implements ProfileFeatureAPI.UpdateInfo {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HashedDir getHashedDir() {
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
package pro.gravit.launcher.runtime.backend;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
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.method.AuthMethod;
|
||||||
|
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
|
||||||
|
import pro.gravit.launcher.core.api.model.SelfUser;
|
||||||
|
import pro.gravit.launcher.core.api.model.Texture;
|
||||||
|
import pro.gravit.launcher.core.api.model.UserPermissions;
|
||||||
|
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
|
||||||
|
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.runtime.NewLauncherSettings;
|
||||||
|
import pro.gravit.launcher.runtime.managers.SettingsManager;
|
||||||
|
import pro.gravit.launcher.runtime.utils.LauncherUpdater;
|
||||||
|
import pro.gravit.utils.helper.JavaHelper;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class LauncherBackendImpl implements LauncherBackendAPI {
|
||||||
|
private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this);
|
||||||
|
private volatile MainCallback callback;
|
||||||
|
ExecutorService executorService;
|
||||||
|
private volatile AuthMethod authMethod;
|
||||||
|
// Settings
|
||||||
|
private SettingsManager settingsManager;
|
||||||
|
private NewLauncherSettings allSettings;
|
||||||
|
private BackendSettings backendSettings;
|
||||||
|
// Data
|
||||||
|
private volatile List<ProfileFeatureAPI.ClientProfile> profiles;
|
||||||
|
private volatile UserPermissions permissions;
|
||||||
|
private volatile SelfUser selfUser;
|
||||||
|
private volatile List<Java> availableJavas;
|
||||||
|
private volatile CompletableFuture<List<Java>> availableJavasFuture;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCallback(MainCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doInit() throws Exception {
|
||||||
|
executorService = Executors.newScheduledThreadPool(2, (r) -> {
|
||||||
|
Thread thread = new Thread(r);
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
});
|
||||||
|
registerUserSettings("backend", BackendSettings.class);
|
||||||
|
settingsManager = new SettingsManager();
|
||||||
|
settingsManager.generateConfigIfNotExists();
|
||||||
|
settingsManager.loadConfig();
|
||||||
|
allSettings = settingsManager.getConfig();
|
||||||
|
backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings());
|
||||||
|
permissions = new ClientPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<LauncherInitData> init() {
|
||||||
|
try {
|
||||||
|
doInit();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return CompletableFuture.failedFuture(e);
|
||||||
|
}
|
||||||
|
return LauncherAPIHolder.core().checkUpdates().thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> {
|
||||||
|
if(updatesInfo.required()) {
|
||||||
|
try {
|
||||||
|
LauncherUpdater.prepareUpdate(URI.create(updatesInfo.url()).toURL());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
callback.onShutdown();
|
||||||
|
LauncherUpdater.restart();
|
||||||
|
}
|
||||||
|
return new LauncherInitData(authMethods);
|
||||||
|
}, executorService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void selectAuthMethod(AuthMethod method) {
|
||||||
|
this.authMethod = method;
|
||||||
|
LauncherAPIHolder.changeAuthId(method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<SelfUser> tryAuthorize() {
|
||||||
|
if(this.authMethod == null) {
|
||||||
|
return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod"));
|
||||||
|
}
|
||||||
|
if(backendSettings.auth == null) {
|
||||||
|
return CompletableFuture.failedFuture(new LauncherBackendException("Auth data not found"));
|
||||||
|
}
|
||||||
|
if(backendSettings.auth.expireIn > 0 && LocalDateTime.ofInstant(Instant.ofEpochMilli(backendSettings.auth.expireIn), ZoneOffset.UTC).isBefore(LocalDateTime.now(ZoneOffset.UTC))) {
|
||||||
|
return LauncherAPIHolder.auth().refreshToken(backendSettings.auth.refreshToken).thenCompose((response) -> {
|
||||||
|
setAuthToken(response);
|
||||||
|
return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true);
|
||||||
|
}).thenApply((user) -> {
|
||||||
|
onAuthorize(user);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true).thenApply((user) -> {
|
||||||
|
onAuthorize(user);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAuthToken(AuthFeatureAPI.AuthToken authToken) {
|
||||||
|
backendSettings.auth = new BackendSettings.AuthorizationData();
|
||||||
|
backendSettings.auth.accessToken = authToken.getAccessToken();
|
||||||
|
backendSettings.auth.refreshToken = authToken.getRefreshToken();
|
||||||
|
if(authToken.getExpire() <= 0) {
|
||||||
|
backendSettings.auth.expireIn = 0;
|
||||||
|
}
|
||||||
|
backendSettings.auth.expireIn = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
|
.plus(authToken.getExpire(), ChronoUnit.MILLIS).
|
||||||
|
toEpochSecond(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAuthorize(SelfUser selfUser) {
|
||||||
|
permissions = selfUser.getPermissions();
|
||||||
|
callback.onAuthorize(selfUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<SelfUser> authorize(String login, AuthMethodPassword password) {
|
||||||
|
if(this.authMethod == null) {
|
||||||
|
return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod"));
|
||||||
|
}
|
||||||
|
return LauncherAPIHolder.auth().auth(login, password).thenApply((response) -> {
|
||||||
|
setAuthToken(response.authToken());
|
||||||
|
onAuthorize(response.user());
|
||||||
|
return response.user();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> fetchProfiles() {
|
||||||
|
return LauncherAPIHolder.profile().getProfiles().thenApply((profiles) -> {
|
||||||
|
this.profiles = profiles;
|
||||||
|
callback.onProfiles(profiles);
|
||||||
|
return profiles;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile) {
|
||||||
|
var settings = backendSettings.settings.get(profile.getUUID());
|
||||||
|
if(settings == null) {
|
||||||
|
settings = new ProfileSettingsImpl((ClientProfile) profile);
|
||||||
|
} else {
|
||||||
|
settings = settings.copy();
|
||||||
|
settings.initAfterGson((ClientProfile) profile, this);
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveClientProfileSettings(ClientProfileSettings settings) {
|
||||||
|
var impl = (ProfileSettingsImpl) settings;
|
||||||
|
impl.updateEnabledMods();
|
||||||
|
backendSettings.settings.put(impl.profile.getUUID(), impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback) {
|
||||||
|
return clientDownloadImpl.downloadProfile((ClientProfile) profile, (ProfileSettingsImpl) settings, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<byte[]> fetchTexture(Texture texture) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<List<Java>> getAvailableJava() {
|
||||||
|
if(availableJavas == null) {
|
||||||
|
if(availableJavasFuture == null) {
|
||||||
|
availableJavasFuture = CompletableFuture.supplyAsync(() -> {
|
||||||
|
return (List) JavaHelper.findJava(); // TODO: Custom Java
|
||||||
|
}, executorService).thenApply(e -> {
|
||||||
|
availableJavas = e;
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CompletableFuture.completedFuture(availableJavas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerUserSettings(String name, Class<? extends UserSettings> clazz) {
|
||||||
|
UserSettings.providers.register(name, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSettings getUserSettings(String name, Function<String, UserSettings> ifNotExist) {
|
||||||
|
return allSettings.userSettings.computeIfAbsent(name, ifNotExist);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserPermissions getPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(String permission) {
|
||||||
|
return permissions.hasPerm(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return selfUser == null ? "Player" : getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SelfUser getSelfUser() {
|
||||||
|
return selfUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Extension> T getExtension(Class<T> clazz) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
try {
|
||||||
|
settingsManager.saveConfig();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogHelper.error("Config not saved", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
package pro.gravit.launcher.runtime.backend;
|
||||||
|
|
||||||
|
import oshi.SystemInfo;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
import pro.gravit.launcher.base.profiles.optional.OptionalFile;
|
||||||
|
import pro.gravit.launcher.base.profiles.optional.OptionalView;
|
||||||
|
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||||
|
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||||
|
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
|
||||||
|
import pro.gravit.launcher.runtime.utils.SystemMemory;
|
||||||
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
|
import pro.gravit.utils.helper.JavaHelper;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings {
|
||||||
|
transient ClientProfile profile;
|
||||||
|
transient LauncherBackendImpl backend;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
private Map<MemoryClass, Long> ram;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
private Set<Flag> flags;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
private Set<String> enabled;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
private String saveJavaPath;
|
||||||
|
transient OptionalView view;
|
||||||
|
transient JavaHelper.JavaVersion selectedJava;
|
||||||
|
|
||||||
|
public ProfileSettingsImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileSettingsImpl(ClientProfile profile) {
|
||||||
|
this.profile = profile;
|
||||||
|
var def = profile.getSettings();
|
||||||
|
this.ram = new HashMap<>();
|
||||||
|
this.ram.put(MemoryClass.TOTAL, ((long)def.ram) << 20);
|
||||||
|
this.flags = new HashSet<>();
|
||||||
|
if(def.autoEnter) {
|
||||||
|
this.flags.add(Flag.AUTO_ENTER);
|
||||||
|
}
|
||||||
|
if(def.fullScreen) {
|
||||||
|
this.flags.add(Flag.FULLSCREEN);
|
||||||
|
}
|
||||||
|
this.view = new OptionalView(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getReservedMemoryBytes(MemoryClass memoryClass) {
|
||||||
|
return ram.getOrDefault(memoryClass, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxMemoryBytes(MemoryClass memoryClass) {
|
||||||
|
try {
|
||||||
|
return SystemMemory.getPhysicalMemorySize();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
SystemInfo systemInfo = new SystemInfo();
|
||||||
|
return systemInfo.getHardware().getMemory().getTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReservedMemoryBytes(MemoryClass memoryClass, long value) {
|
||||||
|
this.ram.put(memoryClass, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Flag> getFlags() {
|
||||||
|
return Collections.unmodifiableSet(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Flag> getAvailableFlags() {
|
||||||
|
Set<Flag> set = new HashSet<>();
|
||||||
|
set.add(Flag.AUTO_ENTER);
|
||||||
|
set.add(Flag.FULLSCREEN);
|
||||||
|
if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {
|
||||||
|
set.add(Flag.LINUX_WAYLAND_SUPPORT);
|
||||||
|
}
|
||||||
|
if(backend.hasPermission("launcher.debug.skipfilemonitor")) {
|
||||||
|
set.add(Flag.DEBUG_SKIP_FILE_MONITOR);
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFlag(Flag flag) {
|
||||||
|
return flags.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addFlag(Flag flag) {
|
||||||
|
flags.add(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFlag(Flag flag) {
|
||||||
|
flags.remove(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
@Override
|
||||||
|
public Set<ProfileFeatureAPI.OptionalMod> getEnabledOptionals() {
|
||||||
|
return (Set) view.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) {
|
||||||
|
view.enable((OptionalFile) mod, true, callback::onChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) {
|
||||||
|
view.disable((OptionalFile) mod, callback::onChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaHelper.JavaVersion getSelectedJava() {
|
||||||
|
return selectedJava;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedJava(LauncherBackendAPI.Java java) {
|
||||||
|
selectedJava = (JavaHelper.JavaVersion) java;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecommended(LauncherBackendAPI.Java java) {
|
||||||
|
return java.getMajorVersion() == profile.getRecommendJavaVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompatible(LauncherBackendAPI.Java java) {
|
||||||
|
return java.getMajorVersion() >= profile.getMinJavaVersion() && java.getMajorVersion() <= profile.getMaxJavaVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProfileSettingsImpl copy() {
|
||||||
|
ProfileSettingsImpl cloned = new ProfileSettingsImpl();
|
||||||
|
cloned.profile = profile;
|
||||||
|
cloned.ram = new HashMap<>(ram);
|
||||||
|
cloned.flags = new HashSet<>(flags);
|
||||||
|
cloned.enabled = new HashSet<>(enabled);
|
||||||
|
if(view != null) {
|
||||||
|
cloned.view = new OptionalView(profile, view);
|
||||||
|
}
|
||||||
|
cloned.selectedJava = selectedJava;
|
||||||
|
cloned.saveJavaPath = saveJavaPath;
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateEnabledMods() {
|
||||||
|
enabled = new HashSet<>();
|
||||||
|
for(var e : view.enabled) {
|
||||||
|
enabled.add(e.name);
|
||||||
|
}
|
||||||
|
saveJavaPath = selectedJava.getPath().toAbsolutePath().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) {
|
||||||
|
this.backend = backend;
|
||||||
|
this.profile = profile;
|
||||||
|
this.view = new OptionalView(profile);
|
||||||
|
for(var e : enabled) {
|
||||||
|
var opt = profile.getOptionalFile(e);
|
||||||
|
if(opt == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
enableOptional(opt, (var1, var2) -> {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package pro.gravit.launcher.runtime.backend;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
|
||||||
|
import pro.gravit.launcher.base.profiles.PlayerProfile;
|
||||||
|
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||||
|
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
|
||||||
|
import pro.gravit.launcher.runtime.client.ClientLauncherProcess;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class ReadyProfileImpl implements LauncherBackendAPI.ReadyProfile {
|
||||||
|
private LauncherBackendImpl backend;
|
||||||
|
private ClientProfile profile;
|
||||||
|
private ProfileSettingsImpl settings;
|
||||||
|
private ClientDownloadImpl.DownloadedDir clientDir;
|
||||||
|
private ClientDownloadImpl.DownloadedDir assetDir;
|
||||||
|
private ClientDownloadImpl.DownloadedDir javaDir;
|
||||||
|
private volatile Thread writeParamsThread;
|
||||||
|
private volatile Thread runThread;
|
||||||
|
private volatile ClientLauncherProcess process;
|
||||||
|
private volatile Process nativeProcess;
|
||||||
|
private volatile LauncherBackendAPI.RunCallback callback;
|
||||||
|
|
||||||
|
public ReadyProfileImpl(LauncherBackendImpl backend, ClientProfile profile, ProfileSettingsImpl settings, ClientDownloadImpl.DownloadedDir clientDir, ClientDownloadImpl.DownloadedDir assetDir, ClientDownloadImpl.DownloadedDir javaDir) {
|
||||||
|
this.backend = backend;
|
||||||
|
this.profile = profile;
|
||||||
|
this.settings = settings;
|
||||||
|
this.clientDir = clientDir;
|
||||||
|
this.assetDir = assetDir;
|
||||||
|
this.javaDir = javaDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProfileFeatureAPI.ClientProfile getClientProfile() {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LauncherBackendAPI.ClientProfileSettings getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(LauncherBackendAPI.RunCallback callback) {
|
||||||
|
if(isAlive()) {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
this.callback = callback;
|
||||||
|
if(backend.hasPermission("launcher.debug.skipfilemonitor") && settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.DEBUG_SKIP_FILE_MONITOR)) {
|
||||||
|
var builder = new ClientProfileBuilder(profile);
|
||||||
|
builder.setUpdate(new ArrayList<>());
|
||||||
|
builder.setUpdateVerify(new ArrayList<>());
|
||||||
|
builder.setUpdateExclusions(new ArrayList<>());
|
||||||
|
profile = builder.createClientProfile();
|
||||||
|
}
|
||||||
|
process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), settings.getSelectedJava(), clientDir.path().resolve("resourcepacks"),
|
||||||
|
profile, new PlayerProfile(backend.getSelfUser()), settings.view, backend.getSelfUser().getAccessToken(),
|
||||||
|
clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir());
|
||||||
|
process.params.ram = (int) (settings.getReservedMemoryBytes(LauncherBackendAPI.ClientProfileSettings.MemoryClass.TOTAL) >> 20);
|
||||||
|
if (process.params.ram > 0) {
|
||||||
|
process.jvmArgs.add("-Xms" + process.params.ram + 'M');
|
||||||
|
process.jvmArgs.add("-Xmx" + process.params.ram + 'M');
|
||||||
|
}
|
||||||
|
process.params.fullScreen = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.FULLSCREEN);
|
||||||
|
process.params.autoEnter = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.AUTO_ENTER);
|
||||||
|
if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {
|
||||||
|
process.params.lwjglGlfwWayland = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.LINUX_WAYLAND_SUPPORT);
|
||||||
|
}
|
||||||
|
writeParamsThread = new Thread(this::writeParams);
|
||||||
|
writeParamsThread.setDaemon(true);
|
||||||
|
writeParamsThread.start();
|
||||||
|
runThread = new Thread(this::readThread);
|
||||||
|
runThread.setDaemon(true);
|
||||||
|
runThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readThread() {
|
||||||
|
try {
|
||||||
|
process.start(true);
|
||||||
|
nativeProcess = process.getProcess();
|
||||||
|
callback.onCanTerminate(this::terminate);
|
||||||
|
InputStream stream = nativeProcess.getInputStream();
|
||||||
|
byte[] buf = IOHelper.newBuffer();
|
||||||
|
try {
|
||||||
|
for (int length = stream.read(buf); length >= 0; length = stream.read(buf)) {
|
||||||
|
callback.onNormalOutput(buf, 0, length);
|
||||||
|
}
|
||||||
|
} catch (EOFException ignored) {
|
||||||
|
}
|
||||||
|
if (nativeProcess.isAlive()) {
|
||||||
|
int code = nativeProcess.waitFor();
|
||||||
|
callback.onFinished(code);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(e instanceof InterruptedException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
if(nativeProcess == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nativeProcess.destroyForcibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlive() {
|
||||||
|
return nativeProcess != null && nativeProcess.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeParams() {
|
||||||
|
try {
|
||||||
|
process.runWriteParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package pro.gravit.launcher.runtime.client;
|
package pro.gravit.launcher.runtime.client;
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import pro.gravit.launcher.core.backend.UserSettings;
|
||||||
import pro.gravit.launcher.start.RuntimeModuleManager;
|
import pro.gravit.launcher.start.RuntimeModuleManager;
|
||||||
import pro.gravit.launcher.core.managers.GsonManager;
|
import pro.gravit.launcher.core.managers.GsonManager;
|
||||||
import pro.gravit.launcher.base.modules.events.PreGsonPhase;
|
import pro.gravit.launcher.base.modules.events.PreGsonPhase;
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package pro.gravit.launcher.runtime.utils;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedEntry;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class AssetIndexHelper {
|
||||||
|
|
||||||
|
public static AssetIndex parse(Path path) throws IOException {
|
||||||
|
try (Reader reader = IOHelper.newReader(path)) {
|
||||||
|
return Launcher.gsonManager.gson.fromJson(reader, AssetIndex.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void modifyHashedDir(AssetIndex index, HashedDir original) {
|
||||||
|
Set<String> hashes = new HashSet<>();
|
||||||
|
for (AssetIndexObject obj : index.objects.values()) {
|
||||||
|
hashes.add(obj.hash);
|
||||||
|
}
|
||||||
|
HashedDir objects = (HashedDir) original.getEntry("objects");
|
||||||
|
List<String> toDeleteDirs = new ArrayList<>(16);
|
||||||
|
for (Map.Entry<String, HashedEntry> entry : objects.map().entrySet()) {
|
||||||
|
if (entry.getValue().getType() != HashedEntry.Type.DIR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HashedDir dir = (HashedDir) entry.getValue();
|
||||||
|
List<String> toDelete = new ArrayList<>(16);
|
||||||
|
for (String hash : dir.map().keySet()) {
|
||||||
|
if (!hashes.contains(hash)) {
|
||||||
|
toDelete.add(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String s : toDelete) {
|
||||||
|
dir.remove(s);
|
||||||
|
}
|
||||||
|
if (dir.map().isEmpty()) {
|
||||||
|
toDeleteDirs.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String s : toDeleteDirs) {
|
||||||
|
objects.remove(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AssetIndex {
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public boolean virtual;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public Map<String, AssetIndexObject> objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AssetIndexObject {
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public String hash;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public long size;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package pro.gravit.launcher.runtime.utils;
|
||||||
|
|
||||||
|
import com.sun.management.OperatingSystemMXBean;
|
||||||
|
|
||||||
|
public class SystemMemory {
|
||||||
|
private static final OperatingSystemMXBean systemMXBean = (OperatingSystemMXBean) java.lang.management.ManagementFactory.getOperatingSystemMXBean();
|
||||||
|
public static long getPhysicalMemorySize() {
|
||||||
|
return systemMXBean.getTotalMemorySize();
|
||||||
|
}
|
||||||
|
}
|
|
@ -235,7 +235,7 @@ protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, Do
|
||||||
return task.get();
|
return task.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
|
public static URI makeURI(URI baseUri, String filePath) throws URISyntaxException {
|
||||||
URI uri;
|
URI uri;
|
||||||
if(baseUri != null) {
|
if(baseUri != null) {
|
||||||
String scheme = baseUri.getScheme();
|
String scheme = baseUri.getScheme();
|
||||||
|
@ -248,6 +248,11 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy
|
||||||
} else {
|
} else {
|
||||||
uri = new URI(filePath);
|
uri = new URI(filePath);
|
||||||
}
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
|
||||||
|
var uri = makeURI(baseUri, filePath);
|
||||||
return HttpRequest.newBuilder()
|
return HttpRequest.newBuilder()
|
||||||
.GET()
|
.GET()
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
|
|
|
@ -42,6 +42,14 @@ public PlayerProfile(UUID uuid, String username, Map<String, Texture> assets, Ma
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public PlayerProfile(User user) {
|
||||||
|
this.uuid = user.getUUID();
|
||||||
|
this.username = user.getUsername();
|
||||||
|
this.assets = new HashMap<>((Map) user.getAssets());
|
||||||
|
this.properties = user.getProperties();
|
||||||
|
}
|
||||||
|
|
||||||
public static PlayerProfile newOfflineProfile(String username) {
|
public static PlayerProfile newOfflineProfile(String username) {
|
||||||
return new PlayerProfile(offlineUUID(username), username, new HashMap<>(), new HashMap<>());
|
return new PlayerProfile(offlineUUID(username), username, new HashMap<>(), new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,5 +165,15 @@ public CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName) {
|
||||||
return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url));
|
return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {}
|
public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {
|
||||||
|
@Override
|
||||||
|
public HashedDir getHashedDir() {
|
||||||
|
return hdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,10 @@ public interface ProfileFeatureAPI extends FeatureAPI {
|
||||||
CompletableFuture<List<ClientProfile>> getProfiles();
|
CompletableFuture<List<ClientProfile>> getProfiles();
|
||||||
CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName);
|
CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName);
|
||||||
|
|
||||||
interface UpdateInfo {}
|
interface UpdateInfo {
|
||||||
|
HashedDir getHashedDir();
|
||||||
|
String getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
interface ClientProfile {
|
interface ClientProfile {
|
||||||
String getName();
|
String getName();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package pro.gravit.launcher.core.backend;
|
package pro.gravit.launcher.core.backend;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||||
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
|
||||||
import pro.gravit.launcher.core.api.method.AuthMethod;
|
import pro.gravit.launcher.core.api.method.AuthMethod;
|
||||||
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
|
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
|
||||||
|
@ -8,8 +9,11 @@
|
||||||
import pro.gravit.launcher.core.api.model.UserPermissions;
|
import pro.gravit.launcher.core.api.model.UserPermissions;
|
||||||
import pro.gravit.launcher.core.backend.extensions.Extension;
|
import pro.gravit.launcher.core.backend.extensions.Extension;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public interface LauncherBackendAPI {
|
public interface LauncherBackendAPI {
|
||||||
void setCallback(MainCallback callback);
|
void setCallback(MainCallback callback);
|
||||||
|
@ -19,15 +23,22 @@ public interface LauncherBackendAPI {
|
||||||
CompletableFuture<SelfUser> authorize(String login, AuthMethodPassword password);
|
CompletableFuture<SelfUser> authorize(String login, AuthMethodPassword password);
|
||||||
CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> fetchProfiles();
|
CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> fetchProfiles();
|
||||||
ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile);
|
ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile);
|
||||||
|
void saveClientProfileSettings(ClientProfileSettings settings);
|
||||||
CompletableFuture<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback);
|
CompletableFuture<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback);
|
||||||
// Tools
|
// Tools
|
||||||
CompletableFuture<byte[]> fetchTexture(Texture texture);
|
CompletableFuture<byte[]> fetchTexture(Texture texture);
|
||||||
|
CompletableFuture<List<Java>> getAvailableJava();
|
||||||
|
// Settings
|
||||||
|
void registerUserSettings(String name, Class<? extends UserSettings> clazz);
|
||||||
|
UserSettings getUserSettings(String name, Function<String, UserSettings> ifNotExist);
|
||||||
// Status
|
// Status
|
||||||
UserPermissions getPermissions();
|
UserPermissions getPermissions();
|
||||||
boolean hasPermission(String permission);
|
boolean hasPermission(String permission);
|
||||||
String getUsername();
|
String getUsername();
|
||||||
|
SelfUser getSelfUser();
|
||||||
// Extensions
|
// Extensions
|
||||||
<T extends Extension> T getExtension(Class<T> clazz);
|
<T extends Extension> T getExtension(Class<T> clazz);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
record LauncherInitData(List<AuthMethod> methods) {}
|
record LauncherInitData(List<AuthMethod> methods) {}
|
||||||
|
|
||||||
|
@ -38,20 +49,36 @@ interface ReadyProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClientProfileSettings {
|
interface ClientProfileSettings {
|
||||||
long getReservedMemoryBytes();
|
long getReservedMemoryBytes(MemoryClass memoryClass);
|
||||||
long getMaxMemoryBytes();
|
long getMaxMemoryBytes(MemoryClass memoryClass);
|
||||||
void setReservedMemoryBytes(long value);
|
void setReservedMemoryBytes(MemoryClass memoryClass, long value);
|
||||||
List<Flag> getFlags();
|
Set<Flag> getFlags();
|
||||||
|
Set<Flag> getAvailableFlags();
|
||||||
boolean hasFlag(Flag flag);
|
boolean hasFlag(Flag flag);
|
||||||
void addFlag(Flag flag);
|
void addFlag(Flag flag);
|
||||||
void removeFlag(Flag flag);
|
void removeFlag(Flag flag);
|
||||||
List<ProfileFeatureAPI.OptionalMod> getEnabledOptionals();
|
Set<ProfileFeatureAPI.OptionalMod> getEnabledOptionals();
|
||||||
void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
|
void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
|
||||||
void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
|
void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
|
||||||
ClientProfileSettings clone();
|
Java getSelectedJava();
|
||||||
|
void setSelectedJava(Java java);
|
||||||
|
boolean isRecommended(Java java);
|
||||||
|
boolean isCompatible(Java java);
|
||||||
|
ClientProfileSettings copy();
|
||||||
|
|
||||||
enum Flag {
|
enum Flag {
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
AUTO_ENTER,
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
FULLSCREEN,
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
LINUX_WAYLAND_SUPPORT,
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
DEBUG_SKIP_FILE_MONITOR
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MemoryClass {
|
||||||
|
TOTAL
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChangedOptionalStatusCallback {
|
interface ChangedOptionalStatusCallback {
|
||||||
|
@ -78,6 +105,10 @@ public void onAuthorize(SelfUser selfUser) {
|
||||||
public void onNotify(String header, String description) {
|
public void onNotify(String header, String description) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onShutdown() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RunCallback {
|
class RunCallback {
|
||||||
|
@ -85,6 +116,10 @@ public void onStarted() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onCanTerminate(Runnable terminate) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void onFinished(int code) {
|
public void onFinished(int code) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -99,6 +134,14 @@ public void onErrorOutput(byte[] buf, int offset, int size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadCallback {
|
class DownloadCallback {
|
||||||
|
public static final String STAGE_ASSET_VERIFY = "assetVerify";
|
||||||
|
public static final String STAGE_HASHING = "hashing";
|
||||||
|
public static final String STAGE_DIFF = "diff";
|
||||||
|
public static final String STAGE_DOWNLOAD = "download";
|
||||||
|
public static final String STAGE_DELETE_EXTRA = "deleteExtra";
|
||||||
|
public static final String STAGE_DONE_PART = "done.part";
|
||||||
|
public static final String STAGE_DONE = "done";
|
||||||
|
|
||||||
public void onStage(String stage) {
|
public void onStage(String stage) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -115,4 +158,9 @@ public void onCurrentDownloaded(long current) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Java {
|
||||||
|
int getMajorVersion();
|
||||||
|
Path getPath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package pro.gravit.launcher.runtime.client;
|
package pro.gravit.launcher.core.backend;
|
||||||
|
|
||||||
import pro.gravit.utils.ProviderMap;
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package pro.gravit.launcher.core.backend.exceptions;
|
||||||
|
|
||||||
|
public class LauncherBackendException extends RuntimeException {
|
||||||
|
public LauncherBackendException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public LauncherBackendException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LauncherBackendException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package pro.gravit.utils.helper;
|
package pro.gravit.utils.helper;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -186,7 +188,7 @@ public JavaVersionAndBuild() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class JavaVersion {
|
public static class JavaVersion implements LauncherBackendAPI.Java {
|
||||||
public final Path jvmDir;
|
public final Path jvmDir;
|
||||||
public final int version;
|
public final int version;
|
||||||
public final int build;
|
public final int build;
|
||||||
|
@ -301,5 +303,15 @@ public static boolean isExistExtJavaLibrary(Path jvmDir, String name) {
|
||||||
Path jdkPathLin = jvmDir.resolve("jre").resolve("lib").resolve(name.concat(".jar"));
|
Path jdkPathLin = jvmDir.resolve("jre").resolve("lib").resolve(name.concat(".jar"));
|
||||||
return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath) || IOHelper.isFile(jdkPathLin) || IOHelper.isFile(jrePathLin);
|
return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath) || IOHelper.isFile(jdkPathLin) || IOHelper.isFile(jrePathLin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMajorVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path getPath() {
|
||||||
|
return jvmDir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
id 'org.openjfx.javafxplugin' version '0.1.0' apply false
|
id 'org.openjfx.javafxplugin' version '0.1.0' apply false
|
||||||
}
|
}
|
||||||
group = 'pro.gravit.launcher'
|
group = 'pro.gravit.launcher'
|
||||||
version = '5.6.2'
|
version = '5.7.0-SNAPSHOT'
|
||||||
|
|
||||||
apply from: 'props.gradle'
|
apply from: 'props.gradle'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue