Merge pull request #5 from SashegDev/ui
feat(ui): add Web UI with JavaFX, install service, and new tests
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package me.sashegdev.zernmc.launcher;
|
package me.sashegdev.zernmc.launcher;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.api.LauncherAPI;
|
||||||
import me.sashegdev.zernmc.launcher.auth.AuthManager;
|
import me.sashegdev.zernmc.launcher.auth.AuthManager;
|
||||||
import me.sashegdev.zernmc.launcher.menu.*;
|
import me.sashegdev.zernmc.launcher.menu.*;
|
||||||
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
|
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
|
||||||
@@ -21,6 +22,7 @@ import java.util.List;
|
|||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
private static final String CURRENT_VERSION = Version.getCurrentVersion();
|
private static final String CURRENT_VERSION = Version.getCurrentVersion();
|
||||||
|
private static final LauncherAPI api = new LauncherAPI();
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
boolean cliMode = Arrays.asList(args).contains("--cli") || Arrays.asList(args).contains("-c");
|
boolean cliMode = Arrays.asList(args).contains("--cli") || Arrays.asList(args).contains("-c");
|
||||||
@@ -95,11 +97,11 @@ public class Main {
|
|||||||
|
|
||||||
checkAndAutoUpdateLauncher();
|
checkAndAutoUpdateLauncher();
|
||||||
|
|
||||||
// === АВТОРИЗАЦИЯ ===
|
// === АВТОРИЗАЦИЯ (используем новый API) ===
|
||||||
System.out.println(ZAnsi.cyan("Проверка авторизации..."));
|
System.out.println(ZAnsi.cyan("Проверка авторизации..."));
|
||||||
boolean sessionRestored = AuthManager.loadSavedSession();
|
var sessionResponse = api.checkSession();
|
||||||
|
|
||||||
if (!sessionRestored) {
|
if (!sessionResponse.isSuccess()) {
|
||||||
LoginMenu loginMenu = new LoginMenu();
|
LoginMenu loginMenu = new LoginMenu();
|
||||||
boolean loggedIn = loginMenu.show();
|
boolean loggedIn = loginMenu.show();
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
@@ -108,7 +110,8 @@ public class Main {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + AuthManager.getUsername() + "!"));
|
var sessionInfo = sessionResponse.getData();
|
||||||
|
System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + sessionInfo.getUsername() + "!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ГЛАВНЫЙ ЦИКЛ ===
|
// === ГЛАВНЫЙ ЦИКЛ ===
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.api;
|
||||||
|
|
||||||
|
public class ApiResponse<T> {
|
||||||
|
private boolean success;
|
||||||
|
private T data;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
public ApiResponse(boolean success, T data, String error) {
|
||||||
|
this.success = success;
|
||||||
|
this.data = data;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(T data) {
|
||||||
|
return new ApiResponse<>(true, data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> error(String error) {
|
||||||
|
return new ApiResponse<>(false, null, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.api.auth;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.api.ApiResponse;
|
||||||
|
import me.sashegdev.zernmc.launcher.auth.AuthManager;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class AuthService {
|
||||||
|
|
||||||
|
public ApiResponse<LoginResult> login(String username, String password) {
|
||||||
|
try {
|
||||||
|
AuthManager.AuthResult result = AuthManager.login(username, password);
|
||||||
|
if (result.success) {
|
||||||
|
LoginResult loginResult = new LoginResult(AuthManager.getUsername(), AuthManager.getAccessToken());
|
||||||
|
return ApiResponse.success(loginResult);
|
||||||
|
}
|
||||||
|
return ApiResponse.error(result.error != null ? result.error : "Неверный логин или пароль");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка авторизации: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Boolean> logout() {
|
||||||
|
try {
|
||||||
|
AuthManager.logout();
|
||||||
|
return ApiResponse.success(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка при выходе: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<SessionInfo> checkSession() {
|
||||||
|
try {
|
||||||
|
boolean restored = AuthManager.loadSavedSession();
|
||||||
|
if (restored) {
|
||||||
|
SessionInfo info = new SessionInfo(
|
||||||
|
AuthManager.getUsername(),
|
||||||
|
AuthManager.getAccessToken(),
|
||||||
|
AuthManager.hasActivePass()
|
||||||
|
);
|
||||||
|
return ApiResponse.success(info);
|
||||||
|
}
|
||||||
|
return ApiResponse.error("Сессия не найдена");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка проверки сессии: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Boolean> activatePass(String passCode) {
|
||||||
|
try {
|
||||||
|
String response = post("/auth/pass/activate",
|
||||||
|
"{\"code\":\"" + passCode + "\"}");
|
||||||
|
return ApiResponse.success(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка активации проходки: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String post(String endpoint, String jsonBody) throws Exception {
|
||||||
|
String fullUrl = ZHttpClient.getBaseUrl() + endpoint;
|
||||||
|
java.net.URL url = new java.net.URL(fullUrl);
|
||||||
|
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
||||||
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
conn.setRequestProperty("User-Agent", "ZernMC-Launcher/1.0");
|
||||||
|
|
||||||
|
if (AuthManager.getAccessToken() != null && !AuthManager.getAccessToken().equals("0")) {
|
||||||
|
conn.setRequestProperty("Authorization", "Bearer " + AuthManager.getAccessToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
|
||||||
|
try (var os = conn.getOutputStream()) {
|
||||||
|
byte[] input = jsonBody.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
os.write(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
int statusCode = conn.getResponseCode();
|
||||||
|
var is = (statusCode >= 200 && statusCode < 300) ? conn.getInputStream() : conn.getErrorStream();
|
||||||
|
|
||||||
|
String responseBody;
|
||||||
|
try (var scanner = new java.util.Scanner(is, java.nio.charset.StandardCharsets.UTF_8.name())) {
|
||||||
|
responseBody = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
if (statusCode != 200) {
|
||||||
|
throw new IOException("HTTP " + statusCode + ": " + responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoggedIn() {
|
||||||
|
return AuthManager.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentUsername() {
|
||||||
|
return AuthManager.getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoginResult {
|
||||||
|
private String username;
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
public LoginResult(String username, String token) {
|
||||||
|
this.username = username;
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public String getToken() { return token; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SessionInfo {
|
||||||
|
private String username;
|
||||||
|
private String token;
|
||||||
|
private boolean passActive;
|
||||||
|
|
||||||
|
public SessionInfo(String username, String token, boolean passActive) {
|
||||||
|
this.username = username;
|
||||||
|
this.token = token;
|
||||||
|
this.passActive = passActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public String getToken() { return token; }
|
||||||
|
public boolean isPassActive() { return passActive; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.api.instance;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.api.ApiResponse;
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.Instance;
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class InstanceService {
|
||||||
|
|
||||||
|
public ApiResponse<List<InstanceInfo>> getAllInstances() {
|
||||||
|
try {
|
||||||
|
List<Instance> instances = InstanceManager.getAllInstances();
|
||||||
|
List<InstanceInfo> infoList = instances.stream()
|
||||||
|
.map(this::toInstanceInfo)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return ApiResponse.success(infoList);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return ApiResponse.error("Ошибка получения списка сборок: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<InstanceInfo> getInstance(String name) {
|
||||||
|
try {
|
||||||
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
|
if (instance == null) {
|
||||||
|
return ApiResponse.error("Сборка не найдена: " + name);
|
||||||
|
}
|
||||||
|
return ApiResponse.success(toInstanceInfo(instance));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка получения сборки: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<InstanceInfo> createInstance(String name) {
|
||||||
|
try {
|
||||||
|
boolean created = InstanceManager.createInstanceFolder(name);
|
||||||
|
if (!created) {
|
||||||
|
return ApiResponse.error("Сборка с таким именем уже существует: " + name);
|
||||||
|
}
|
||||||
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
|
return ApiResponse.success(toInstanceInfo(instance));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return ApiResponse.error("Ошибка создания сборки: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Boolean> deleteInstance(String name) {
|
||||||
|
try {
|
||||||
|
boolean deleted = InstanceManager.deleteInstance(name);
|
||||||
|
if (!deleted) {
|
||||||
|
return ApiResponse.error("Не удалось удалить сборку: " + name);
|
||||||
|
}
|
||||||
|
return ApiResponse.success(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка удаления сборки: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Boolean> isInstanceExists(String name) {
|
||||||
|
try {
|
||||||
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
|
return ApiResponse.success(instance != null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка проверки сборки: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InstanceInfo toInstanceInfo(Instance instance) {
|
||||||
|
return new InstanceInfo(
|
||||||
|
instance.getName(),
|
||||||
|
instance.getPath().toString(),
|
||||||
|
instance.getMinecraftVersion(),
|
||||||
|
instance.getLoaderType()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InstanceInfo {
|
||||||
|
private String name;
|
||||||
|
private String path;
|
||||||
|
private String version;
|
||||||
|
private String loaderType;
|
||||||
|
|
||||||
|
public InstanceInfo(String name, String path, String version, String loaderType) {
|
||||||
|
this.name = name;
|
||||||
|
this.path = path;
|
||||||
|
this.version = version;
|
||||||
|
this.loaderType = loaderType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
public String getPath() { return path; }
|
||||||
|
public String getVersion() { return version; }
|
||||||
|
public String getLoaderType() { return loaderType; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.api.launch;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.api.ApiResponse;
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.Instance;
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LaunchService {
|
||||||
|
|
||||||
|
public ApiResponse<LaunchInfo> prepareLaunch(String instanceName) {
|
||||||
|
try {
|
||||||
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
|
if (instance == null) {
|
||||||
|
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
||||||
|
LaunchOptions options = new LaunchOptions();
|
||||||
|
|
||||||
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
|
LaunchInfo info = new LaunchInfo(
|
||||||
|
instanceName,
|
||||||
|
command,
|
||||||
|
instance.getPath().toString()
|
||||||
|
);
|
||||||
|
return ApiResponse.success(info);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка подготовки запуска: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<ProcessInfo> launch(String instanceName) {
|
||||||
|
try {
|
||||||
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
|
if (instance == null) {
|
||||||
|
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
||||||
|
LaunchOptions options = new LaunchOptions();
|
||||||
|
|
||||||
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||||
|
processBuilder.directory(instance.getPath().toFile());
|
||||||
|
processBuilder.inheritIO();
|
||||||
|
|
||||||
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
|
ProcessInfo info = new ProcessInfo(
|
||||||
|
instanceName,
|
||||||
|
process.pid(),
|
||||||
|
"RUNNING"
|
||||||
|
);
|
||||||
|
return ApiResponse.success(info);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка запуска: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Boolean> isReady(String instanceName) {
|
||||||
|
try {
|
||||||
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
|
if (instance == null) {
|
||||||
|
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path versionJson = instance.getPath().resolve("version.json");
|
||||||
|
boolean hasVersionJson = versionJson.toFile().exists();
|
||||||
|
|
||||||
|
return ApiResponse.success(hasVersionJson);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка проверки готовности: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<InstanceInfo> getLaunchInfo(String instanceName) {
|
||||||
|
try {
|
||||||
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
|
if (instance == null) {
|
||||||
|
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceInfo info = new InstanceInfo(
|
||||||
|
instance.getName(),
|
||||||
|
instance.getMinecraftVersion(),
|
||||||
|
instance.getLoaderType(),
|
||||||
|
instance.getLoaderVersion(),
|
||||||
|
instance.getAssetIndex()
|
||||||
|
);
|
||||||
|
return ApiResponse.success(info);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.error("Ошибка получения информации: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LaunchInfo {
|
||||||
|
private String instanceName;
|
||||||
|
private List<String> command;
|
||||||
|
private String workingDirectory;
|
||||||
|
|
||||||
|
public LaunchInfo(String instanceName, List<String> command, String workingDirectory) {
|
||||||
|
this.instanceName = instanceName;
|
||||||
|
this.command = command;
|
||||||
|
this.workingDirectory = workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInstanceName() { return instanceName; }
|
||||||
|
public List<String> getCommand() { return command; }
|
||||||
|
public String getWorkingDirectory() { return workingDirectory; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ProcessInfo {
|
||||||
|
private String instanceName;
|
||||||
|
private long pid;
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
public ProcessInfo(String instanceName, long pid, String status) {
|
||||||
|
this.instanceName = instanceName;
|
||||||
|
this.pid = pid;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInstanceName() { return instanceName; }
|
||||||
|
public long getPid() { return pid; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InstanceInfo {
|
||||||
|
private String name;
|
||||||
|
private String minecraftVersion;
|
||||||
|
private String loaderType;
|
||||||
|
private String loaderVersion;
|
||||||
|
private String assetIndex;
|
||||||
|
|
||||||
|
public InstanceInfo(String name, String minecraftVersion, String loaderType,
|
||||||
|
String loaderVersion, String assetIndex) {
|
||||||
|
this.name = name;
|
||||||
|
this.minecraftVersion = minecraftVersion;
|
||||||
|
this.loaderType = loaderType;
|
||||||
|
this.loaderVersion = loaderVersion;
|
||||||
|
this.assetIndex = assetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
public String getMinecraftVersion() { return minecraftVersion; }
|
||||||
|
public String getLoaderType() { return loaderType; }
|
||||||
|
public String getLoaderVersion() { return loaderVersion; }
|
||||||
|
public String getAssetIndex() { return assetIndex; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user