feat: migrate storage from JSON files to SQLite
Some checks failed
Lint & Test / test (push) Failing after 28s

Replace 22 individual JSON store files with a single SQLite database
(data/ledgrab.db). All entity stores now use BaseSqliteStore backed by
SQLite with WAL mode, write-through caching, and thread-safe access.

- Add Database class with SQLite backup/restore API
- Add BaseSqliteStore as drop-in replacement for BaseJsonStore
- Convert all 16 entity stores to SQLite
- Move global settings (MQTT, external URL, auto-backup) to SQLite
  settings table
- Replace JSON backup/restore with SQLite snapshot backups (.db files)
- Remove partial export/import feature (backend + frontend)
- Update demo seed to write directly to SQLite
- Add "Backup Now" button to settings UI
- Remove StorageConfig file path fields (single database_file remains)
This commit is contained in:
2026-03-25 00:03:19 +03:00
parent 29fb944494
commit 9dfd2365f4
38 changed files with 941 additions and 880 deletions

View File

@@ -1,6 +1,6 @@
"""Gradient storage with built-in seeding.
Provides CRUD for gradient entities. On first run (empty/missing file),
Provides CRUD for gradient entities. On first run (empty/missing data),
seeds 8 built-in gradients matching the legacy hardcoded palettes.
Built-in gradients are read-only and cannot be deleted or modified.
"""
@@ -9,7 +9,8 @@ import uuid
from datetime import datetime, timezone
from typing import List, Optional
from wled_controller.storage.base_store import BaseJsonStore
from wled_controller.storage.base_sqlite_store import BaseSqliteStore
from wled_controller.storage.database import Database
from wled_controller.storage.gradient import Gradient
from wled_controller.utils import get_logger
@@ -43,12 +44,12 @@ def _tuples_to_stops(tuples: list) -> list:
return [{"position": t[0], "color": [t[1], t[2], t[3]]} for t in tuples]
class GradientStore(BaseJsonStore[Gradient]):
_json_key = "gradients"
class GradientStore(BaseSqliteStore[Gradient]):
_table_name = "gradients"
_entity_name = "Gradient"
def __init__(self, file_path: str):
super().__init__(file_path, Gradient.from_dict)
def __init__(self, db: Database):
super().__init__(db, Gradient.from_dict)
if not self._items:
self._seed_builtins()
@@ -70,7 +71,7 @@ class GradientStore(BaseJsonStore[Gradient]):
logger.info(f"Seeded {len(_BUILTIN_DEFS)} built-in gradients")
# Aliases
get_all_gradients = BaseJsonStore.get_all
get_all_gradients = BaseSqliteStore.get_all
def get_gradient(self, gradient_id: str) -> Gradient:
return self.get(gradient_id)
@@ -104,7 +105,7 @@ class GradientStore(BaseJsonStore[Gradient]):
tags=tags or [],
)
self._items[gid] = gradient
self._save()
self._save_item(gid, gradient)
logger.info(f"Created gradient: {name} ({gid})")
return gradient
@@ -129,7 +130,7 @@ class GradientStore(BaseJsonStore[Gradient]):
if tags is not None:
gradient.tags = tags
gradient.updated_at = datetime.now(timezone.utc)
self._save()
self._save_item(gradient_id, gradient)
logger.info(f"Updated gradient: {gradient_id}")
return gradient