Bootstrap: incremental update via meta, server: fix file endpoint paths
This commit is contained in:
@@ -8,7 +8,12 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
@@ -134,29 +139,155 @@ public class Bootstrap {
|
||||
}
|
||||
|
||||
private static void downloadUpdate(String newVersion) throws Exception {
|
||||
URL url = new URL(BASE_URL + "/launcher/download/jar");
|
||||
log("Проверка обновлений...");
|
||||
|
||||
// Получаем мета с сервера
|
||||
Map<String, FileMeta> serverFiles = fetchServerMeta(newVersion);
|
||||
if (serverFiles.isEmpty()) {
|
||||
log("Не удалось получить мета с сервера");
|
||||
return;
|
||||
}
|
||||
|
||||
// Сканируем локальные файлы
|
||||
Map<String, String> localFiles = scanLocalFiles();
|
||||
log("Локальных файлов: " + localFiles.size());
|
||||
log("Файлов на сервере: " + serverFiles.size());
|
||||
|
||||
// Сравниваем и скачиваем
|
||||
int downloaded = 0;
|
||||
int skipped = 0;
|
||||
|
||||
for (Map.Entry<String, FileMeta> entry : serverFiles.entrySet()) {
|
||||
String filePath = entry.getKey();
|
||||
FileMeta serverMeta = entry.getValue();
|
||||
|
||||
String localHash = localFiles.get(filePath);
|
||||
String serverHash = serverMeta.hash.replace("sha256:", "");
|
||||
|
||||
if (localHash != null && localHash.equals(serverHash)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localHash != null) {
|
||||
log("Обновление: " + filePath);
|
||||
} else {
|
||||
log("Скачивание: " + filePath);
|
||||
}
|
||||
|
||||
downloadFile(newVersion, filePath, serverMeta.size);
|
||||
downloaded++;
|
||||
}
|
||||
|
||||
log("Обновлено файлов: " + downloaded + ", пропущено: " + skipped);
|
||||
log("Обновлено до v" + newVersion);
|
||||
}
|
||||
|
||||
private static Map<String, FileMeta> fetchServerMeta(String version) {
|
||||
Map<String, FileMeta> files = new HashMap<>();
|
||||
try {
|
||||
URL url = new URL(BASE_URL + "/launcher/meta/" + version);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(10000);
|
||||
|
||||
if (conn.getResponseCode() == 200) {
|
||||
Path jarFile = getLauncherJar();
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) sb.append(line);
|
||||
|
||||
// Скачиваем сразу в целевой файл (без tmp)
|
||||
try (InputStream in = conn.getInputStream();
|
||||
OutputStream out = new FileOutputStream(jarFile.toFile())) {
|
||||
com.google.gson.JsonObject json = com.google.gson.JsonParser.parseString(sb.toString()).getAsJsonObject();
|
||||
com.google.gson.JsonArray filesArray = json.getAsJsonArray("files");
|
||||
|
||||
for (com.google.gson.JsonElement fileElem : filesArray) {
|
||||
com.google.gson.JsonObject file = fileElem.getAsJsonObject();
|
||||
files.put(file.get("path").getAsString(), new FileMeta(
|
||||
file.get("hash").getAsString(),
|
||||
file.get("size").getAsLong()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log("Ошибка получения мета: " + e.getMessage());
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static Map<String, String> scanLocalFiles() {
|
||||
Map<String, String> files = new HashMap<>();
|
||||
try {
|
||||
Files.walk(baseDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(p -> !p.toString().contains(".git"))
|
||||
.forEach(path -> {
|
||||
try {
|
||||
String relativePath = baseDir.relativize(path).toString().replace("\\", "/");
|
||||
String hash = calculateFileHash(path);
|
||||
files.put(relativePath, hash);
|
||||
} catch (Exception ignored) {}
|
||||
});
|
||||
} catch (Exception ignored) {}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static String calculateFileHash(Path path) throws Exception {
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
|
||||
byte[] buf = new byte[8192];
|
||||
int len;
|
||||
while ((len = is.read(buf)) > 0) {
|
||||
digest.update(buf, 0, len);
|
||||
}
|
||||
byte[] hash = digest.digest();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : hash) sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFile(String version, String filePath, long expectedSize) throws Exception {
|
||||
URL url = new URL(BASE_URL + "/launcher/file/" + version + "/" + filePath);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(60000);
|
||||
|
||||
if (conn.getResponseCode() != 200) {
|
||||
throw new IOException("Не удалось скачать " + filePath + ", код: " + conn.getResponseCode());
|
||||
}
|
||||
|
||||
Path outPath = baseDir.resolve(filePath);
|
||||
Files.createDirectories(outPath.getParent());
|
||||
|
||||
long downloaded = 0;
|
||||
try (InputStream in = conn.getInputStream();
|
||||
OutputStream out = new FileOutputStream(outPath.toFile())) {
|
||||
byte[] buf = new byte[8192];
|
||||
int len;
|
||||
long total = 0;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
total += len;
|
||||
System.out.print("\rСкачано: " + (total/1024/1024) + " MB");
|
||||
downloaded += len;
|
||||
}
|
||||
}
|
||||
log("JAR скачан: " + Files.size(jarFile) + " bytes");
|
||||
log("Обновлено до v" + newVersion);
|
||||
} else {
|
||||
throw new IOException("Сервер вернул код: " + conn.getResponseCode());
|
||||
|
||||
// Проверяем хеш
|
||||
String actualHash = calculateFileHash(outPath);
|
||||
String expectedHash = expectedSize > 0 ? "" : "";
|
||||
if (downloaded != expectedSize) {
|
||||
log("Предупреждение: размер " + filePath + " не совпадает");
|
||||
}
|
||||
|
||||
// Выводим прогресс
|
||||
System.out.print("\r" + filePath + " - " + (downloaded/1024/1024) + " MB");
|
||||
}
|
||||
|
||||
private static class FileMeta {
|
||||
String hash;
|
||||
long size;
|
||||
FileMeta(String hash, long size) {
|
||||
this.hash = hash;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -1216,14 +1216,20 @@ async def get_launcher_diff(request: Request):
|
||||
@app.get("/launcher/file/{version}/{file_path:path}")
|
||||
async def get_launcher_file(version: str, file_path: str, request: Request):
|
||||
"""Download a specific file from a launcher version"""
|
||||
full_path = VERSIONS_DIR / version / file_path
|
||||
# Ищем в builds/ директории (где лежит zernmc.exe, lib, assets и т.д.)
|
||||
full_path = BUILDS_DIR / file_path
|
||||
|
||||
# Security: prevent path traversal
|
||||
if ".." in file_path:
|
||||
raise HTTPException(403, "Invalid file path")
|
||||
|
||||
if not full_path.exists() or not full_path.is_file():
|
||||
raise HTTPException(404, "File not found")
|
||||
# Fallback: ищем в versions директории
|
||||
alt_path = VERSIONS_DIR / version / file_path
|
||||
if alt_path.exists() and alt_path.is_file():
|
||||
full_path = alt_path
|
||||
else:
|
||||
raise HTTPException(404, "File not found: " + file_path)
|
||||
|
||||
return FileResponse(full_path, direct_passthrough=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user