mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-28 21:19:58 +08:00
107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
"""从 service 仓库下载原版小赖字体,校验后原子写入本地缓存。"""
|
|
|
|
import hashlib
|
|
from importlib import resources
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
from zipfile import ZipFile
|
|
|
|
from .bundle import _LOCK, _copy_remote, validate_options
|
|
|
|
|
|
FONT_NAME = "Xiaolai-Regular.ttf"
|
|
FILES = {
|
|
FONT_NAME: (
|
|
22220806,
|
|
"e2f68daf0e72777a8cf58bc83de1b98634b251e537ddbfca24b0ae50d1802da2",
|
|
),
|
|
"OFL-Xiaolai.txt": (
|
|
4432,
|
|
"0df7e09be4c2c850a48bd8beb9cd64b343aad49cd5d3f6cfb2ad2e3d28a56ca4",
|
|
),
|
|
}
|
|
|
|
|
|
def expected_manifest():
|
|
return json.loads(resources.files(__package__).joinpath(
|
|
"data", "font-manifest.json",
|
|
).read_bytes())
|
|
|
|
|
|
def _valid(path, size, digest):
|
|
if not path.is_file() or path.stat().st_size != size:
|
|
return False
|
|
checksum = hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for chunk in iter(lambda: stream.read(65536), b""):
|
|
checksum.update(chunk)
|
|
return checksum.hexdigest() == digest
|
|
|
|
|
|
def _atomic_write(path, content):
|
|
temporary = None
|
|
try:
|
|
with tempfile.NamedTemporaryFile(
|
|
dir=path.parent, suffix=".download", delete=False,
|
|
) as stream:
|
|
temporary = Path(stream.name)
|
|
stream.write(content)
|
|
temporary.replace(path)
|
|
finally:
|
|
if temporary is not None:
|
|
temporary.unlink(missing_ok=True)
|
|
|
|
|
|
def ensure_font(data_dir, source="Github", service_url=""):
|
|
"""校验全部缓存文件;无效时下载,始终同时保留完整 OFL 许可。"""
|
|
validate_options(source, service_url)
|
|
manifest = expected_manifest()
|
|
root = Path(data_dir) / "fonts" / manifest["sha256"]
|
|
with _LOCK:
|
|
if all(_valid(root / name, *checks)
|
|
for name, checks in FILES.items()):
|
|
return root / FONT_NAME
|
|
root.mkdir(parents=True, exist_ok=True)
|
|
archive_path = root / "assets.zip"
|
|
if not _valid(archive_path, manifest["size_bytes"],
|
|
manifest["sha256"]):
|
|
url = (
|
|
service_url.rstrip("/") + "/v1/fonts/"
|
|
+ manifest["sha256"] + "/assets.zip"
|
|
if source == "farm_service"
|
|
else manifest["sources"][source.lower()]
|
|
)
|
|
temporary = None
|
|
try:
|
|
with tempfile.NamedTemporaryFile(
|
|
dir=root, suffix=".download", delete=False,
|
|
) as stream:
|
|
temporary = Path(stream.name)
|
|
_copy_remote(url, stream, manifest["size_bytes"])
|
|
if not _valid(temporary, manifest["size_bytes"],
|
|
manifest["sha256"]):
|
|
raise ValueError("Font archive size or SHA256 mismatch")
|
|
temporary.replace(archive_path)
|
|
finally:
|
|
if temporary is not None:
|
|
temporary.unlink(missing_ok=True)
|
|
with ZipFile(archive_path) as archive:
|
|
entries = archive.infolist()
|
|
if (len(entries) != len(FILES)
|
|
or {item.filename for item in entries} != set(FILES)):
|
|
raise ValueError("Unexpected font archive contents")
|
|
content = {}
|
|
for item in entries:
|
|
size, digest = FILES[item.filename]
|
|
if item.file_size != size or item.flag_bits & 1:
|
|
raise ValueError("Invalid font archive member")
|
|
value = archive.read(item)
|
|
if hashlib.sha256(value).hexdigest() != digest:
|
|
raise ValueError("Font member SHA256 mismatch")
|
|
content[item.filename] = value
|
|
# 先写许可,再使字体可用;不使用 extractall 或远端提供的路径。
|
|
for name in ("OFL-Xiaolai.txt", FONT_NAME):
|
|
_atomic_write(root / name, content[name])
|
|
return root / FONT_NAME
|