mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-08-01 00:09:48 +00:00
Update database connection handling to ensure string paths and set timeout
- Modified database connection calls in DBManager, FeedManager, and MessageScheduler to convert db_path to a string and set a timeout of 30 seconds, improving reliability and performance of database operations. - Enhanced error logging to include detailed information about database path existence and permissions, aiding in debugging and ensuring smoother operation.
This commit is contained in:
+15
-15
@@ -38,7 +38,7 @@ class DBManager:
|
||||
def _init_database(self):
|
||||
"""Initialize the SQLite database with required tables"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create geocoding_cache table for weather command optimization
|
||||
@@ -215,7 +215,7 @@ class DBManager:
|
||||
def get_cached_geocoding(self, query: str) -> Tuple[Optional[float], Optional[float]]:
|
||||
"""Get cached geocoding result for a query"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT latitude, longitude FROM geocoding_cache
|
||||
@@ -236,7 +236,7 @@ class DBManager:
|
||||
if not isinstance(cache_hours, int) or cache_hours < 1 or cache_hours > 87600: # Max 10 years
|
||||
raise ValueError(f"cache_hours must be an integer between 1 and 87600, got: {cache_hours}")
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
# Use parameter binding instead of string formatting
|
||||
cursor.execute('''
|
||||
@@ -252,7 +252,7 @@ class DBManager:
|
||||
def get_cached_value(self, cache_key: str, cache_type: str) -> Optional[str]:
|
||||
"""Get cached value for a key and type"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT cache_value FROM generic_cache
|
||||
@@ -273,7 +273,7 @@ class DBManager:
|
||||
if not isinstance(cache_hours, int) or cache_hours < 1 or cache_hours > 87600: # Max 10 years
|
||||
raise ValueError(f"cache_hours must be an integer between 1 and 87600, got: {cache_hours}")
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
# Use parameter binding instead of string formatting
|
||||
cursor.execute('''
|
||||
@@ -308,7 +308,7 @@ class DBManager:
|
||||
def cleanup_expired_cache(self):
|
||||
"""Remove expired cache entries from all cache tables"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Clean up geocoding cache
|
||||
@@ -331,7 +331,7 @@ class DBManager:
|
||||
def cleanup_geocoding_cache(self):
|
||||
"""Remove expired geocoding cache entries"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM geocoding_cache WHERE expires_at < datetime('now')")
|
||||
deleted_count = cursor.rowcount
|
||||
@@ -344,7 +344,7 @@ class DBManager:
|
||||
def get_database_stats(self) -> Dict[str, Any]:
|
||||
"""Get database statistics"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
stats = {}
|
||||
@@ -380,7 +380,7 @@ class DBManager:
|
||||
def vacuum_database(self):
|
||||
"""Optimize database by reclaiming unused space"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
conn.execute("VACUUM")
|
||||
self.logger.info("Database vacuum completed")
|
||||
except Exception as e:
|
||||
@@ -398,7 +398,7 @@ class DBManager:
|
||||
if not re.match(r'^[a-z_][a-z0-9_]*$', table_name):
|
||||
raise ValueError(f"Invalid table name format: {table_name}")
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
# Table names cannot be parameterized, but we've validated against whitelist
|
||||
cursor.execute(f'CREATE TABLE IF NOT EXISTS {table_name} ({schema})')
|
||||
@@ -422,7 +422,7 @@ class DBManager:
|
||||
# Extra safety: log critical action
|
||||
self.logger.warning(f"CRITICAL: Dropping table '{table_name}'")
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
# Table names cannot be parameterized, but we've validated against whitelist
|
||||
cursor.execute(f'DROP TABLE IF EXISTS {table_name}')
|
||||
@@ -435,7 +435,7 @@ class DBManager:
|
||||
def execute_query(self, query: str, params: Tuple = ()) -> List[Dict]:
|
||||
"""Execute a custom query and return results as list of dictionaries"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, params)
|
||||
@@ -448,7 +448,7 @@ class DBManager:
|
||||
def execute_update(self, query: str, params: Tuple = ()) -> int:
|
||||
"""Execute an update/insert/delete query and return number of affected rows"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, params)
|
||||
conn.commit()
|
||||
@@ -461,7 +461,7 @@ class DBManager:
|
||||
def set_metadata(self, key: str, value: str):
|
||||
"""Set a metadata value for the bot"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO bot_metadata (key, value, updated_at)
|
||||
@@ -474,7 +474,7 @@ class DBManager:
|
||||
def get_metadata(self, key: str) -> Optional[str]:
|
||||
"""Get a metadata value for the bot"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT value FROM bot_metadata WHERE key = ?', (key,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
+16
-5
@@ -11,6 +11,7 @@ import time
|
||||
import hashlib
|
||||
import html
|
||||
import re
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from pathlib import Path
|
||||
@@ -272,7 +273,7 @@ class FeedManager:
|
||||
|
||||
# Query database for all processed item IDs for this feed
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT DISTINCT item_id FROM feed_activity
|
||||
@@ -431,7 +432,7 @@ class FeedManager:
|
||||
|
||||
# Query database for all processed item IDs for this feed
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(str(self.db_path), timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT DISTINCT item_id FROM feed_activity
|
||||
@@ -1181,7 +1182,8 @@ class FeedManager:
|
||||
"""Process queued feed messages and send them at configured intervals"""
|
||||
try:
|
||||
# Get all unsent messages, ordered by priority and queue time
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
db_path = str(self.db_path) # Ensure string, not Path object
|
||||
with sqlite3.connect(db_path, timeout=30.0) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
@@ -1225,7 +1227,7 @@ class FeedManager:
|
||||
|
||||
if success:
|
||||
# Mark as sent
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
with sqlite3.connect(db_path, timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE feed_message_queue
|
||||
@@ -1250,6 +1252,15 @@ class FeedManager:
|
||||
|
||||
except Exception as e:
|
||||
db_path = getattr(self, 'db_path', 'unknown')
|
||||
db_path_str = str(db_path) if db_path != 'unknown' else 'unknown'
|
||||
self.logger.error(f"Error processing message queue: {e}")
|
||||
self.logger.error(f"Database path: {db_path} (exists: {Path(db_path).exists() if isinstance(db_path, str) else False})")
|
||||
if db_path_str != 'unknown':
|
||||
path_obj = Path(db_path_str)
|
||||
self.logger.error(f"Database path: {db_path_str} (exists: {path_obj.exists()}, readable: {os.access(db_path_str, os.R_OK) if path_obj.exists() else False}, writable: {os.access(db_path_str, os.W_OK) if path_obj.exists() else False})")
|
||||
# Check parent directory permissions
|
||||
if path_obj.exists():
|
||||
parent = path_obj.parent
|
||||
self.logger.error(f"Parent directory: {parent} (exists: {parent.exists()}, writable: {os.access(str(parent), os.W_OK) if parent.exists() else False})")
|
||||
else:
|
||||
self.logger.error(f"Database path: {db_path_str}")
|
||||
|
||||
|
||||
+15
-5
@@ -11,6 +11,7 @@ import datetime
|
||||
import pytz
|
||||
import sqlite3
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
@@ -260,10 +261,10 @@ class MessageScheduler:
|
||||
async def _process_channel_operations(self):
|
||||
"""Process pending channel operations from the web viewer"""
|
||||
try:
|
||||
db_path = self.bot.db_manager.db_path
|
||||
db_path = str(self.bot.db_manager.db_path) # Ensure string, not Path object
|
||||
|
||||
# Get pending operations
|
||||
with sqlite3.connect(db_path) as conn:
|
||||
with sqlite3.connect(db_path, timeout=30.0) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
@@ -322,7 +323,7 @@ class MessageScheduler:
|
||||
error_msg = "Failed to remove channel"
|
||||
|
||||
# Update operation status
|
||||
with sqlite3.connect(db_path) as conn:
|
||||
with sqlite3.connect(db_path, timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
if success:
|
||||
cursor.execute('''
|
||||
@@ -346,7 +347,7 @@ class MessageScheduler:
|
||||
self.logger.error(f"Error processing channel operation {op_id}: {e}")
|
||||
# Mark as failed
|
||||
try:
|
||||
with sqlite3.connect(db_path) as conn:
|
||||
with sqlite3.connect(db_path, timeout=30.0) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE channel_operations
|
||||
@@ -361,5 +362,14 @@ class MessageScheduler:
|
||||
|
||||
except Exception as e:
|
||||
db_path = getattr(self.bot.db_manager, 'db_path', 'unknown')
|
||||
db_path_str = str(db_path) if db_path != 'unknown' else 'unknown'
|
||||
self.logger.error(f"Error in _process_channel_operations: {e}")
|
||||
self.logger.error(f"Database path: {db_path} (exists: {Path(db_path).exists() if isinstance(db_path, str) else False})")
|
||||
if db_path_str != 'unknown':
|
||||
path_obj = Path(db_path_str)
|
||||
self.logger.error(f"Database path: {db_path_str} (exists: {path_obj.exists()}, readable: {os.access(db_path_str, os.R_OK) if path_obj.exists() else False}, writable: {os.access(db_path_str, os.W_OK) if path_obj.exists() else False})")
|
||||
# Check parent directory permissions
|
||||
if path_obj.exists():
|
||||
parent = path_obj.parent
|
||||
self.logger.error(f"Parent directory: {parent} (exists: {parent.exists()}, writable: {os.access(str(parent), os.W_OK) if parent.exists() else False})")
|
||||
else:
|
||||
self.logger.error(f"Database path: {db_path_str}")
|
||||
|
||||
Reference in New Issue
Block a user