From dc514895df6c35c1e89bf91a7cf409808cacc4f2 Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Thu, 29 Aug 2024 12:05:04 +0200 Subject: [PATCH] further improve db error handling --- src/EzLanManager.py | 43 ++-------- src/ez_lan_manager/pages/DbErrorPage.py | 14 +++- src/ez_lan_manager/pages/NewsPage.py | 11 ++- .../services/DatabaseService.py | 84 ++++++++++++------- 4 files changed, 84 insertions(+), 68 deletions(-) diff --git a/src/EzLanManager.py b/src/EzLanManager.py index 2ece807..0e709c5 100644 --- a/src/EzLanManager.py +++ b/src/EzLanManager.py @@ -6,12 +6,12 @@ from pathlib import Path from rio import App, Theme, Color, Font, Page, Session from from_root import from_root -from src.ez_lan_manager import pages, init_services, ConfigurationService +from src.ez_lan_manager import pages, init_services from src.ez_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard from src.ez_lan_manager.services.DatabaseService import NoDatabaseConnectionError from src.ez_lan_manager.types.SessionStorage import SessionStorage -logger = logging.getLogger(__name__.split(".")[-1]) +logger = logging.getLogger("EzLanManager") if __name__ == "__main__": theme = Theme.from_colors( @@ -31,38 +31,8 @@ if __name__ == "__main__": try: services = init_services() except NoDatabaseConnectionError: - configuration_service = ConfigurationService(from_root("config.toml")) - lan_info = configuration_service.get_lan_info() - app = App( - name="EZ LAN Manager", - pages=[ - Page( - name="DbErrorPage", - page_url="", - build=pages.DbErrorPage, - ) - ], - theme=theme, - default_attachments=[configuration_service], - assets_dir=Path(__file__).parent / "assets", - icon=from_root("src/ez_lan_manager/assets/img/favicon.png"), - meta_tags={ - "robots": "INDEX,FOLLOW", - "description": f"Info und Verwaltungs-Seite der LAN Party '{lan_info.name} - {lan_info.iteration}'.", - "og:description": f"Info und Verwaltungs-Seite der LAN Party '{lan_info.name} - {lan_info.iteration}'.", - "keywords": "Gaming, Clan, Guild, Verein, Club, Einfach, Zocken, Genuss, Gesellschaft, Videospiele, " - "Videogames, LAN, Party, EZ, LAN, Manager", - "author": "David Rodenkirchen", - "publisher": "EZ GG e.V.", - "copyright": "EZ GG e.V.", - "audience": "Alle", - "page-type": "Management Application", - "page-topic": "LAN Party", - "expires": "", - "revisit-after": "2 days" - } - ) - sys.exit(app.run_as_web_server()) + logger.fatal("Could not connect to database, exiting...") + sys.exit(1) lan_info = services[2].get_lan_info() @@ -157,6 +127,11 @@ if __name__ == "__main__": page_url="account", build=pages.AccountPage, guard=logged_in_guard + ), + Page( + name="DbErrorPage", + page_url="db-error", + build=pages.DbErrorPage, ) ], theme=theme, diff --git a/src/ez_lan_manager/pages/DbErrorPage.py b/src/ez_lan_manager/pages/DbErrorPage.py index 33558a8..c20b634 100644 --- a/src/ez_lan_manager/pages/DbErrorPage.py +++ b/src/ez_lan_manager/pages/DbErrorPage.py @@ -1,23 +1,33 @@ from __future__ import annotations +from asyncio import sleep from typing import * # type: ignore from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text +from src.ez_lan_manager.services.DatabaseService import DatabaseService from src.ez_lan_manager import ConfigurationService from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox class DbErrorPage(Component): @event.on_window_size_change - async def on_window_size_change(self): + async def on_window_size_change(self) -> None: await self.force_refresh() + @event.on_mount + async def retry_db_connect(self) -> None: + while not self.session[DatabaseService].is_connected: + await sleep(2) + self.session.navigate_to("./") + def build(self) -> Component: content = Card( content=MainViewContentBox( content=Text( - text="Ouh-oh, da läuft gerade irgendwas schief mit unserer Datenbank.\n\nUnser Team kümmert sich bereits um das Problem.", + text="Ouh-oh, da läuft gerade irgendwas schief.\n\n" + "Unser Team kümmert sich bereits um das Problem.\n\n" + "Du wirst automatisch weitergeleitet sobald das System wieder verfügbar ist.", margin=2, style=TextStyle( fill=self.session.theme.danger_color, diff --git a/src/ez_lan_manager/pages/NewsPage.py b/src/ez_lan_manager/pages/NewsPage.py index ec241f3..8aad72c 100644 --- a/src/ez_lan_manager/pages/NewsPage.py +++ b/src/ez_lan_manager/pages/NewsPage.py @@ -3,11 +3,20 @@ from rio import Column, Component, event from src.ez_lan_manager import ConfigurationService, NewsService from src.ez_lan_manager.components.NewsPost import NewsPost from src.ez_lan_manager.pages import BasePage +from src.ez_lan_manager.services.DatabaseService import NoDatabaseConnectionError +from src.ez_lan_manager.types.News import News + class NewsPage(Component): + news_posts: list[News] = [] + @event.on_populate async def on_populate(self) -> None: await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Neuigkeiten") + try: + self.news_posts = self.session[NewsService].get_news()[:8] + except NoDatabaseConnectionError: + self.session.navigate_to("db-error") def build(self) -> Component: posts = [NewsPost( @@ -16,7 +25,7 @@ class NewsPage(Component): text=news.content, date=news.news_date.strftime("%d.%m.%Y"), author=news.author.user_name - ) for news in self.session[NewsService].get_news()[:8]] + ) for news in self.news_posts] return BasePage( content=Column( *posts, diff --git a/src/ez_lan_manager/services/DatabaseService.py b/src/ez_lan_manager/services/DatabaseService.py index 1615436..920c744 100644 --- a/src/ez_lan_manager/services/DatabaseService.py +++ b/src/ez_lan_manager/services/DatabaseService.py @@ -33,10 +33,31 @@ class DatabaseService: f"Connecting to database '{self._database_config.db_name}' on " f"{self._database_config.db_user}@{self._database_config.db_host}:{self._database_config.db_port}" ) - self._establish_new_connection() + self._connection: Optional[mariadb.Connection] = None + self._reestablishment_lock = False + self.establish_new_connection() + + @property + def is_connected(self) -> bool: + try: + self._connection.ping() + except Exception: + try: + self.establish_new_connection() + return True + except NoDatabaseConnectionError: + return False + return True + + def establish_new_connection(self) -> None: + if self._reestablishment_lock: + return + self._reestablishment_lock = True + + if isinstance(self._connection, mariadb.Connection): + self._connection.close() + self._connection = None - def _establish_new_connection(self) -> None: - retries = 0 for _ in range(DatabaseService.MAX_CONNECTION_RETRIES): try: self._connection = mariadb.connect( @@ -47,10 +68,11 @@ class DatabaseService: database=self._database_config.db_name ) except mariadb.Error: - sleep(0.5) - retries += 1 + sleep(0.4) continue + self._reestablishment_lock = False return + self._reestablishment_lock = False raise NoDatabaseConnectionError @@ -110,7 +132,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.create_user(user_name, user_mail, password_hash) except mariadb.IntegrityError as e: logger.warning(f"Aborted duplication entry: {e}") @@ -130,7 +152,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.update_user(user) except mariadb.IntegrityError as e: logger.warning(f"Aborted duplication entry: {e}") @@ -147,7 +169,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.add_transaction(transaction) except Exception as e: logger.warning(f"Error adding Transaction: {e}") @@ -164,7 +186,7 @@ class DatabaseService: self._connection.commit() result = cursor.fetchall() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_all_transactions_for_user(user_id) except mariadb.Error as e: logger.error(f"Error getting all transactions for user: {e}") @@ -190,7 +212,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.add_news(news) except Exception as e: logger.warning(f"Error adding Transaction: {e}") @@ -202,7 +224,7 @@ class DatabaseService: cursor.execute("SELECT * FROM news INNER JOIN users ON news.news_author = users.user_id WHERE news_date BETWEEN ? AND ?;", (dt_start, dt_end)) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_news(dt_start, dt_end) except Exception as e: logger.warning(f"Error fetching news: {e}") @@ -228,7 +250,7 @@ class DatabaseService: cursor.execute("SELECT * FROM tickets INNER JOIN users ON tickets.user = users.user_id;", ()) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_tickets() except Exception as e: logger.warning(f"Error fetching tickets: {e}") @@ -251,7 +273,7 @@ class DatabaseService: cursor.execute("SELECT * FROM tickets INNER JOIN users ON tickets.user = users.user_id WHERE user_id=?;", (user_id, )) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_ticket_for_user(user_id) except Exception as e: logger.warning(f"Error fetching ticket for user: {e}") @@ -275,7 +297,7 @@ class DatabaseService: cursor.execute("INSERT INTO tickets (ticket_category, user) VALUES (?, ?)", (category, user_id)) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.generate_ticket_for_user(user_id, category) except Exception as e: logger.warning(f"Error generating ticket for user: {e}") @@ -290,7 +312,7 @@ class DatabaseService: affected_rows = cursor.rowcount self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.change_ticket_owner(ticket_id, new_owner_id) except Exception as e: logger.warning(f"Error transferring ticket to user: {e}") @@ -303,7 +325,7 @@ class DatabaseService: cursor.execute("DELETE FROM tickets WHERE ticket_id = ?;", (ticket_id, )) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.change_ticket_owner(ticket_id) except Exception as e: logger.warning(f"Error deleting ticket: {e}") @@ -319,7 +341,7 @@ class DatabaseService: cursor.execute("INSERT INTO seats (seat_id, seat_category) VALUES (?, ?);", (seat[0], seat[1])) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.generate_fresh_seats_table(seats) except Exception as e: logger.warning(f"Error generating fresh seats table: {e}") @@ -332,7 +354,7 @@ class DatabaseService: cursor.execute("SELECT seats.*, users.* FROM seats LEFT JOIN users ON seats.user = users.user_id;") self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_seating_info() except Exception as e: logger.warning(f"Error getting seats table: {e}") @@ -355,7 +377,7 @@ class DatabaseService: affected_rows = cursor.rowcount self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.seat_user(seat_id, user_id) except Exception as e: logger.warning(f"Error seating user: {e}") @@ -369,7 +391,7 @@ class DatabaseService: cursor.execute("SELECT * FROM catering_menu_items;") self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_menu_items() except Exception as e: logger.warning(f"Error fetching menu items: {e}") @@ -393,7 +415,7 @@ class DatabaseService: cursor.execute("SELECT * FROM catering_menu_items WHERE catering_menu_item_id = ?;", (menu_item_id, )) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_menu_item(menu_item_id) except Exception as e: logger.warning(f"Error fetching menu items: {e}") @@ -420,7 +442,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.add_menu_item(name, info, price, category, is_disabled) except Exception as e: logger.warning(f"Error adding menu item: {e}") @@ -441,7 +463,7 @@ class DatabaseService: cursor.execute("DELETE FROM catering_menu_items WHERE catering_menu_item_id = ?;", (menu_item_id,)) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.delete_menu_item(menu_item_id) except Exception as e: logger.warning(f"Error deleting menu item: {e}") @@ -458,7 +480,7 @@ class DatabaseService: affected_rows = cursor.rowcount self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.update_menu_item(updated_item) except Exception as e: logger.warning(f"Error updating menu item: {e}") @@ -489,7 +511,7 @@ class DatabaseService: is_delivery=is_delivery ) except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.add_new_order(menu_items, user_id, is_delivery) except Exception as e: logger.warning(f"Error placing order: {e}") @@ -505,7 +527,7 @@ class DatabaseService: affected_rows = cursor.rowcount self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.change_order_status(order_id, status) except Exception as e: logger.warning(f"Error updating menu item: {e}") @@ -528,7 +550,7 @@ class DatabaseService: cursor.execute(query) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_orders(user_id, status) except Exception as e: logger.warning(f"Error getting orders: {e}") @@ -560,7 +582,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_menu_items_for_order(order_id) except Exception as e: logger.warning(f"Error getting order items: {e}") @@ -587,7 +609,7 @@ class DatabaseService: ) self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.set_user_profile_picture(user_id, picture_data) except Exception as e: logger.warning(f"Error setting user profile picture: {e}") @@ -602,7 +624,7 @@ class DatabaseService: return return r[0] except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_user_profile_picture(user_id) except Exception as e: logger.warning(f"Error setting user profile picture: {e}") @@ -615,7 +637,7 @@ class DatabaseService: cursor.execute("SELECT * FROM users;") self._connection.commit() except mariadb.InterfaceError: - self._establish_new_connection() + self.establish_new_connection() return self.get_all_users() except Exception as e: logger.warning(f"Error getting all users: {e}")