Handle corrupt/empty notebook files, atomic writes
_read_notebook_file returns None on empty or unparseable JSON instead of crashing with JSONDecodeError (surfaced as 500). save_notebook now writes to a temp file and atomically renames, preventing empty files from container restarts mid-write.
This commit is contained in:
parent
a2b76539da
commit
9781c9e676
@ -2,7 +2,9 @@
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
@ -116,6 +118,9 @@ def list_notebooks(directory: Path) -> list[NotebookSummary]:
|
||||
|
||||
try:
|
||||
nb = _read_notebook_file(path)
|
||||
if nb is None:
|
||||
logger.warning("Skipping empty/corrupt notebook: %s", path)
|
||||
continue
|
||||
description, schematic_svg = _extract_card_data(nb)
|
||||
summaries.append(NotebookSummary(
|
||||
id=nb_id,
|
||||
@ -152,10 +157,18 @@ def save_notebook(directory: Path, notebook_id: str, notebook: Notebook) -> None
|
||||
notebook.metadata.modified = _now_iso()
|
||||
|
||||
path = user_dir / f"{notebook_id}{SPICEBOOK_EXT}"
|
||||
path.write_text(
|
||||
notebook.model_dump_json(indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
content = notebook.model_dump_json(indent=2)
|
||||
# Atomic write: write to temp file then rename, so a crash can't leave an empty file
|
||||
fd, tmp = tempfile.mkstemp(dir=user_dir, suffix=".tmp")
|
||||
try:
|
||||
os.write(fd, content.encode("utf-8"))
|
||||
os.fsync(fd)
|
||||
os.close(fd)
|
||||
os.replace(tmp, path)
|
||||
except BaseException:
|
||||
os.close(fd)
|
||||
os.unlink(tmp)
|
||||
raise
|
||||
|
||||
|
||||
def create_notebook(directory: Path, title: str, engine: str = "ngspice") -> tuple[str, Notebook]:
|
||||
@ -196,8 +209,13 @@ def delete_notebook(directory: Path, notebook_id: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _read_notebook_file(path: Path) -> Notebook:
|
||||
"""Read and validate a .spicebook JSON file."""
|
||||
def _read_notebook_file(path: Path) -> Notebook | None:
|
||||
"""Read and validate a .spicebook JSON file. Returns None on corrupt/empty files."""
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
data = json.loads(raw)
|
||||
if not raw.strip():
|
||||
return None
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
return Notebook.model_validate(data)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user