feat: add favorite task templates and optimize evidence exports
This commit is contained in:
1 parent
e1ea099592
commit
a4a5430922
46 files changed
+1714
-188
No files matched your search
@@ -0,0 +1,66 @@
|
||||
"""Per-object export coordination and content-aware artifact reuse."""
|
||||
|
||||
from contextlib import contextmanager, ExitStack
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import threading
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
_guard = threading.Lock()
|
||||
_locks: WeakValueDictionary = WeakValueDictionary()
|
||||
TERMINAL = {"completed", "success", "failed", "cancelled", "interrupted", "skipped"}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def export_lock(*keys: str):
|
||||
# Retain strong references until every lock is released. Unrelated exports
|
||||
# run concurrently; nested session exports can reacquire their own RLock.
|
||||
with _guard:
|
||||
locks = []
|
||||
for key in sorted(set(keys)):
|
||||
lock = _locks.get(key)
|
||||
if lock is None:
|
||||
lock = threading.RLock()
|
||||
_locks[key] = lock
|
||||
locks.append(lock)
|
||||
with ExitStack() as stack:
|
||||
for lock in locks:
|
||||
stack.enter_context(lock)
|
||||
yield
|
||||
|
||||
|
||||
def fingerprint(report: dict, files=(), extra=None) -> str:
|
||||
metadata = []
|
||||
for path, name in files:
|
||||
stat = path.stat()
|
||||
metadata.append((name, str(path), stat.st_size, stat.st_mtime_ns))
|
||||
payload = json.dumps([2, report, metadata, extra], sort_keys=True, default=str)
|
||||
return hashlib.sha256(payload.encode()).hexdigest()
|
||||
|
||||
|
||||
def reusable(path: Path, signature: str, status: str) -> bool:
|
||||
if status not in TERMINAL or not path.is_file():
|
||||
return False
|
||||
try:
|
||||
return path.with_suffix(path.suffix + ".fingerprint").read_text() == signature
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def remember(path: Path, signature: str) -> None:
|
||||
marker = path.with_suffix(path.suffix + ".fingerprint")
|
||||
temporary = marker.with_suffix(".tmp")
|
||||
temporary.write_text(signature, encoding="ascii")
|
||||
temporary.replace(marker)
|
||||
|
||||
|
||||
def compression(path: Path) -> int:
|
||||
import zipfile
|
||||
|
||||
return (
|
||||
zipfile.ZIP_STORED
|
||||
if path.suffix.lower()
|
||||
in {".mp4", ".mkv", ".webm", ".docx", ".xlsx", ".png", ".jpg", ".jpeg", ".zip"}
|
||||
else zipfile.ZIP_DEFLATED
|
||||
)
|
||||
Reference in new issue
Block a user