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 json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from playhouse.sqlite_ext import JSONField
|
from playhouse.sqlite_ext import JSONField
|
||||||
|
|
||||||
from .models import Archive, Log, Recipes, Session, Users, db, db_archive
|
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
|
# Keep a unified reference for consumers like Crud_DB
|
||||||
models_reference = {
|
models_reference = {
|
||||||
|
|
@ -16,9 +19,32 @@ models_reference = {
|
||||||
"users": Users,
|
"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
|
# Connect both databases and create tables in their respective DBs
|
||||||
db.connect()
|
try:
|
||||||
db_archive.connect()
|
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)
|
# Create tables for the main database (exclude Archive which lives in db_archive)
|
||||||
main_models = [Log, Recipes, Users]
|
main_models = [Log, Recipes, Users]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,203 @@
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import traceback
|
||||||
|
|
||||||
from peewee import Model
|
from peewee import Model
|
||||||
from playhouse.sqlite_ext import SqliteExtDatabase
|
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"
|
db_path = "./data/database"
|
||||||
os.makedirs(db_path, exist_ok=True)
|
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
|
# Main application database
|
||||||
db = SqliteExtDatabase(
|
db = SafeSqliteExtDatabase(
|
||||||
db_path + "/sqlite.db",
|
db_path + "/sqlite.db",
|
||||||
pragmas={ # see https://www.sqlite.org/pragma.html
|
pragmas={ # see https://www.sqlite.org/pragma.html
|
||||||
"auto_vacuum": 1,
|
"auto_vacuum": 1,
|
||||||
|
|
@ -22,7 +212,7 @@ db = SqliteExtDatabase(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Separate database dedicated to archive data
|
# Separate database dedicated to archive data
|
||||||
db_archive = SqliteExtDatabase(
|
db_archive = SafeSqliteExtDatabase(
|
||||||
db_path + "/sqlite_archive.db",
|
db_path + "/sqlite_archive.db",
|
||||||
pragmas={
|
pragmas={
|
||||||
"auto_vacuum": 1,
|
"auto_vacuum": 1,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user