feat(api): add internal API foundation for UI

- Create api package with AuthService, InstanceService, LaunchService
- Add ApiResponse<T> model for consistent responses
- Create LauncherAPI central facade for all services
- Update Main.java to use new API for session checking
- All services compile successfully
This commit is contained in:
SashegDev
2026-05-05 04:12:39 +00:00
parent f2d3de82f7
commit d0b4e187c8
6 changed files with 503 additions and 4 deletions
@@ -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;
@@ -16,6 +17,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 IOException { public static void main(String[] args) throws IOException {
System.setProperty("org.jline.terminal.disableDeprecatedProviderWarning", "true"); System.setProperty("org.jline.terminal.disableDeprecatedProviderWarning", "true");
@@ -32,11 +34,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) {
@@ -45,7 +47,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,74 @@
package me.sashegdev.zernmc.launcher.api;
import me.sashegdev.zernmc.launcher.api.auth.AuthService;
import me.sashegdev.zernmc.launcher.api.instance.InstanceService;
import me.sashegdev.zernmc.launcher.api.launch.LaunchService;
import java.util.List;
/**
* Центральный фасад для внутреннего API лаунчера.
* Используется как единая точка входа для UI и других компонентов.
*/
public class LauncherAPI {
private final AuthService authService;
private final InstanceService instanceService;
private final LaunchService launchService;
public LauncherAPI() {
this.authService = new AuthService();
this.instanceService = new InstanceService();
this.launchService = new LaunchService();
}
public AuthService auth() {
return authService;
}
public InstanceService instances() {
return instanceService;
}
public LaunchService launch() {
return launchService;
}
// ====================== Удобные методы ======================
public boolean isLoggedIn() {
return authService.isLoggedIn();
}
public String getCurrentUsername() {
return authService.getCurrentUsername();
}
public ApiResponse<AuthService.SessionInfo> checkSession() {
return authService.checkSession();
}
public ApiResponse<AuthService.LoginResult> login(String username, String password) {
return authService.login(username, password);
}
public ApiResponse<Boolean> logout() {
return authService.logout();
}
public ApiResponse<List<InstanceService.InstanceInfo>> getAllInstances() {
return instanceService.getAllInstances();
}
public ApiResponse<LaunchService.InstanceInfo> getLaunchInfo(String instanceName) {
return launchService.getLaunchInfo(instanceName);
}
public ApiResponse<LaunchService.LaunchInfo> prepareLaunch(String instanceName) {
return launchService.prepareLaunch(instanceName);
}
public ApiResponse<LaunchService.ProcessInfo> launch(String instanceName) {
return launchService.launch(instanceName);
}
}
@@ -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; }
}
}