Write voice metadata atomically

This commit is contained in:
mARTin-B78 2026-08-19 18:39:11 +02:00
parent 11cd5b500c
commit e452b0f000
4 changed files with 29 additions and 5 deletions

View File

@ -5,6 +5,11 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [1.20.44] — 2026-08-19
### Fixed
- **Voice metadata is now written atomically.** Interrupted saves cannot leave truncated `.meta.json` files that make ratings or voice settings disappear.
## [1.20.43] — 2026-08-19
### Added

View File

@ -1 +1 @@
1.20.43
1.20.44

View File

@ -2,8 +2,10 @@
from __future__ import annotations
import json
import os
import re
import shutil
import tempfile
import time
import uuid
from datetime import datetime, timezone
@ -119,7 +121,24 @@ def _load_meta(wav: Path) -> dict:
def _save_meta(wav: Path, meta: dict) -> None:
_meta_path(wav).write_text(json.dumps(meta, indent=2))
# Write beside the final file and replace it atomically. A process exit or
# host/container interruption during write must never leave a truncated
# meta.json that makes ratings/settings appear to have vanished.
target = _meta_path(wav)
target.parent.mkdir(parents=True, exist_ok=True)
fd, tmp_name = tempfile.mkstemp(prefix=f".{target.name}.", suffix=".tmp", dir=target.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(meta, handle, indent=2, ensure_ascii=False)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(tmp_name, target)
finally:
try:
os.unlink(tmp_name)
except FileNotFoundError:
pass
def _picture_path(wav: Path) -> Path | None:

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.20.43">
<meta name="app-version" content="1.20.44">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,7 +27,7 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.20.43">
<link rel="stylesheet" href="/static/style.css?v=1.20.44">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -378,7 +378,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.20.43"></script>
<script src="/static/loader.js?v=1.20.44"></script>
</body>
</html>