fix: 3 bug standalone — soundfont preview/play, Carla bridge play item, realtime sync LAN/Tauri

Bug1: dispatcher CREATE_MIDI_ITEM track_id number vs state string -> String(); play+preview native soundfont verified.
Bug2: Carla play item — chờ OSC ready (poll carla-status, queue nốt khi cold start), chống spawn trùng (carla-status dò port bind, open-in-carla skip khi đã chạy).
Bug3: realtime sync — GET /temp/revision (updated_at+client_id), poll 3s, apply qua deserializeProjectFromSchema; hash-skip autosave + client_id chống ping-pong.
This commit is contained in:
2026-08-10 22:10:12 +07:00
parent d31e226f00
commit aa1cd8d5de
7 changed files with 238 additions and 32 deletions
+36
View File
@@ -136,6 +136,7 @@ class SaveProjectRequest(BaseModel):
class SaveTempProjectRequest(BaseModel):
data_json: str
client_id: Optional[str] = None # client ghi bản này (LAN browser / Tauri UI) — chống ping-pong sync
def get_optional_user(authorization: Optional[str] = Header(None)) -> Optional[dict]:
if authorization and authorization.startswith("Bearer "):
@@ -172,6 +173,10 @@ async def save_temp_project(req: SaveTempProjectRequest, current_user: Optional[
os.makedirs(temp_dir, exist_ok=True)
with open(os.path.join(temp_dir, "autosave.json"), "w", encoding="utf-8") as f:
json.dump({"user_id": user_id, "updated_at": now, "data_json": validated_data_json}, f, ensure_ascii=False)
# Revision sidecar (nhỏ) — realtime sync LAN/Tauri đọc updated_at +
# client_id KHÔNG cần parse data_json (có thể MB).
with open(os.path.join(temp_dir, f"revision_{user_id}.json"), "w", encoding="utf-8") as f:
json.dump({"client_id": req.client_id or "", "updated_at": now}, f, ensure_ascii=False)
except Exception:
pass
return {"message": "Đã lưu dự án tạm tự động", "updated_at": now}
@@ -212,6 +217,37 @@ async def get_temp_project(current_user: Optional[dict] = Depends(get_optional_u
pass
return {"has_temp": False}
@router.get("/temp/revision")
async def temp_project_revision(current_user: Optional[dict] = Depends(get_optional_user)):
"""Revision nhẹ của bản temp: updated_at + client_id — frontend poll 2-3s
để realtime sync giữa LAN browser và Tauri standalone (không tải full
data_json mỗi lần). client_id = client ghi bản cuối → client khác bỏ qua
bản do CHÍNH NÓ ghi (chống ping-pong)."""
user_id = current_user["user_id"] if current_user else "anonymous"
client_id = ""
updated_at = 0.0
try:
from app.config import settings as _st
rev_path = os.path.join(_st.STORAGE_DIR, "temp", f"revision_{user_id}.json")
if os.path.isfile(rev_path):
with open(rev_path, "r", encoding="utf-8") as f:
meta = json.load(f)
client_id = meta.get("client_id", "") or ""
updated_at = float(meta.get("updated_at", 0) or 0)
except Exception:
pass
if not updated_at:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT updated_at FROM projects WHERE id = ? AND is_temp = 1", (f"temp_{user_id}",))
row = cursor.fetchone()
conn.close()
if row:
updated_at = float(row["updated_at"] or 0)
return {"updated_at": updated_at, "client_id": client_id}
def _os_projects_dir() -> str:
"""Thư mục dự án trên hệ điều hành (Ctrl-S desktop):
Windows → Documents/SonicForgeDAW/Projects (fallback USERPROFILE);