Server: Add legacy build support

- Add version parsing to distinguish new vs legacy format builds
- New format: ZernMC-win-*.zip (1.0.8+ with bundled JRE21/JavaFX)
- Legacy: ZernMCLauncher-*.zip (< 1.0.8 or with suffix)
- /launcher/download/latest now returns new format by default
- Add /launcher/download/legacy endpoint for old builds
- Add legacy info to /launcher/info and /launcher/version responses
- Update download_zip to accept both ZernMCLauncher- and ZernMC-win- patterns
This commit is contained in:
SashegDev
2026-05-07 16:44:10 +00:00
parent 0cef411125
commit f40cf7afed
+148 -18
View File
@@ -696,14 +696,66 @@ def get_current_launcher_version() -> str:
return "1.0.0" return "1.0.0"
def parse_version(version_str: str) -> dict:
"""Parse version string to determine if it's new or legacy format"""
import re
match = re.match(r'^(\d+)\.(\d+)\.(\d+)(.*)$', version_str)
if not match:
return {"major": 0, "minor": 0, "patch": 0, "suffix": version_str, "is_legacy": True}
major, minor, patch, suffix = match.groups()
suffix = suffix.strip("-")
is_legacy = bool(suffix)
return {
"major": int(major),
"minor": int(minor),
"patch": int(patch),
"suffix": suffix,
"is_legacy": is_legacy
}
def is_new_format(filename: str) -> bool:
"""Check if filename represents new format build"""
return filename.startswith("ZernMC-win-")
def get_available_zips() -> list: def get_available_zips() -> list:
"""Get list of available zip archives""" """Get list of available zip archives (new format only)"""
if not BUILDS_DIR.exists(): if not BUILDS_DIR.exists():
return [] return []
zips = [] zips = []
for zip_file in BUILDS_DIR.glob("ZernMCLauncher-*.zip"): for zip_file in BUILDS_DIR.glob("ZernMCLauncher-*.zip"):
if is_new_format(zip_file.name):
continue
version = zip_file.stem.replace("ZernMCLauncher-", "") version = zip_file.stem.replace("ZernMCLauncher-", "")
parsed = parse_version(version)
stat = zip_file.stat()
zips.append({
"version": version,
"filename": zip_file.name,
"size": stat.st_size,
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
"is_legacy": parsed["is_legacy"]
})
zips.sort(key=lambda x: x["version"], reverse=True)
return zips
def get_new_format_zips() -> list:
"""Get list of available zip archives (new format: ZernMC-win-*.zip)"""
if not BUILDS_DIR.exists():
return []
zips = []
for zip_file in BUILDS_DIR.glob("ZernMC-win-*.zip"):
version = zip_file.stem.replace("ZernMC-win-", "")
stat = zip_file.stat() stat = zip_file.stat()
zips.append({ zips.append({
"version": version, "version": version,
@@ -716,11 +768,42 @@ def get_available_zips() -> list:
return zips return zips
def get_legacy_zips() -> list:
"""Get list of available legacy zip archives (< 1.0.8 or with suffix)"""
if not BUILDS_DIR.exists():
return []
zips = []
for zip_file in BUILDS_DIR.glob("ZernMCLauncher-*.zip"):
version = zip_file.stem.replace("ZernMCLauncher-", "")
parsed = parse_version(version)
is_legacy = (
parsed["is_legacy"] or
(parsed["major"] < 1) or
(parsed["major"] == 1 and parsed["minor"] == 0 and parsed["patch"] < 8)
)
if is_legacy:
stat = zip_file.stat()
zips.append({
"version": version,
"filename": zip_file.name,
"size": stat.st_size,
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
"is_legacy": True
})
zips.sort(key=lambda x: x["version"], reverse=True)
return zips
@app.get("/launcher/version") @app.get("/launcher/version")
async def get_launcher_version(): async def get_launcher_version():
"""Return launcher version information""" """Return launcher version information"""
version = get_current_launcher_version() version = get_current_launcher_version()
zips = get_available_zips() new_zips = get_new_format_zips()
legacy_zips = get_legacy_zips()
response = { response = {
"version": version, "version": version,
@@ -737,11 +820,15 @@ async def get_launcher_version():
response["download_exe"] = "/launcher/download/exe" response["download_exe"] = "/launcher/download/exe"
response["exe_size"] = exe_path.stat().st_size response["exe_size"] = exe_path.stat().st_size
if zips: if new_zips:
response["download_zip"] = f"/launcher/download/zip/{zips[0]['filename']}" response["download_zip"] = f"/launcher/download/zip/{new_zips[0]['filename']}"
response["zip_version"] = zips[0]["version"] response["zip_version"] = new_zips[0]["version"]
response["zip_size"] = zips[0]["size"] response["zip_size"] = new_zips[0]["size"]
response["all_zips"] = zips response["all_zips"] = new_zips
if legacy_zips:
response["legacy_zips"] = legacy_zips
response["legacy_download_url"] = "/launcher/download/legacy"
return response return response
@@ -796,25 +883,56 @@ async def download_launcher_zip(filename: str):
@app.get("/launcher/download/latest") @app.get("/launcher/download/latest")
async def download_latest_launcher(): async def download_latest_launcher():
"""Download the latest launcher (prefer ZIP if available, fallback to JAR)""" """Download the latest launcher (new format: ZernMC-win-*.zip)"""
zips = get_available_zips() zips = get_new_format_zips()
if zips: if zips:
latest_zip = zips[0]["filename"] latest_zip = zips[0]["filename"]
return await download_launcher_zip(latest_zip) return await download_launcher_zip(latest_zip)
jar_path = BUILDS_DIR / "ZernMCLauncher.jar" raise HTTPException(404, "No new format launcher files available")
if jar_path.exists():
return await download_launcher_jar()
@app.get("/launcher/download/legacy")
async def download_legacy_launcher():
"""Download the latest legacy launcher (< 1.0.8 or with suffix)"""
zips = get_legacy_zips()
raise HTTPException(404, "No launcher files available") if zips:
latest_zip = zips[0]["filename"]
return await download_launcher_zip(latest_zip)
raise HTTPException(404, "No legacy launcher files available")
@app.get("/launcher/download/zip/{filename}")
async def download_launcher_zip(filename: str):
"""Download specific launcher ZIP archive"""
if ".." in filename:
raise HTTPException(400, "Invalid filename")
valid_patterns = ["ZernMCLauncher-", "ZernMC-win-"]
if not any(filename.startswith(p) for p in valid_patterns) or not filename.endswith(".zip"):
raise HTTPException(400, "Invalid filename")
file_path = BUILDS_DIR / filename
if not file_path.exists():
raise HTTPException(404, "ZIP file not found")
return FileResponse(
path=file_path,
filename=filename,
media_type="application/zip"
)
@app.get("/launcher/info") @app.get("/launcher/info")
async def get_launcher_full_info(): async def get_launcher_full_info():
"""Full launcher information with all available files""" """Full launcher information with all available files"""
version = get_current_launcher_version() version = get_current_launcher_version()
zips = get_available_zips() new_zips = get_new_format_zips()
legacy_zips = get_legacy_zips()
info = { info = {
"current_version": version, "current_version": version,
@@ -822,9 +940,21 @@ async def get_launcher_full_info():
"files": { "files": {
"jar": None, "jar": None,
"exe": None, "exe": None,
"zips": zips "zips": new_zips + legacy_zips
}, },
"recommended": "zip" if zips else ("exe" if (BUILDS_DIR / "ZernMCLauncher.exe").exists() else "jar") "recommended": "zip" if new_zips else ("exe" if (BUILDS_DIR / "ZernMCLauncher.exe").exists() else "jar"),
"new_format": {
"available": len(new_zips) > 0,
"latest": new_zips[0] if new_zips else None,
"download_url": "/launcher/download/latest"
},
"legacy": {
"available": len(legacy_zips) > 0,
"count": len(legacy_zips),
"latest": legacy_zips[0] if legacy_zips else None,
"download_url": "/launcher/download/legacy",
"warning": "Legacy builds are technically compatible but not recommended. Consider upgrading to new format."
}
} }
jar_path = BUILDS_DIR / "ZernMCLauncher.jar" jar_path = BUILDS_DIR / "ZernMCLauncher.jar"
@@ -841,8 +971,8 @@ async def get_launcher_full_info():
"download_url": "/launcher/download/exe" "download_url": "/launcher/download/exe"
} }
if zips: if new_zips:
info["files"]["latest_zip"] = zips[0] info["files"]["latest_zip"] = new_zips[0]
info["files"]["download_latest"] = "/launcher/download/latest" info["files"]["download_latest"] = "/launcher/download/latest"
return info return info