split db pop_up and semi auto fix
This commit is contained in:
parent
9d5c935fd2
commit
9fdd0dd977
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user