Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/source/available_checks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Options
Optional password to use for authenticating at a server requiring authentication.
If used, also a user name must be provided.

.. option:: match

Optional string used to match the calendar summary.
If used, it will remove calendar events that don't _contain_ this string.


Requirements
============

Expand Down
5 changes: 5 additions & 0 deletions doc/source/available_wakeups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ Options

Timeout for executed requests in seconds. Default: 5.

.. option:: match

Optional string used to match the calendar summary.
If used, it will remove calendar events that don't _contain_ this string.


Requirements
============
Expand Down
46 changes: 33 additions & 13 deletions src/autosuspend/checks/ical.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def _extract_events_from_component(


def list_calendar_events(
data: IO[bytes], start_at: datetime, end_at: datetime
data: IO[bytes], start_at: datetime, end_at: datetime, match: str | None
) -> Sequence[CalendarEvent]:
"""List all relevant calendar events in the provided interval.

Expand All @@ -282,27 +282,46 @@ def list_calendar_events(

events = []
for component in calendar.walk("VEVENT"):
events.extend(
_extract_events_from_component(
component, recurring_changes, start_at, end_at
if match is None or match in component.get('summary'):
events.extend(
_extract_events_from_component(
component, recurring_changes, start_at, end_at
)
)
)

return sorted(events, key=lambda e: e.start)

class GeneralCalendar(NetworkMixin):
"""
Superclass of calendar events """

def __init__(self, name: str, match: str, **kwargs: Any) -> None:
NetworkMixin.__init__(self, **kwargs)
self._match = match

class ActiveCalendarEvent(NetworkMixin, Activity):
@classmethod
def collect_init_args(
cls, config, *args,
**kwargs: Any
) -> dict[str, Any]:
args = NetworkMixin.collect_init_args(config, *args, **kwargs)
args["match"] = config.get("match")
return args


class ActiveCalendarEvent(GeneralCalendar, Activity):
"""Determines activity by checking against events in an icalendar file."""

def __init__(self, name: str, **kwargs: Any) -> None:
NetworkMixin.__init__(self, **kwargs)
def __init__(self, name: str, *args, **kwargs) -> None:
GeneralCalendar.__init__(self, **kwargs)
Activity.__init__(self, name)

def check(self) -> str | None:
response = self.request()
start = datetime.now(timezone.utc)
end = start + timedelta(minutes=1)
events = list_calendar_events(BytesIO(response.content), start, end)
events = list_calendar_events(
BytesIO(response.content), start, end, match=self._match)
self.logger.debug(
"Listing active events between %s and %s returned %s events",
start,
Expand All @@ -315,18 +334,19 @@ def check(self) -> str | None:
return None


class Calendar(NetworkMixin, Wakeup):
class Calendar(GeneralCalendar, Wakeup):
"""Uses an ical calendar to wake up on the next scheduled event."""

def __init__(self, name: str, **kwargs: Any) -> None:
NetworkMixin.__init__(self, **kwargs)
def __init__(self, name: str, *args, **kwargs) -> None:
GeneralCalendar.__init__(self, **kwargs)
Wakeup.__init__(self, name)

def check(self, timestamp: datetime) -> datetime | None:
response = self.request()

end = timestamp + timedelta(weeks=6 * 4)
events = list_calendar_events(BytesIO(response.content), timestamp, end)
events = list_calendar_events(
BytesIO(response.content), timestamp, end, match=self._match)
# Filter out currently active events. They are not our business.
events = [e for e in events if e.start >= timestamp]

Expand Down
Loading