feat: 服务端集成自动打包流程
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_json_file(path: str, default):
|
||||
try:
|
||||
return json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return default() if callable(default) else default
|
||||
|
||||
|
||||
def save_json_file(path: str, payload):
|
||||
Path(path).write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
class IncrementalAnalysisCache:
|
||||
def __init__(self, cache_path: str, version: int):
|
||||
self.cache_path = cache_path
|
||||
self.version = version
|
||||
self._state = None
|
||||
self._dirty = False
|
||||
self._memo = {}
|
||||
|
||||
def _default_state(self) -> dict:
|
||||
return {
|
||||
"version": self.version,
|
||||
"files": {},
|
||||
}
|
||||
|
||||
def load_state(self) -> dict:
|
||||
if self._state is not None:
|
||||
return self._state
|
||||
|
||||
payload = load_json_file(self.cache_path, self._default_state)
|
||||
if payload.get("version") != self.version or not isinstance(payload.get("files"), dict):
|
||||
payload = self._default_state()
|
||||
|
||||
self._state = payload
|
||||
return self._state
|
||||
|
||||
def save_if_dirty(self):
|
||||
if not self._dirty:
|
||||
return
|
||||
|
||||
try:
|
||||
save_json_file(self.cache_path, self.load_state())
|
||||
self._dirty = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def build_cache_key(analysis_kind: str, path: str) -> str:
|
||||
return f"{analysis_kind}:{os.path.realpath(path)}"
|
||||
|
||||
@staticmethod
|
||||
def _read_text_with_hash(path: str):
|
||||
try:
|
||||
text = Path(path).read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
return None, None
|
||||
return text, hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||
|
||||
def get_or_compute(self, path: str, analysis_kind: str, analyzer):
|
||||
cache_key = self.build_cache_key(analysis_kind, path)
|
||||
memo_key = (analysis_kind, os.path.realpath(path))
|
||||
if memo_key in self._memo:
|
||||
return self._memo[memo_key]
|
||||
|
||||
cache_state = self.load_state()
|
||||
files_cache = cache_state.setdefault("files", {})
|
||||
cached_entry = files_cache.get(cache_key)
|
||||
|
||||
try:
|
||||
stat = os.stat(path)
|
||||
stat_signature = {
|
||||
"mtime_ns": stat.st_mtime_ns,
|
||||
"size": stat.st_size,
|
||||
}
|
||||
except Exception:
|
||||
stat_signature = {
|
||||
"mtime_ns": None,
|
||||
"size": None,
|
||||
}
|
||||
|
||||
if (
|
||||
isinstance(cached_entry, dict)
|
||||
and cached_entry.get("mtime_ns") == stat_signature["mtime_ns"]
|
||||
and cached_entry.get("size") == stat_signature["size"]
|
||||
and isinstance(cached_entry.get("analysis"), dict)
|
||||
):
|
||||
analysis = cached_entry["analysis"]
|
||||
self._memo[memo_key] = analysis
|
||||
return analysis
|
||||
|
||||
text, text_hash = self._read_text_with_hash(path)
|
||||
if text is None:
|
||||
analysis = analyzer("", path)
|
||||
self._memo[memo_key] = analysis
|
||||
return analysis
|
||||
|
||||
if (
|
||||
isinstance(cached_entry, dict)
|
||||
and cached_entry.get("sha256") == text_hash
|
||||
and isinstance(cached_entry.get("analysis"), dict)
|
||||
):
|
||||
cached_entry.update(stat_signature)
|
||||
self._dirty = True
|
||||
analysis = cached_entry["analysis"]
|
||||
self._memo[memo_key] = analysis
|
||||
return analysis
|
||||
|
||||
analysis = analyzer(text, path)
|
||||
files_cache[cache_key] = {
|
||||
"path": os.path.realpath(path),
|
||||
"analysis_kind": analysis_kind,
|
||||
"mtime_ns": stat_signature["mtime_ns"],
|
||||
"size": stat_signature["size"],
|
||||
"sha256": text_hash,
|
||||
"analysis": analysis,
|
||||
}
|
||||
self._dirty = True
|
||||
self._memo[memo_key] = analysis
|
||||
return analysis
|
||||
Reference in New Issue
Block a user