diff --git a/database/db.py b/database/db.py index a9e936b..d0639fc 100644 --- a/database/db.py +++ b/database/db.py @@ -23,11 +23,38 @@ class BotDB: def connect(self): """Создание соединения и курсора.""" - # Добавляем таймаут для предотвращения зависаний - self.conn = sqlite3.connect(self.db_file, timeout=10.0) - # Включаем WAL режим для лучшей производительности - self.conn.execute("PRAGMA journal_mode=WAL") - self.cursor = self.conn.cursor() + try: + # Проверяем существование файла базы данных + if not os.path.exists(self.db_file): + self.logger.error(f"Файл базы данных не найден: {self.db_file}") + raise FileNotFoundError(f"Файл базы данных не найден: {self.db_file}") + + # Проверяем права доступа к файлу + if not os.access(self.db_file, os.R_OK | os.W_OK): + self.logger.error(f"Нет прав доступа к файлу базы данных: {self.db_file}") + raise PermissionError(f"Нет прав доступа к файлу базы данных: {self.db_file}") + + # Добавляем таймаут для предотвращения зависаний + self.conn = sqlite3.connect(self.db_file, timeout=10.0) + + # Включаем WAL режим для лучшей производительности + self.conn.execute("PRAGMA journal_mode=WAL") + self.conn.execute("PRAGMA synchronous=NORMAL") + self.conn.execute("PRAGMA cache_size=10000") + self.conn.execute("PRAGMA temp_store=MEMORY") + + self.cursor = self.conn.cursor() + self.logger.info(f"Успешное подключение к базе данных: {self.db_file}") + + except sqlite3.Error as e: + self.logger.error(f"Ошибка SQLite при подключении к базе данных: {e}") + raise + except (FileNotFoundError, PermissionError) as e: + self.logger.error(f"Ошибка файловой системы при подключении к базе данных: {e}") + raise + except Exception as e: + self.logger.error(f"Неожиданная ошибка при подключении к базе данных: {e}") + raise def create_table(self, sql_script): """ @@ -1379,10 +1406,19 @@ class BotDB: def close(self): """Закрытие соединения и курсора.""" - if self.cursor: - self.cursor.close() - if self.conn: - self.conn.close() + try: + if self.cursor: + self.cursor.close() + self.cursor = None + except Exception as e: + self.logger.error(f"Ошибка при закрытии курсора: {e}") + + try: + if self.conn: + self.conn.close() + self.conn = None + except Exception as e: + self.logger.error(f"Ошибка при закрытии соединения: {e}") async def check_user_in_blacklist_async(self, user_id: int): """ @@ -1397,3 +1433,43 @@ class BotDB: """ loop = asyncio.get_event_loop() return await loop.run_in_executor(self.executor, self.get_blacklist_users_by_id, user_id) + + def check_database_integrity(self): + """ + Проверяет целостность базы данных и очищает WAL файлы. + """ + try: + self.connect() + # Проверяем целостность базы данных + self.cursor.execute("PRAGMA integrity_check") + integrity_result = self.cursor.fetchone() + + if integrity_result and integrity_result[0] == "ok": + self.logger.info("Проверка целостности базы данных прошла успешно") + # Очищаем WAL файлы + self.cursor.execute("PRAGMA wal_checkpoint(TRUNCATE)") + self.logger.info("WAL файлы очищены") + else: + self.logger.warning(f"Проблемы с целостностью базы данных: {integrity_result}") + + except Exception as e: + self.logger.error(f"Ошибка при проверке целостности базы данных: {e}") + raise + finally: + self.close() + + def cleanup_wal_files(self): + """ + Очищает WAL файлы и переключает на DELETE режим для предотвращения проблем с I/O. + """ + try: + self.connect() + # Переключаем на DELETE режим для очистки WAL файлов + self.cursor.execute("PRAGMA journal_mode=DELETE") + self.cursor.execute("PRAGMA journal_mode=WAL") + self.logger.info("WAL файлы очищены и режим восстановлен") + except Exception as e: + self.logger.error(f"Ошибка при очистке WAL файлов: {e}") + raise + finally: + self.close() diff --git a/database/fix_database.py b/database/fix_database.py new file mode 100644 index 0000000..a021d30 --- /dev/null +++ b/database/fix_database.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +""" +Скрипт для диагностики и исправления проблем с базой данных Telegram бота. +""" + +import os +import sys +import sqlite3 +from pathlib import Path + +def check_database_file(db_path): + """Проверяет состояние файла базы данных.""" + print(f"Проверка файла: {db_path}") + + if not os.path.exists(db_path): + print(f"❌ Файл базы данных не найден: {db_path}") + return False + + # Проверяем права доступа + if not os.access(db_path, os.R_OK | os.W_OK): + print(f"❌ Нет прав доступа к файлу: {db_path}") + return False + + # Проверяем размер файла + file_size = os.path.getsize(db_path) + print(f"✅ Размер файла: {file_size} байт") + + return True + +def check_wal_files(db_path): + """Проверяет WAL файлы.""" + db_dir = os.path.dirname(db_path) + db_name = os.path.basename(db_path) + base_name = os.path.splitext(db_name)[0] + + wal_file = os.path.join(db_dir, f"{base_name}.db-wal") + shm_file = os.path.join(db_dir, f"{base_name}.db-shm") + + print(f"\nПроверка WAL файлов:") + + if os.path.exists(wal_file): + wal_size = os.path.getsize(wal_file) + print(f"✅ WAL файл найден: {wal_file} ({wal_size} байт)") + else: + print(f"ℹ️ WAL файл не найден: {wal_file}") + + if os.path.exists(shm_file): + shm_size = os.path.getsize(shm_file) + print(f"✅ SHM файл найден: {shm_file} ({shm_size} байт)") + else: + print(f"ℹ️ SHM файл не найден: {shm_file}") + + return wal_file, shm_file + +def test_database_connection(db_path): + """Тестирует подключение к базе данных.""" + print(f"\nТестирование подключения к базе данных...") + + try: + conn = sqlite3.connect(db_path, timeout=10.0) + cursor = conn.cursor() + + # Проверяем версию SQLite + cursor.execute("SELECT sqlite_version()") + version = cursor.fetchone()[0] + print(f"✅ SQLite версия: {version}") + + # Проверяем таблицы + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = cursor.fetchall() + print(f"✅ Найдено таблиц: {len(tables)}") + + # Проверяем целостность + cursor.execute("PRAGMA integrity_check") + integrity = cursor.fetchone()[0] + if integrity == "ok": + print("✅ Целостность базы данных: OK") + else: + print(f"⚠️ Проблемы с целостностью: {integrity}") + + conn.close() + return True + + except sqlite3.Error as e: + print(f"❌ Ошибка SQLite: {e}") + return False + except Exception as e: + print(f"❌ Неожиданная ошибка: {e}") + return False + +def cleanup_wal_files(db_path): + """Очищает WAL файлы.""" + print(f"\nОчистка WAL файлов...") + + try: + conn = sqlite3.connect(db_path, timeout=10.0) + cursor = conn.cursor() + + # Переключаем на DELETE режим для очистки WAL + cursor.execute("PRAGMA journal_mode=DELETE") + cursor.execute("PRAGMA journal_mode=WAL") + + # Принудительно создаем checkpoint + cursor.execute("PRAGMA wal_checkpoint(TRUNCATE)") + + conn.close() + print("✅ WAL файлы очищены") + return True + + except Exception as e: + print(f"❌ Ошибка при очистке WAL файлов: {e}") + return False + +def main(): + """Основная функция.""" + print("🔧 Диагностика базы данных Telegram бота") + print("=" * 50) + + # Определяем путь к базе данных + current_dir = os.getcwd() + db_path = os.path.join(current_dir, 'database', 'tg-bot-database.db') + + print(f"Текущая директория: {current_dir}") + print(f"Путь к базе данных: {db_path}") + + # Проверяем файл базы данных + if not check_database_file(db_path): + print("\n❌ Файл базы данных недоступен. Проверьте права доступа и существование файла.") + return + + # Проверяем WAL файлы + wal_file, shm_file = check_wal_files(db_path) + + # Тестируем подключение + if not test_database_connection(db_path): + print("\n❌ Не удалось подключиться к базе данных.") + return + + # Очищаем WAL файлы + if cleanup_wal_files(db_path): + print("\n✅ База данных проверена и исправлена.") + else: + print("\n⚠️ База данных проверена, но не удалось очистить WAL файлы.") + + print("\n📋 Рекомендации:") + print("1. Убедитесь, что у процесса есть права на запись в директорию database/") + print("2. Проверьте свободное место на диске") + print("3. Если проблемы продолжаются, попробуйте перезапустить бота") + print("4. В крайнем случае, создайте резервную копию и пересоздайте базу данных") + +if __name__ == "__main__": + main() diff --git a/helper_bot/utils/base_dependency_factory.py b/helper_bot/utils/base_dependency_factory.py index adb9706..9d4082f 100644 --- a/helper_bot/utils/base_dependency_factory.py +++ b/helper_bot/utils/base_dependency_factory.py @@ -14,7 +14,9 @@ class BaseDependencyFactory: self.config = configparser.ConfigParser() self.config.read(config_path) self.settings = {} - self.database = BotDB(current_dir, 'tg-bot-database.db') + # Используем абсолютный путь к директории проекта + project_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + self.database = BotDB(project_dir, 'tg-bot-database.db') for section in self.config.sections(): self.settings[section] = {}