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:
+148
-18
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user