diff --git a/AppImage/scripts/health_persistence.py b/AppImage/scripts/health_persistence.py index 244a7625..1c507301 100644 --- a/AppImage/scripts/health_persistence.py +++ b/AppImage/scripts/health_persistence.py @@ -55,9 +55,9 @@ class HealthPersistence: def _get_conn(self) -> sqlite3.Connection: """Get a SQLite connection with timeout and WAL mode for safe concurrency.""" - conn = sqlite3.connect(str(self.db_path), timeout=10) + conn = sqlite3.connect(str(self.db_path), timeout=30) conn.execute('PRAGMA journal_mode=WAL') - conn.execute('PRAGMA busy_timeout=5000') + conn.execute('PRAGMA busy_timeout=10000') return conn @contextmanager @@ -1327,7 +1327,7 @@ class HealthPersistence: # ──────────────────────────────────────────────────────────────── # Disk Observations API - # ──────────────────────���───────────────────────────────────────── + # ──────────────────────────────────────────────────────────────── def register_disk(self, device_name: str, serial: Optional[str] = None, model: Optional[str] = None, size_bytes: Optional[int] = None): @@ -1340,56 +1340,57 @@ class HealthPersistence: under 'ata8' and we now know the real block device is 'sdh' with serial 'WX72...', update the old entry so observations are linked. """ - now = datetime.now().isoformat() - try: - conn = self._get_conn() - cursor = conn.cursor() - - # Consolidate: if serial is known and an old entry exists with - # a different device_name (e.g. 'ata8' instead of 'sdh'), - # update that entry's device_name so observations carry over. - if serial: + with self._db_lock: + now = datetime.now().isoformat() + try: + conn = self._get_conn() + cursor = conn.cursor() + + # Consolidate: if serial is known and an old entry exists with + # a different device_name (e.g. 'ata8' instead of 'sdh'), + # update that entry's device_name so observations carry over. + if serial: + cursor.execute(''' + SELECT id, device_name FROM disk_registry + WHERE serial = ? AND serial != '' AND device_name != ? + ''', (serial, device_name)) + old_rows = cursor.fetchall() + for old_id, old_dev in old_rows: + # Only consolidate ATA names -> block device names + if old_dev.startswith('ata') and not device_name.startswith('ata'): + # Check if target (device_name, serial) already exists + cursor.execute( + 'SELECT id FROM disk_registry WHERE device_name = ? AND serial = ?', + (device_name, serial)) + existing = cursor.fetchone() + if existing: + # Merge: move observations from old -> existing, then delete old + cursor.execute( + 'UPDATE disk_observations SET disk_registry_id = ? WHERE disk_registry_id = ?', + (existing[0], old_id)) + cursor.execute('DELETE FROM disk_registry WHERE id = ?', (old_id,)) + else: + # Rename the old entry to the real block device name + cursor.execute( + 'UPDATE disk_registry SET device_name = ?, model = COALESCE(?, model), ' + 'size_bytes = COALESCE(?, size_bytes), last_seen = ?, removed = 0 ' + 'WHERE id = ?', + (device_name, model, size_bytes, now, old_id)) + cursor.execute(''' - SELECT id, device_name FROM disk_registry - WHERE serial = ? AND serial != '' AND device_name != ? - ''', (serial, device_name)) - old_rows = cursor.fetchall() - for old_id, old_dev in old_rows: - # Only consolidate ATA names -> block device names - if old_dev.startswith('ata') and not device_name.startswith('ata'): - # Check if target (device_name, serial) already exists - cursor.execute( - 'SELECT id FROM disk_registry WHERE device_name = ? AND serial = ?', - (device_name, serial)) - existing = cursor.fetchone() - if existing: - # Merge: move observations from old -> existing, then delete old - cursor.execute( - 'UPDATE disk_observations SET disk_registry_id = ? WHERE disk_registry_id = ?', - (existing[0], old_id)) - cursor.execute('DELETE FROM disk_registry WHERE id = ?', (old_id,)) - else: - # Rename the old entry to the real block device name - cursor.execute( - 'UPDATE disk_registry SET device_name = ?, model = COALESCE(?, model), ' - 'size_bytes = COALESCE(?, size_bytes), last_seen = ?, removed = 0 ' - 'WHERE id = ?', - (device_name, model, size_bytes, now, old_id)) - - cursor.execute(''' - INSERT INTO disk_registry (device_name, serial, model, size_bytes, first_seen, last_seen, removed) - VALUES (?, ?, ?, ?, ?, ?, 0) - ON CONFLICT(device_name, serial) DO UPDATE SET - model = COALESCE(excluded.model, model), - size_bytes = COALESCE(excluded.size_bytes, size_bytes), - last_seen = excluded.last_seen, - removed = 0 - ''', (device_name, serial or '', model, size_bytes, now, now)) - - conn.commit() - conn.close() - except Exception as e: - print(f"[HealthPersistence] Error registering disk {device_name}: {e}") + INSERT INTO disk_registry (device_name, serial, model, size_bytes, first_seen, last_seen, removed) + VALUES (?, ?, ?, ?, ?, ?, 0) + ON CONFLICT(device_name, serial) DO UPDATE SET + model = COALESCE(excluded.model, model), + size_bytes = COALESCE(excluded.size_bytes, size_bytes), + last_seen = excluded.last_seen, + removed = 0 + ''', (device_name, serial or '', model, size_bytes, now, now)) + + conn.commit() + conn.close() + except Exception as e: + print(f"[HealthPersistence] Error registering disk {device_name}: {e}") def _get_disk_registry_id(self, cursor, device_name: str, serial: Optional[str] = None, diff --git a/AppImage/scripts/notification_channels.py b/AppImage/scripts/notification_channels.py index 53ff059d..6d535092 100644 --- a/AppImage/scripts/notification_channels.py +++ b/AppImage/scripts/notification_channels.py @@ -251,7 +251,7 @@ class TelegramChannel(NotificationChannel): .replace('>', '>')) -# ─── Gotify ────────────────────────────────────────────────────── +# ─── Gotify ───────────────────────────────────��────────────────── class GotifyChannel(NotificationChannel): """Gotify push notification channel with priority mapping.""" diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index 39cdf376..4a251309 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -197,7 +197,7 @@ def capture_journal_context(keywords: list, lines: int = 30, return "" -# ─── Journal Watcher (Real-time) ───────────────────────────────── +# ─── Journal Watcher (Real-time) ───────────────��───────────────── class JournalWatcher: """Watches journald in real-time for critical system events. diff --git a/AppImage/scripts/notification_manager.py b/AppImage/scripts/notification_manager.py index b70e1dcf..050cdf70 100644 --- a/AppImage/scripts/notification_manager.py +++ b/AppImage/scripts/notification_manager.py @@ -1615,6 +1615,7 @@ class NotificationManager: 'ai_openai_base_url': self._config.get('ai_openai_base_url', ''), 'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'), 'ai_custom_prompt': self._config.get('ai_custom_prompt', ''), + 'ai_allow_suggestions': self._config.get('ai_allow_suggestions', 'false') == 'true', 'ai_detail_levels': ai_detail_levels, 'hostname': self._config.get('hostname', ''), 'webhook_secret': self._config.get('webhook_secret', ''),