From 9acbb9530ad45313f888fff7f5e07b5491186bb4 Mon Sep 17 00:00:00 2001 From: edo-neo Date: Mon, 15 Sep 2025 11:23:58 +0200 Subject: [PATCH] auto back up upon save --- src/lib/db/crud_db.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/lib/db/crud_db.py b/src/lib/db/crud_db.py index 7ab757f..1f6a97b 100755 --- a/src/lib/db/crud_db.py +++ b/src/lib/db/crud_db.py @@ -1,4 +1,6 @@ -from peewee import TextField +import time +import logging +from peewee import TextField, OperationalError from playhouse.shortcuts import model_to_dict from . import db, models_reference @@ -18,6 +20,33 @@ class Crud_DB: for column_name, filter in filters.items(): self.filter(column_name, filter, filter_storage=self.default_filters) + def _execute_with_retry(self, func, max_retries=8, base_delay=0.1): + """Execute a DB operation with retry on transient SQLite 'database is locked' errors.""" + last_exc = None + for attempt in range(max_retries): + try: + return func() + except OperationalError as e: + msg = str(e).lower() + if "database is locked" in msg or "database is busy" in msg: + delay = min(base_delay * (2 ** attempt), 1.5) + try: + logging.getLogger(__name__).warning( + f"SQLite busy/locked, retrying commit (attempt {attempt + 1}/{max_retries}) after {delay:.2f}s" + ) + except Exception: + pass + time.sleep(delay) + last_exc = e + continue + # Not a transient lock error: re-raise immediately + raise + # Exhausted retries + if last_exc is not None: + raise last_exc + # Fallback if no exception captured (should not happen) + return func() + @db.connection_context() @db.atomic() def commit(self, data, deleted_rows=None): @@ -25,7 +54,7 @@ class Crud_DB: if hasattr(self.table_model, "crud_delete"): deleted = self.table_model.crud_delete(deleted_rows) else: - deleted = self.table_model.delete().where(self.table_pk << deleted_rows).execute() + deleted = self._execute_with_retry(lambda: self.table_model.delete().where(self.table_pk << deleted_rows).execute()) if deleted != len(deleted_rows): raise AssertionError(f"deleted {deleted} rows instead of the expected {len(deleted_rows)}") # SQLITE DOES NOT SUPPORT UPDATE, ONLY REPLACE @@ -42,7 +71,7 @@ class Crud_DB: if hasattr(self.table_model, "crud_update"): self.table_model.crud_update(complete_data) else: - self.table_model.insert_many(complete_data).on_conflict_replace().execute() + self._execute_with_retry(lambda: self.table_model.insert_many(complete_data).on_conflict_replace().execute()) def revert(self): self.sorting.clear()