split db pop_up and semi auto fix

This commit is contained in:
edo-neo 2025-09-23 15:19:55 +02:00
parent 9d5c935fd2
commit 9fdd0dd977
2 changed files with 220 additions and 4 deletions

View File

@ -3,10 +3,13 @@ import csv
import json
import logging
import re
import os
import sys
from playhouse.sqlite_ext import JSONField
from .models import Archive, Log, Recipes, Session, Users, db, db_archive
from .models.base_model import handle_fatal_sqlite_error
# Keep a unified reference for consumers like Crud_DB
models_reference = {
@ -16,9 +19,32 @@ models_reference = {
"users": Users,
}
# Optional test flag to simulate on-disk SQLite corruption (archive DB only)
if "--kill-db" in sys.argv:
try:
_db = db_archive # Only corrupt the archive database
path = getattr(_db, "database", None)
if path:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wb") as f:
f.write(b"NOT A DATABASE - corrupted for test")
except Exception:
pass
# Connect both databases and create tables in their respective DBs
db.connect()
db_archive.connect()
try:
db.connect()
db_archive.connect()
except Exception as e:
msg = str(e).lower()
if any(s in msg for s in (
"malformed",
"not a database",
"file is encrypted or is not a database",
"database disk image is malformed",
)):
handle_fatal_sqlite_error(e)
raise
# Create tables for the main database (exclude Archive which lives in db_archive)
main_models = [Log, Recipes, Users]

View File

@ -1,13 +1,203 @@
import os
import sys
import shutil
import traceback
from peewee import Model
from playhouse.sqlite_ext import SqliteExtDatabase
# GUI popup support (create minimal app if needed)
try:
from PyQt5.QtWidgets import QApplication, QMessageBox
except Exception:
QApplication = None
QMessageBox = None
db_path = "./data/database"
os.makedirs(db_path, exist_ok=True)
def _delete_archive_and_folder_and_exit():
# Try to detach any attached archive data source alias 'e' if present
try:
if 'db' in globals() and getattr(globals()['db'], 'is_closed', lambda: True)() is False:
try:
globals()['db'].execute_sql('DETACH DATABASE e')
except Exception:
pass
except Exception:
pass
try:
if 'db_archive' in globals() and getattr(globals()['db_archive'], 'is_closed', lambda: True)() is False:
try:
globals()['db_archive'].execute_sql('DETACH DATABASE e')
except Exception:
pass
except Exception:
pass
# Try closing DBs if already created
try:
if 'db' in globals() and getattr(globals()['db'], 'is_closed', lambda: True)() is False:
try:
globals()['db'].close()
except Exception:
pass
except Exception:
pass
try:
if 'db_archive' in globals() and getattr(globals()['db_archive'], 'is_closed', lambda: True)() is False:
try:
globals()['db_archive'].close()
except Exception:
pass
except Exception:
pass
# Remove archive DB explicitly (base file and side-car files)
try:
base = os.path.join(db_path, 'sqlite_archive.db')
candidates = [
base,
os.path.join(db_path, 'sqlite_archive'),
base + '-wal',
base + '-shm',
base + '-journal',
]
for p in candidates:
try:
os.remove(p)
except Exception:
pass
except Exception:
pass
# Try to recreate the new archive DB and import seed data
try:
# Reconnect will create the file if missing
if 'db_archive' in globals():
try:
if getattr(globals()['db_archive'], 'is_closed', lambda: True)():
globals()['db_archive'].connect()
except Exception:
# If connect fails, try reopen after ensuring close
try:
globals()['db_archive'].close()
except Exception:
pass
try:
globals()['db_archive'].connect()
except Exception:
pass
# Lazy import to avoid circular
try:
from .archive import Archive # type: ignore
# Create table
try:
globals()['db_archive'].create_tables([Archive])
except Exception:
pass
# Import from CSV if present
try:
import csv, json, ast
from playhouse.sqlite_ext import JSONField as _JSONField
path = "src/lib/db/imports/archive.csv"
if os.path.exists(path):
with open(path, "r") as f:
reader = csv.DictReader(f)
fields = list(Archive._meta.fields)
count = 0
with globals()['db_archive'].atomic():
for row in reader:
obj = {}
for field in fields:
if type(Archive._meta.fields[field]) is _JSONField:
obj[field] = json.loads(row.get(field, "null"))
else:
try:
obj[field] = ast.literal_eval(row.get(field, ""))
except (SyntaxError, ValueError):
obj[field] = row.get(field)
try:
Archive.insert(**obj).on_conflict_replace().execute()
count += 1
except Exception:
pass
except Exception:
pass
except Exception:
pass
except Exception:
pass
# Hard exit to ensure restart
try:
os._exit(1)
except Exception:
sys.exit(1)
def handle_fatal_sqlite_error(exc=None):
"""Show popup, print error to terminal, delete only the archive DB, try to re-import, then exit."""
msg_text = "ATTENZIONE ERRORE FATALE DATABASE DI TEST PEFOVARORE PREMERE OK E RIAVVIAREIL PROGRAMMA"
# Always print error details to the execution terminal
try:
sys.stderr.write("FATAL SQLITE ERROR DETECTED\n")
if exc is not None:
sys.stderr.write(f"Exception: {exc!r}\n")
try:
traceback.print_exc()
except Exception:
pass
sys.stderr.flush()
except Exception:
pass
# If GUI available, show a critical popup. Create a temp app if needed.
app_created = False
app = None
try:
if QApplication is not None:
app = QApplication.instance()
if app is None:
app = QApplication([])
app_created = True
if QMessageBox is not None:
QMessageBox.critical(None, "Errore Database", msg_text)
except Exception:
# Also echo the user-facing message to stderr in case GUI cannot be shown
try:
sys.stderr.write(msg_text + "\n")
sys.stderr.flush()
except Exception:
pass
finally:
# Ensure resources are cleaned up and exit (with attempted rebuild)
_delete_archive_and_folder_and_exit()
class SafeSqliteExtDatabase(SqliteExtDatabase):
def execute_sql(self, sql, params=None, commit=True):
try:
return super().execute_sql(sql, params=params, commit=commit)
except Exception as e:
msg = str(e).lower()
if any(s in msg for s in (
"malformed",
"not a database",
"file is encrypted or is not a database",
"database disk image is malformed",
)):
handle_fatal_sqlite_error(e)
raise
# Main application database
db = SqliteExtDatabase(
db = SafeSqliteExtDatabase(
db_path + "/sqlite.db",
pragmas={ # see https://www.sqlite.org/pragma.html
"auto_vacuum": 1,
@ -22,7 +212,7 @@ db = SqliteExtDatabase(
)
# Separate database dedicated to archive data
db_archive = SqliteExtDatabase(
db_archive = SafeSqliteExtDatabase(
db_path + "/sqlite_archive.db",
pragmas={
"auto_vacuum": 1,