Read and update file_hashes.json on deploy; add --force-upload and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
46 lines
1.2 KiB
Python
46 lines
1.2 KiB
Python
"""
|
|
Host-side helpers for file_hashes.json (same format as led-driver/src/file_hashes.py).
|
|
"""
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
|
|
MANIFEST_VERSION = 1
|
|
MANIFEST_FILENAME = "file_hashes.json"
|
|
HASH_ALGO = "sha256"
|
|
|
|
|
|
def normalize_remote_path(remote_file: str) -> str:
|
|
"""Device-relative path with forward slashes (no leading slash)."""
|
|
return remote_file.replace("\\", "/").lstrip("/")
|
|
|
|
|
|
def sha256_hex_file(path: str) -> str:
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(65536), b""):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
|
|
def parse_manifest(data: bytes) -> dict:
|
|
"""Return path -> hash map from manifest bytes."""
|
|
try:
|
|
doc = json.loads(data.decode("utf-8"))
|
|
except (UnicodeDecodeError, json.JSONDecodeError, TypeError, ValueError):
|
|
return {}
|
|
if not isinstance(doc, dict):
|
|
return {}
|
|
files = doc.get("files")
|
|
return dict(files) if isinstance(files, dict) else {}
|
|
|
|
|
|
def build_manifest_bytes(files: dict) -> bytes:
|
|
doc = {
|
|
"version": MANIFEST_VERSION,
|
|
"algorithm": HASH_ALGO,
|
|
"files": files,
|
|
}
|
|
return json.dumps(doc, separators=(",", ":")).encode("utf-8")
|