feat: asset-based image/video sources, notification sounds, UI improvements
Lint & Test / test (push) Has been cancelled
Lint & Test / test (push) Has been cancelled
- Replace URL-based image_source/url fields with image_asset_id/video_asset_id on StaticImagePictureSource and VideoCaptureSource (clean break, no migration) - Resolve asset IDs to file paths at runtime via AssetStore.get_file_path() - Add EntitySelect asset pickers for image/video in stream editor modal - Add notification sound configuration (global sound + per-app overrides) - Unify per-app color and sound overrides into single "Per-App Overrides" section - Persist notification history between server restarts - Add asset management system (upload, edit, delete, soft-delete) - Replace emoji buttons with SVG icons throughout UI - Various backend improvements: SQLite stores, auth, backup, MQTT, webhooks
This commit is contained in:
@@ -55,9 +55,19 @@ _ENTITY_TABLES = [
|
||||
"color_strip_processing_templates",
|
||||
"gradients",
|
||||
"weather_sources",
|
||||
"assets",
|
||||
]
|
||||
|
||||
|
||||
_VALID_TABLES = frozenset(_ENTITY_TABLES) | {"settings", "schema_version"}
|
||||
|
||||
|
||||
def _check_table(table: str) -> None:
|
||||
"""Raise ValueError if *table* is not a known entity table."""
|
||||
if table not in _VALID_TABLES:
|
||||
raise ValueError(f"Invalid table name: {table!r}")
|
||||
|
||||
|
||||
class Database:
|
||||
"""Thread-safe SQLite connection wrapper with WAL mode.
|
||||
|
||||
@@ -169,6 +179,7 @@ class Database:
|
||||
|
||||
Returns list of dicts parsed from the ``data`` JSON column.
|
||||
"""
|
||||
_check_table(table)
|
||||
with self._lock:
|
||||
rows = self._conn.execute(
|
||||
f"SELECT id, data FROM [{table}]"
|
||||
@@ -187,6 +198,7 @@ class Database:
|
||||
|
||||
Skipped silently when writes are frozen.
|
||||
"""
|
||||
_check_table(table)
|
||||
if _writes_frozen:
|
||||
return
|
||||
json_data = json.dumps(data, ensure_ascii=False)
|
||||
@@ -202,6 +214,7 @@ class Database:
|
||||
|
||||
Skipped silently when writes are frozen.
|
||||
"""
|
||||
_check_table(table)
|
||||
if _writes_frozen:
|
||||
return
|
||||
with self._lock:
|
||||
@@ -215,6 +228,7 @@ class Database:
|
||||
|
||||
Skipped silently when writes are frozen.
|
||||
"""
|
||||
_check_table(table)
|
||||
if _writes_frozen:
|
||||
return
|
||||
with self._lock:
|
||||
@@ -226,6 +240,7 @@ class Database:
|
||||
|
||||
Skipped silently when writes are frozen.
|
||||
"""
|
||||
_check_table(table)
|
||||
if _writes_frozen:
|
||||
return
|
||||
with self._lock:
|
||||
@@ -237,6 +252,7 @@ class Database:
|
||||
|
||||
def count(self, table: str) -> int:
|
||||
"""Count rows in an entity table."""
|
||||
_check_table(table)
|
||||
with self._lock:
|
||||
row = self._conn.execute(
|
||||
f"SELECT COUNT(*) as cnt FROM [{table}]"
|
||||
@@ -245,13 +261,15 @@ class Database:
|
||||
|
||||
def table_exists_with_data(self, table: str) -> bool:
|
||||
"""Check if a table exists and has at least one row."""
|
||||
_check_table(table)
|
||||
with self._lock:
|
||||
try:
|
||||
row = self._conn.execute(
|
||||
f"SELECT COUNT(*) as cnt FROM [{table}]"
|
||||
).fetchone()
|
||||
return row["cnt"] > 0
|
||||
except sqlite3.OperationalError:
|
||||
except sqlite3.OperationalError as e:
|
||||
logger.debug("Table %s does not exist or is inaccessible: %s", table, e)
|
||||
return False
|
||||
|
||||
# -- Settings (key-value) ------------------------------------------------
|
||||
@@ -266,7 +284,8 @@ class Database:
|
||||
return None
|
||||
try:
|
||||
return json.loads(row["value"])
|
||||
except json.JSONDecodeError:
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning("Corrupt JSON in setting '%s': %s", key, e)
|
||||
return None
|
||||
|
||||
def set_setting(self, key: str, value: dict) -> None:
|
||||
|
||||
Reference in New Issue
Block a user