diff --git a/app/controllers/debugManager.py b/app/controllers/debugManager.py index e1037bb..2d6cc6e 100644 --- a/app/controllers/debugManager.py +++ b/app/controllers/debugManager.py @@ -6,6 +6,7 @@ from sqlmodel import Session, select from app.model.models import Card from app.services.auth import auth_is_admin +from app.services.caldav import get_caldav_handler from app.services.database import add_and_refresh, get_session logger = logging.getLogger(__name__) @@ -63,3 +64,27 @@ def list_all_cards(db: Session = Depends(get_session)): for i in cards: out.append({i.key: i.group.name}) return out + + +@debug_router.get("/calGetActiveEvents") +def cal_get_active_events(db: Session = Depends(get_session)): + handler = get_caldav_handler() + if handler is None: + raise HTTPException(503, "Caldav handler not initialized") + return handler.get_active_events() + + +@debug_router.get("/calGetFutureEvents") +def cal_get_future_events(db: Session = Depends(get_session)): + handler = get_caldav_handler() + if handler is None: + raise HTTPException(503, "Caldav handler not initialized") + return handler.get_future_events() + + +@debug_router.get("/calGetActiveGroups") +def cal_get_active_groups(db: Session = Depends(get_session)): + handler = get_caldav_handler() + if handler is None: + raise HTTPException(503, "Caldav handler not initialized") + return handler.get_active_groups() diff --git a/app/main.py b/app/main.py index 6c7d156..db0f527 100644 --- a/app/main.py +++ b/app/main.py @@ -14,9 +14,9 @@ from app.controllers import ( userManager, ) from app.services.auth import create_first_user, token_router -from app.services.caldav import CaldavEventCache +from app.services.caldav import init_caldav_handler, stop_caldav_handler from app.services.database import create_db_and_tables, get_db_session -from app.services.door import DoorController, init_caldav_handler, init_controller +from app.services.door import DoorController, init_controller from app.services.scanner import BackgroundScanner from app.services.settings import settings @@ -24,12 +24,6 @@ logger = logging.getLogger(__name__) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") scanner = BackgroundScanner(db=get_db_session()) -caldav_handler = CaldavEventCache( - url=settings.caldav_url, - username=settings.caldav_user, - password=settings.caldav_password, - calendarNames=settings.caldav_calendars, -) logging.basicConfig(level=logging.INFO) @@ -47,14 +41,13 @@ async def lifespan(app: FastAPI): if not settings.disable_cards: scanner.start() - if not settings.disable_caldav: - caldav_handler.start() - init_caldav_handler(caldav_handler) + init_caldav_handler() logger.info("-" * 63) logger.info("---- Documentation is at http://127.0.0.1:8000/api/v1/docs ----") logger.info("-" * 63) yield + stop_caldav_handler() # scanner.stop() diff --git a/app/services/caldav.py b/app/services/caldav.py index c15f8dc..5112e47 100644 --- a/app/services/caldav.py +++ b/app/services/caldav.py @@ -1,6 +1,5 @@ import logging import threading -import time from datetime import UTC, date, datetime, timedelta from caldav import get_calendars @@ -19,18 +18,21 @@ class CaldavEventCache: self.events = [] self.is_running = False self.thread: threading.Thread | None = None + self._stopped = threading.Event() def start(self): if self.is_running: logger.error("CaldavEventCache already running") return self.is_running = True + self._stopped.clear() self.thread = threading.Thread(target=self._refresh_loop, daemon=True) self.thread.start() logger.info("CaldavEventCache started") def stop(self): self.is_running = False + self._stopped.set() if self.thread: self.thread.join() logger.info("CaldavEventCache stopped") @@ -41,7 +43,7 @@ class CaldavEventCache: self.refresh() except Exception as e: logger.error(f"Error updating CaldavEventCache: {e}", exc_info=True) - time.sleep(settings.caldav_refresh_interval) + self._stopped.wait(settings.caldav_refresh_interval) def refresh(self, daysBack=1, daysAhead=7): calendars = get_calendars( @@ -113,6 +115,28 @@ class CaldavEventCache: return active + def get_future_events(self, active_time=None): + active_time = active_time or datetime.now().astimezone() + if active_time.tzinfo is None: + active_time = active_time.astimezone() + + today = active_time.date() + future = [] + + for event in self.events: + start = event["start"] + + if event["all_day"]: # noqa: SIM108 + # All-day event end dates are exclusive. + is_active = start <= today + else: + is_active = start <= active_time + + if is_active: + future.append(event) + + return future + def get_active_groups(self, active_time=None): active_events = self.get_active_events(active_time) @@ -126,3 +150,37 @@ class CaldavEventCache: def is_group_active(self, group_name, active_time=None): return group_name in self.get_active_groups(active_time) + + +_caldav_handler: CaldavEventCache | None = None + + +def init_caldav_handler() -> CaldavEventCache | None: + """Build and start the event cache from settings. + Returns None when caldav handling is disabled + """ + global _caldav_handler + if settings.disable_caldav: + logger.info("Caldav handling disabled") + return None + + handler = CaldavEventCache( + url=settings.caldav_url, + username=settings.caldav_user, + password=settings.caldav_password, + calendarNames=settings.caldav_calendars, + ) + handler.start() + _caldav_handler = handler + return handler + + +def get_caldav_handler() -> CaldavEventCache | None: + return _caldav_handler + + +def stop_caldav_handler() -> None: + global _caldav_handler + if _caldav_handler is not None: + _caldav_handler.stop() + _caldav_handler = None diff --git a/app/services/door.py b/app/services/door.py index fda34ba..3b512b1 100644 --- a/app/services/door.py +++ b/app/services/door.py @@ -5,6 +5,7 @@ from sqlalchemy import exc from sqlmodel import select from app.model.models import Card, OneShotAccess +from app.services.caldav import get_caldav_handler from app.services.database import Session, add_and_refresh logger = logging.getLogger(__name__) @@ -65,7 +66,6 @@ class DoorController: _contoller: DoorController | None = None -_caldav_handler = None def init_controller(ctrl: DoorController): @@ -73,11 +73,6 @@ def init_controller(ctrl: DoorController): _contoller = ctrl -def init_caldav_handler(handler): - global _caldav_handler - _caldav_handler = handler - - def get_controller(): if _contoller is None: raise RuntimeError("DoorController not initialized.") @@ -111,7 +106,8 @@ def checkAccess(key: str, db: Session): logger.info("Card Inactive!") return False - if _caldav_handler and _caldav_handler.is_group_active(card.group.name): + caldav_handler = get_caldav_handler() + if caldav_handler and caldav_handler.is_group_active(card.group.name): logger.info("Access Valid!") return True