lklk
This commit is contained in:
163
SYS/config.py
163
SYS/config.py
@@ -486,53 +486,7 @@ def load_config() -> Dict[str, Any]:
|
||||
)
|
||||
log(summary)
|
||||
|
||||
# Try to detect if the most recent audit indicates we previously saved items
|
||||
# that are no longer present in the loaded config (possible overwrite/restore)
|
||||
try:
|
||||
audit_path = Path(db.db_path).with_name("config_audit.log")
|
||||
if audit_path.exists():
|
||||
last_line = None
|
||||
with audit_path.open("r", encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
if line and line.strip():
|
||||
last_line = line
|
||||
if last_line:
|
||||
try:
|
||||
last_entry = json.loads(last_line)
|
||||
last_provs = set(last_entry.get("providers") or [])
|
||||
current_provs = set(provs)
|
||||
missing = sorted(list(last_provs - current_provs))
|
||||
if missing:
|
||||
log(
|
||||
f"WARNING: Config mismatch on load - last saved providers {sorted(list(last_provs))} "
|
||||
f"are missing from loaded config: {missing} (last saved {last_entry.get('dt')})"
|
||||
)
|
||||
try:
|
||||
# Write a forensic mismatch record to help diagnose potential overwrites
|
||||
mismatch_path = Path(db.db_path).with_name("config_mismatch.log")
|
||||
record = {
|
||||
"detected": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"db": str(db.db_path),
|
||||
"db_mtime": mtime,
|
||||
"last_saved_dt": last_entry.get("dt"),
|
||||
"last_saved_providers": sorted(list(last_provs)),
|
||||
"missing": missing,
|
||||
}
|
||||
try:
|
||||
backup_dir = Path(db.db_path).with_name("config_backups")
|
||||
if backup_dir.exists():
|
||||
files = sorted(backup_dir.glob("medios-backup-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
record["latest_backup"] = str(files[0]) if files else None
|
||||
except Exception:
|
||||
pass
|
||||
with mismatch_path.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(record) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
# Forensics disabled: audit/mismatch/backup detection removed to simplify code.
|
||||
except Exception:
|
||||
pass
|
||||
return db_config
|
||||
@@ -722,120 +676,13 @@ def save_config(config: Dict[str, Any]) -> int:
|
||||
except Exception as exc:
|
||||
log(f"Warning: WAL checkpoint failed: {exc}")
|
||||
|
||||
# Audit to disk so we can correlate saves across restarts and processes.
|
||||
|
||||
# Audit to disk so we can correlate saves across restarts and processes.
|
||||
# Forensics disabled: audit/logs/backups removed to keep save lean.
|
||||
# Release the save lock we acquired earlier
|
||||
try:
|
||||
audit_path = Path(db.db_path).with_name("config_audit.log")
|
||||
|
||||
# Gather non-secret summary info (provider/store names)
|
||||
provider_names = []
|
||||
store_names = []
|
||||
try:
|
||||
pblock = config.get("provider")
|
||||
if isinstance(pblock, dict):
|
||||
provider_names = [str(k) for k in pblock.keys()]
|
||||
except Exception:
|
||||
provider_names = []
|
||||
try:
|
||||
sblock = config.get("store")
|
||||
if isinstance(sblock, dict):
|
||||
store_names = [str(k) for k in sblock.keys()]
|
||||
except Exception:
|
||||
store_names = []
|
||||
|
||||
stack = traceback.format_stack()
|
||||
caller = stack[-1].strip() if stack else ""
|
||||
|
||||
# Try to include the database file modification time for correlation
|
||||
db_mtime = None
|
||||
try:
|
||||
db_mtime = datetime.datetime.utcfromtimestamp(db.db_path.stat().st_mtime).isoformat() + "Z"
|
||||
except Exception:
|
||||
db_mtime = None
|
||||
|
||||
# Create a consistent timestamped backup of the DB so we can recover later
|
||||
backup_path = None
|
||||
try:
|
||||
backup_dir = Path(db.db_path).with_name("config_backups")
|
||||
backup_dir.mkdir(parents=False, exist_ok=True)
|
||||
ts = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
|
||||
candidate = backup_dir / f"medios-backup-{ts}.db"
|
||||
try:
|
||||
# Use sqlite backup API for a consistent copy
|
||||
src_con = sqlite3.connect(str(db.db_path))
|
||||
dest_con = sqlite3.connect(str(candidate))
|
||||
src_con.backup(dest_con)
|
||||
dest_con.close()
|
||||
src_con.close()
|
||||
backup_path = str(candidate)
|
||||
except Exception as e:
|
||||
log(f"Warning: Failed to create DB backup: {e}")
|
||||
|
||||
# Prune older backups (keep last 20)
|
||||
try:
|
||||
files = sorted(backup_dir.glob("medios-backup-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
for old in files[20:]:
|
||||
try:
|
||||
old.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
backup_path = None
|
||||
|
||||
# Collect process/exec info and a short hash of the config for forensic tracing
|
||||
try:
|
||||
exe = sys.executable
|
||||
argv = list(sys.argv)
|
||||
cwd = os.getcwd()
|
||||
user = getpass.getuser()
|
||||
try:
|
||||
cfg_hash = hashlib.md5(json.dumps(config, sort_keys=True).encode('utf-8')).hexdigest()
|
||||
except Exception:
|
||||
cfg_hash = None
|
||||
except Exception:
|
||||
exe = None
|
||||
argv = None
|
||||
cwd = None
|
||||
user = None
|
||||
cfg_hash = None
|
||||
|
||||
entry = {
|
||||
"ts": time.time(),
|
||||
"dt": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"pid": os.getpid(),
|
||||
"exe": exe,
|
||||
"argv": argv,
|
||||
"cwd": cwd,
|
||||
"user": user,
|
||||
"stack": "".join(stack[-20:]),
|
||||
"caller": caller,
|
||||
"config_hash": cfg_hash,
|
||||
"saved_entries": saved_entries,
|
||||
"changed_count": changed_count,
|
||||
"db": str(db.db_path),
|
||||
"db_mtime": db_mtime,
|
||||
"backup": backup_path,
|
||||
"providers": provider_names,
|
||||
"stores": store_names,
|
||||
}
|
||||
try:
|
||||
with audit_path.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(entry) + "\n")
|
||||
except Exception:
|
||||
# Best-effort; don't fail the save if audit write fails
|
||||
log("Warning: Failed to write config audit file")
|
||||
if lock_dir is not None and lock_dir.exists():
|
||||
_release_save_lock(lock_dir)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
# Release the save lock we acquired earlier
|
||||
try:
|
||||
if lock_dir is not None and lock_dir.exists():
|
||||
_release_save_lock(lock_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
break
|
||||
except sqlite3.OperationalError as exc:
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional
|
||||
from contextlib import contextmanager
|
||||
import time
|
||||
import datetime
|
||||
from SYS.logger import log
|
||||
from SYS.logger import debug, log
|
||||
|
||||
# DB execute retry settings (for transient 'database is locked' errors)
|
||||
_DB_EXEC_RETRY_MAX = 5
|
||||
@@ -61,9 +61,9 @@ class Database:
|
||||
self.db_path = DB_PATH
|
||||
db_existed = self.db_path.exists()
|
||||
if db_existed:
|
||||
log(f"Opening existing medios.db at {self.db_path}")
|
||||
debug(f"Opening existing medios.db at {self.db_path}")
|
||||
else:
|
||||
log(f"Creating medios.db at {self.db_path}")
|
||||
debug(f"Creating medios.db at {self.db_path}")
|
||||
|
||||
self.conn = sqlite3.connect(
|
||||
str(self.db_path),
|
||||
|
||||
Reference in New Issue
Block a user