From eaa521a3b195fa4a1d5ddf7a2ff90b3a3c69a888 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Tue, 3 Nov 2020 21:07:52 +0100
Subject: [PATCH] #30 - added new tile "GarbageContainerSchedule"

---
 Pipfile                                       |  1 +
 src/logic/service/services/IcsService.py      | 67 ++++++++++++++++++
 .../tiles/GarbageContainerScheduleTile.html   | 68 +++++++++++++++++++
 .../tiles/GarbageContainerScheduleTile.py     | 55 +++++++++++++++
 4 files changed, 191 insertions(+)
 create mode 100644 src/logic/service/services/IcsService.py
 create mode 100644 src/logic/tile/tiles/GarbageContainerScheduleTile.html
 create mode 100644 src/logic/tile/tiles/GarbageContainerScheduleTile.py

diff --git a/Pipfile b/Pipfile
index 29084d5..4d2b478 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 0000000..70f42a4
--- /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 0000000..bf77d2f
--- /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 0000000..5e11a70
--- /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__)
-- 
GitLab