Files
rosetta-sdu/src/main/java/im/rosetta/api/UpdatesResource.java
2026-02-12 12:52:29 +02:00

284 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package im.rosetta.api;
import im.rosetta.api.dto.UpdateItem;
import im.rosetta.api.dto.UpdateListResponse;
import im.rosetta.api.dto.UpdateResponse;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Path("/updates")
@Produces(MediaType.APPLICATION_JSON)
public class UpdatesResource {
// Каталоги с обновлениями относительно корня запуска приложения.
private static final java.nio.file.Path KERNEL_DIR = Paths.get("kernel");
private static final java.nio.file.Path PACKS_DIR = Paths.get("packs");
// Регулярка для извлечения версии ядра из имени файла.
private static final Pattern KERNEL_VERSION_PATTERN = Pattern.compile(".*-(\\d+(?:\\.\\d+)*)\\.[^.]+$");
@GET
@Path("/get")
public UpdateResponse getUpdate(@QueryParam("platform") String platform,
@QueryParam("arch") String arch,
@QueryParam("app") String appVersion,
@QueryParam("kernel") String kernelVersion) {
// Нормализуем входные параметры.
String normalizedPlatform = normalize(platform);
String normalizedArch = normalize(arch);
String normalizedApp = normalize(appVersion);
String normalizedKernel = normalize(kernelVersion);
// Приводим платформу и архитектуру к нижнему регистру для сопоставления с
// именами файлов.
if (normalizedPlatform != null) {
normalizedPlatform = normalizedPlatform.toLowerCase(Locale.ROOT);
}
if (normalizedArch != null) {
normalizedArch = normalizedArch.toLowerCase(Locale.ROOT);
}
// Если параметры не заданы — возвращаем пустой ответ.
if (normalizedPlatform == null || normalizedArch == null
|| normalizedApp == null || normalizedKernel == null) {
return new UpdateResponse(null, normalizedPlatform, normalizedArch, false, null, null);
}
// Ищем лучший пакет обновления и последнюю версию приложения на сервере.
PackSelection packSelection = findPackSelection(normalizedPlatform, normalizedArch, normalizedApp);
String serverAppVersion = packSelection.serverVersion() != null
? packSelection.serverVersion()
: normalizedApp;
String servicePackUrl = packSelection.updateFileName() != null
? "/sp/" + packSelection.updateFileName()
: null;
boolean kernelUpdateRequired = false;
String kernelUrl = null;
// Проверяем требование к ядру, только если пакет обновления найден.
if (packSelection.updateFileName() != null
&& compareVersions(normalizedKernel, packSelection.minKernelRequired()) < 0) {
kernelUpdateRequired = true;
// Ищем актуальную версию ядра на сервере.
Optional<KernelFileInfo> latestKernel = findLatestKernel(normalizedPlatform, normalizedArch);
if (latestKernel.isPresent()) {
KernelFileInfo info = latestKernel.get();
kernelUrl = "/kernel/" + normalizedPlatform + "/" + normalizedArch + "/" + info.fileName();
}
}
return new UpdateResponse(serverAppVersion,
normalizedPlatform,
normalizedArch,
kernelUpdateRequired,
servicePackUrl,
kernelUrl);
}
@GET
@Path("/all")
public UpdateListResponse getAll() {
List<UpdateItem> items = new ArrayList<>();
if (!Files.isDirectory(KERNEL_DIR)) {
return new UpdateListResponse(items);
}
try (var stream = Files.list(KERNEL_DIR)) {
for (java.nio.file.Path platformDir : (Iterable<java.nio.file.Path>) stream::iterator) {
if (!Files.isDirectory(platformDir)) {
continue;
}
String platform = platformDir.getFileName().toString();
try (var archStream = Files.list(platformDir)) {
for (java.nio.file.Path archDir : (Iterable<java.nio.file.Path>) archStream::iterator) {
if (!Files.isDirectory(archDir)) {
continue;
}
String arch = archDir.getFileName().toString();
Optional<KernelFileInfo> latestKernel = findLatestKernel(platform, arch);
if (latestKernel.isPresent()) {
KernelFileInfo info = latestKernel.get();
items.add(new UpdateItem(platform, arch, info.version(),
"/kernel/" + platform + "/" + arch + "/" + info.fileName()));
}
}
} catch (IOException ignored) {
}
}
} catch (IOException ignored) {
}
return new UpdateListResponse(items);
}
// Находим самый новый пакет обновления и последнюю версию приложения на
// сервере.
private PackSelection findPackSelection(String platform, String arch, String clientAppVersion) {
if (!Files.isDirectory(PACKS_DIR)) {
return new PackSelection(null, null, null);
}
String bestUpdateFile = null;
String bestUpdateVersion = null;
String minKernelRequired = null;
String maxServerVersion = null;
try (var stream = Files.list(PACKS_DIR)) {
for (java.nio.file.Path path : (Iterable<java.nio.file.Path>) stream::iterator) {
if (!Files.isRegularFile(path)) {
continue;
}
PackFileInfo info = parsePackFileName(path.getFileName().toString());
if (info == null) {
continue;
}
if (!platform.equals(info.platform()) || !arch.equals(info.arch())) {
continue;
}
// Обновляем информацию о максимальной версии на сервере.
if (maxServerVersion == null || compareVersions(info.appVersion(), maxServerVersion) > 0) {
maxServerVersion = info.appVersion();
}
// Проверяем, что версия клиента меньше версии пакета.
if (compareVersions(clientAppVersion, info.appVersion()) >= 0) {
continue;
}
// Выбираем самый новый доступный пакет.
if (bestUpdateVersion == null || compareVersions(info.appVersion(), bestUpdateVersion) > 0) {
bestUpdateVersion = info.appVersion();
bestUpdateFile = info.fileName();
minKernelRequired = info.minKernelRequired();
}
}
} catch (IOException ignored) {
// Если каталог недоступен, возвращаем пустой результат.
return new PackSelection(null, null, null);
}
return new PackSelection(bestUpdateFile, minKernelRequired, maxServerVersion);
}
// Ищем последний доступный файл ядра.
private Optional<KernelFileInfo> findLatestKernel(String platform, String arch) {
java.nio.file.Path targetDir = KERNEL_DIR.resolve(platform).resolve(arch);
if (!Files.isDirectory(targetDir)) {
return Optional.empty();
}
try (var stream = Files.list(targetDir)) {
return stream.filter(Files::isRegularFile)
.map(java.nio.file.Path::getFileName)
.map(java.nio.file.Path::toString)
.map(this::parseKernelFileName)
.filter(info -> info != null)
.max(Comparator.comparing(KernelFileInfo::version, this::compareVersions));
} catch (IOException ignored) {
return Optional.empty();
}
}
// Парсим имя файла ядра и извлекаем версию.
private KernelFileInfo parseKernelFileName(String fileName) {
Matcher matcher = KERNEL_VERSION_PATTERN.matcher(fileName);
if (!matcher.matches()) {
return null;
}
return new KernelFileInfo(fileName, matcher.group(1));
}
// Парсим имя файла пакета обновления приложения.
private PackFileInfo parsePackFileName(String fileName) {
String lower = fileName.toLowerCase(Locale.ROOT);
if (!lower.startsWith("sp-") || !lower.endsWith(".zip")) {
return null;
}
String baseName = fileName.substring(0, fileName.length() - 4);
String[] parts = baseName.split("-");
if (parts.length != 5) {
return null;
}
return new PackFileInfo(parts[1], parts[2], parts[3], parts[4], fileName);
}
// Сравнение версий вида 1.4.5.
private int compareVersions(String left, String right) {
if (left == null && right == null) {
return 0;
}
if (left == null) {
return -1;
}
if (right == null) {
return 1;
}
String[] leftParts = left.split("\\.");
String[] rightParts = right.split("\\.");
int max = Math.max(leftParts.length, rightParts.length);
for (int i = 0; i < max; i++) {
int l = i < leftParts.length ? parseVersionPart(leftParts[i]) : 0;
int r = i < rightParts.length ? parseVersionPart(rightParts[i]) : 0;
if (l != r) {
return Integer.compare(l, r);
}
}
return 0;
}
// Безопасный парсинг части версии.
private int parseVersionPart(String part) {
try {
return Integer.parseInt(part);
} catch (NumberFormatException ignored) {
return 0;
}
}
// Нормализуем строку (убираем пробелы и null).
private String normalize(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
// Вспомогательная структура для файла ядра.
private record KernelFileInfo(String fileName, String version) {
}
// Вспомогательная структура для файла пакета.
private record PackFileInfo(String platform, String arch, String appVersion,
String minKernelRequired, String fileName) {
}
// Результат выбора пакета обновления.
private record PackSelection(String updateFileName, String minKernelRequired, String serverVersion) {
}
}