Files
ahtlon 1d37be5317
Test python action / test (push) Successful in 38s
Pull-Request Vibe Check / ci-test (pull_request) Successful in 29s
Move caldav_handler to caldav.py
Make the sleep interruptable
Add debug endpoints for caldav - gonna make a new endpoint for them i think...
2026-11-05 22:48:26 +01:00

187 lines
5.7 KiB
Python

import logging
import threading
from datetime import UTC, date, datetime, timedelta
from caldav import get_calendars
from app.services.settings import settings
logger = logging.getLogger(__name__)
class CaldavEventCache:
def __init__(self, url, username, password, calendarNames):
self.url = url
self.username = username
self.password = password
self.calendarNames = calendarNames
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")
def _refresh_loop(self):
while self.is_running:
try:
self.refresh()
except Exception as e:
logger.error(f"Error updating CaldavEventCache: {e}", exc_info=True)
self._stopped.wait(settings.caldav_refresh_interval)
def refresh(self, daysBack=1, daysAhead=7):
calendars = get_calendars(
url=self.url,
username=self.username,
password=self.password,
calendar_name=self.calendarNames,
)
cached = []
now = datetime.now(UTC)
for cal in calendars:
my_events = cal.search(
event=True,
start=now - timedelta(days=daysBack),
end=now + timedelta(days=daysAhead),
expand=True,
)
for event in my_events:
component = event.component
start, end = component.start, component.end
all_day = isinstance(start, date) and not isinstance(start, datetime)
if not all_day:
# Treat timezone-naive event times as system-local time.
if start.tzinfo is None:
start = start.astimezone()
if end.tzinfo is None:
end = end.astimezone()
cached.append(
{
"uid": str(component.get("uid", "")),
"summary": str(component.get("summary", "")),
"groups": str(component.get("description", "")).splitlines(),
"start": start,
"end": end,
"all_day": all_day,
}
)
# dont cache empty array.
# it most probably means there was error during caldav connection
# TODO: handle error properly
if len(cached) == 0:
return
self.events = cached
def get_active_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()
active = []
for event in self.events:
start, end = event["start"], event["end"]
if event["all_day"]:
# All-day event end dates are exclusive.
is_active = start <= today < end
else:
is_active = start <= active_time < end
if is_active:
active.append(event)
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)
# combine all groups into a list of lists
active_groups = []
for active_event in active_events:
active_groups.append(active_event["groups"])
# flatten the lists, make every group unique
return set(group for groups in active_groups for group in groups)
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