diff --git a/Pipfile b/Pipfile index 29084d528646b28c8b8f3b4c71029fea380182d6..4d2b47851232f73fda0307dbcfeac52002cd3490 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ flask-socketio= "==4.3.1" apscheduler = "==3.6.3" # services +icalendar = "==4.0.7" python-jenkins = "==1.5.0" [dev-packages] diff --git a/src/logic/service/services/IcsService.py b/src/logic/service/services/IcsService.py new file mode 100644 index 0000000000000000000000000000000000000000..70f42a43e56cb0119e255f81fa853d1a59414bd5 --- /dev/null +++ b/src/logic/service/services/IcsService.py @@ -0,0 +1,67 @@ +from dataclasses import dataclass +from datetime import datetime + +from icalendar import Calendar +from typing import Dict + +from TheCodeLabs_BaseUtils.MultiCacheKeyService import MultiCacheKeyService + + +@dataclass +class CalendarEvent: + summary: str = '' + uid: str = '' + description: str = '' + location: str = '' + start: datetime = '' + end: datetime = '' + + +class IcsService(MultiCacheKeyService): + """ + Fetches information from a given ics calendar file. + """ + + EXAMPLE_SETTINGS = { + "path": "path/to/my/calendar.ics" + } + + def _fetch_data(self, settings: Dict) -> Dict: + events = [] + + with open(settings['path'], 'rb') as f: + calendar = Calendar.from_ical(f.read()) + for component in calendar.walk(): + event = CalendarEvent('event') + + if component.get('SUMMARY') is None: + continue + + event.summary = component.get('SUMMARY') + event.uid = component.get('UID') + + if component.get('DESCRIPTION') is None: + continue + + event.description = component.get('DESCRIPTION') + + event.location = component.get('LOCATION') + + if hasattr(component.get('dtstart'), 'dt'): + event.start = component.get('dtstart').dt + + if hasattr(component.get('dtend'), 'dt'): + event.end = component.get('dtend').dt + + event.url = component.get('URL') + events.append(event) + return {'events': events} + + +if __name__ == '__main__': + s = IcsService() + + events = s.get_data('0815', 5, {'path': 'C:/Users/RobertG/Desktop/abfallkalender_2020_richard-wagner-str.ics'})['events'] + for x in events: + # if 'Papier' in x.summary: + print(x.summary) diff --git a/src/logic/tile/tiles/GarbageContainerScheduleTile.html b/src/logic/tile/tiles/GarbageContainerScheduleTile.html new file mode 100644 index 0000000000000000000000000000000000000000..bf77d2fabc53be34df27326b304bb8130ea43764 --- /dev/null +++ b/src/logic/tile/tiles/GarbageContainerScheduleTile.html @@ -0,0 +1,68 @@ +<style> + .garbageContainerScheduleTile { + background-color: #2F2F2F; + font-size: 3.5vmin; + border-radius: 1vh; + font-weight: bold; + flex-direction: row; + } + + .garbageContainerScheduleTile .icon { + font-size: 10vmin; + } + + .garbageContainerScheduleTile .content { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 85%; + height: 85%; + } + + .garbageContainerScheduleTile .entries { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + flex: 1; + } + + .garbageContainerScheduleTile .temperatures { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + } + + .garbageContainerScheduleTile .temperature-entry { + display: flex; + flex-direction: column; + align-items: center; + padding-left: 3vmin; + justify-content: space-between; + } + + .garbageContainerScheduleTile .label { + font-size: 2vmin; + } + + .garbageContainerScheduleTile .wind { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + font-size: 2vmin; + padding-top: 1vmin; + } + + .garbageContainerScheduleTile .windSpeed { + padding-left: 1vmin; + } +</style> + +<div class="garbageContainerScheduleTile"> + <div class="content"> + {{ nextEventDate }} + </div> +</div> diff --git a/src/logic/tile/tiles/GarbageContainerScheduleTile.py b/src/logic/tile/tiles/GarbageContainerScheduleTile.py new file mode 100644 index 0000000000000000000000000000000000000000..5e11a70bda60333609335224c5fa0daa1eef5c04 --- /dev/null +++ b/src/logic/tile/tiles/GarbageContainerScheduleTile.py @@ -0,0 +1,55 @@ +import os +from datetime import datetime +from typing import Dict, List + +from flask import Blueprint + +from logic.service.ServiceManager import ServiceManager +from logic.service.services.IcsService import CalendarEvent +from logic.tile.Tile import Tile + + +class GarbageContainerScheduleTile(Tile): + DATE_FORMAT = '%d.%m. (%a)' + + EXAMPLE_SETTINGS = { + "path": "path/to/my/calendar.ics", + "garbageType": "Papier" or "Gelbe Säcke" or "Bioabfall" or "Restabfall" + } + + def __init__(self, uniqueName: str, settings: Dict, intervalInSeconds: int): + super().__init__(uniqueName, settings, intervalInSeconds) + + def fetch(self, pageName: str) -> Dict: + icsService = ServiceManager.get_instance().get_service_by_type_name('IcsService') + + cacheKey = f'{pageName}_{self._uniqueName}' + events = icsService.get_data(cacheKey, self._intervalInSeconds, self._settings)['events'] + + eventsForGarbageType = [x for x in events if self._settings['garbageType'] in x.summary] + nextEvent = self.__find_next_date(eventsForGarbageType) + + nextEventDate = '--.--.' + if nextEvent: + nextEventDate = datetime.strftime(nextEvent.start, self.DATE_FORMAT) + + # TODO: set locale for weekday + # TODO: set icon for garbageType + + return { + 'nextEventDate': nextEventDate + } + + def __find_next_date(self, events: List[CalendarEvent]) -> CalendarEvent or None: + now = datetime.now().date() + for event in events: + if event.start < now: + continue + return event + return None + + def render(self, data: Dict) -> str: + return Tile.render_template(os.path.dirname(__file__), __class__.__name__, nextEventDate=data['nextEventDate']) + + def construct_blueprint(self, pageName: str, *args, **kwargs): + return Blueprint(f'{pageName}_{__class__.__name__}_{self.get_uniqueName()}', __name__)