implement setup.py

This commit is contained in:
2022-12-16 23:40:58 +01:00
parent edaa745177
commit 72d780e55c
11 changed files with 132 additions and 5 deletions

View File

View File

@@ -0,0 +1,75 @@
import caldav
import datetime
import logging
import re
import vobject
class CalendarSync(object):
log = logging.getLogger("CalendarSync")
startTime = "06:00"
def __init__(self):
self._calendar = None
self._client = None
def connect(self, url, user, passwd):
self._client = caldav.DAVClient(url=url, username=user, password=passwd)
@property
def is_connected(self) -> bool:
return self._client is not None
def getExistingMuellEvents(self) -> list[caldav.Event]:
events = self._calendar.events()
ret = []
for e in events:
try:
uid = e.vobject_instance.vevent.uid.value
m = re.match(r'(.+)@MyMuell', uid)
if m is not None:
ret.append(e)
except AttributeError:
continue
return ret
def createEvent(self, uid, summary, date):
date_start = datetime.datetime.combine(date, datetime.time.fromisoformat(CalendarSync.startTime))
date_end = date_start + datetime.timedelta(minutes=5)
cal = vobject.iCalendar()
ev = cal.add('vevent')
ev.add("summary").value = summary
ev.add("dtstart").value = date_start
ev.add("dtend").value = date_end
ev.add("uid").value = str(uid) + "@MyMuell"
ev.add("valarm").add("trigger").value = datetime.timedelta(hours=-12)
ev.add("valarm").add("trigger").value = datetime.timedelta(hours=0)
self._calendar.save_event(cal.serialize())
def syncEvents(self, e):
self.createEvent(
e["id"],
e["title"],
datetime.datetime.strptime(e["day"], "%Y-%m-%d"))
def getCalendars(self):
principal = self._client.principal()
cals = principal.calendars()
ret = []
for c in cals:
ret.append(c.name)
return ret
def createCalendar(self, cal):
principal = self._client.principal()
cals = principal.calendars()
for c in cals:
if c.name == cal:
self._calendar = c
if self._calendar is None:
CalendarSync.log.info("creating new calendar \"{}\"".format(cal))
self._calendar = principal.make_calendar(name=cal)

View File

@@ -0,0 +1,75 @@
from appdirs import *
import json
import os
import re
import copy
from cryptography.fernet import Fernet
class LocalDataStorage(object):
def __init__(self):
self.appname = "MyMuellDav"
self.appauthor = "Av3m"
self.__fernet = Fernet(b'kWUFurHmtMWX6nOMhpFR45DpuNVPckSQ9t95_ADG2dA=')
if not os.path.exists(self.user_data_dir):
os.makedirs(self.user_data_dir)
DefaultSettings = {
'url': '',
'user': '',
'password': '',
'calendar': '',
'mymuellcity': '',
}
@property
def user_data_dir(self):
return user_data_dir(self.appname, self.appauthor)
@property
def file_settings(self):
return os.path.join(self.user_data_dir, "settings.json")
@property
def file_city_data(self):
return os.path.join(self.user_data_dir, "city_data.json")
@property
def settings(self):
if os.path.exists(self.file_settings):
with open(self.file_settings, "r") as f:
j = json.load(f)
j["password"] = str(self.__fernet.decrypt(bytes(j["password"], encoding="utf-8")), encoding="utf-8")
return j
else:
return LocalDataStorage.DefaultSettings
@settings.setter
def settings(self, val):
if val is None and os.path.exists(self.file_settings):
os.remove(self.file_settings)
return
with open(self.file_settings, "w+") as f:
v = copy.copy(val)
v["password"] = str(self.__fernet.encrypt(bytes(v["password"], encoding="utf-8")), encoding="utf-8")
json.dump(v, f)
os.chmod(self.file_settings, 0o0600)
@property
def city_data(self):
if os.path.exists(self.file_city_data):
with open(self.file_city_data, "r") as f:
return json.load(f)
else:
return None
@city_data.setter
def city_data(self, val):
if val is None and os.path.exists(self.file_city_data):
os.remove(self.file_city_data)
return
with open(self.file_city_data, "w+") as f:
json.dump(val, f)

View File

@@ -0,0 +1,77 @@
import http.client
import json
import mymuell2caldav.databinding.LocalDataStorage as LocalDataStorage
import logging
import re
class MyMuellDataModel(object):
MyMuellHost = 'mymuell.jumomind.com'
log = logging.getLogger("MyMuellDataModel")
def __init__(self):
self.client = http.client.HTTPSConnection(MyMuellDataModel.MyMuellHost)
self.storage = LocalDataStorage.LocalDataStorage()
self.cities = self.__get_cities()
def get_cities_by_request(self):
self.client.request('GET', '/mmapp/loxone/lox.php?r=cities')
response = self.client.getresponse()
return json.loads(str(response.read(), encoding='utf-8'))
def get_events(self, city_id, area_id):
self.client.request('GET', '/mmapp/loxone/lox.php?r=dates/0&city_id={city_id}&area_id={area_id}'.format(city_id=city_id, area_id=area_id))
response = self.client.getresponse()
ret = str(response.read(), encoding='utf-8')
return json.loads(ret)
def __get_cities(self):
cities = self.storage.city_data
if cities is not None:
MyMuellDataModel.log.debug("using stored values")
return cities
else:
cities = self.get_cities_by_request()
self.storage.city_data = cities
return cities
def get_city_names(self, indices):
ret = []
for idx in indices:
e = self.get_city_by_index(idx)
if e is not None:
ret.append(e["name"])
return ret
def match_city(self, pattern):
ret = []
n = 0
for i in self.cities:
m = re.search(pattern, i["name"], re.IGNORECASE)
if m is not None:
ret.append(n)
n = n + 1
return ret
def get_city_by_index(self, idx):
if len(self.cities) < idx:
return None
return self.cities[idx]
def get_city_by_id(self, id):
for i in self.cities:
if i["id"] == id:
return i
return None
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
model = MyMuellDataModel()
matches = model.match_city("eich")

View File

View File

@@ -0,0 +1,301 @@
from PyQt5.QtWidgets import \
QApplication, \
QWidget, \
QListWidget, \
QListWidgetItem, \
QVBoxLayout, \
QHBoxLayout, \
QGridLayout, \
QLineEdit, \
QGroupBox, \
QLabel, \
QPushButton, \
QMessageBox, \
QComboBox, \
QProgressBar, \
QStatusBar, \
QSizePolicy, \
QMainWindow
from PyQt5.QtCore import Qt
import sys
from mymuell2caldav.databinding.CalendarSync import CalendarSync
from mymuell2caldav.databinding.MyMuellDataModel import MyMuellDataModel
from mymuell2caldav.utils.GuiWorker import GuiWorker
class MyMuell2CalDavGui(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._dataModel = MyMuellDataModel()
self._davClient = CalendarSync()
self._selectedCity = None
self._cities = []
self._citiesWidget = QListWidget()
self._filterText = QLineEdit()
self._url = QLineEdit()
self._user = QLineEdit()
self._password = QLineEdit()
self._calendarNames = QComboBox()
self._connectButton = QPushButton("connect")
self._syncButton = QPushButton("sync events")
self._deleteButton = QPushButton("delete existing events")
self._errorMessage = QMessageBox()
self._progressBar = QProgressBar()
self._statusBar = QStatusBar()
self._settings = self._dataModel.storage.settings
self._url.setText(self._settings["url"])
self._password.setText(self._settings["password"])
self._user.setText(self._settings["user"])
self._workerConnect = GuiWorker(self.runnable_connect_caldav)
self._workerSync = GuiWorker(self.runnable_sync_events)
self._workerDelete = GuiWorker(self.runnable_delete_events)
self.__init_ui()
self.__fill_cities()
def entrySelected(self, i: QListWidgetItem):
city = self._dataModel.get_city_by_id(i.data(QListWidgetItem.UserType))
self._selectedCity = (city["id"], city["area_id"])
def saveSettings(self, val):
self._settings["url"] = self._url.text()
self._settings["user"] = self._user.text()
self._settings["password"] = self._password.text()
if self._calendarNames.currentText() != '':
self._settings["calendar"] = self._calendarNames.currentText()
if len(self._citiesWidget.selectedItems()) > 0:
self._settings["mymuellcity"] = self._citiesWidget.selectedItems()[0].text()
self._dataModel.storage.settings = self._settings
def runnable_sync_events(self, worker: GuiWorker) -> tuple[bool, str]:
if self._selectedCity is None:
return False, "please select a city"
if self._calendarNames.currentText() == '':
return False, "please select a calendar"
worker.stateChanged.emit("create calendar {} if not existent".format(self._calendarNames.currentText()))
self._davClient.createCalendar(self._calendarNames.currentText())
worker.stateChanged.emit("get events from MyMüll.de (city id {}, aread id {}".format(*self._selectedCity))
events = self._dataModel.get_events(*self._selectedCity)
worker.rangeChanged.emit(0, len(events))
worker.progressChanged.emit(0)
for i in range(0, len(events)):
worker.stateChanged.emit("creating event {} {}".format(events[i]["title"], events[i]["day"]))
self._davClient.syncEvents(events[i])
worker.progressChanged.emit(i + 1)
worker.stateChanged.emit("syncing events finished")
return True, "syncing events finished"
def runnable_delete_events(self, worker: GuiWorker) -> tuple[bool, str]:
if self._calendarNames.currentText() == '':
return False, "please select a calendar"
worker.stateChanged.emit("deleting existing events from calendar")
self._davClient.createCalendar(self._calendarNames.currentText())
events = self._davClient.getExistingMuellEvents()
if len(events) > 0:
worker.rangeChanged.emit(0, len(events))
worker.progressChanged.emit(0)
else:
worker.rangeChanged.emit(0, 1)
worker.progressChanged.emit(1)
for i in range(0, len(events)):
worker.stateChanged.emit("deleting event {}".format(events[i].vobject_instance.vevent.uid.value))
events[i].delete()
worker.progressChanged.emit(i + 1)
worker.stateChanged.emit("deletion finished.")
return True, "deleting events finished"
def runnable_connect_caldav(self, worker: GuiWorker) -> tuple[bool, str]:
try:
worker.stateChanged.emit("connecting to {}".format(self._url.text()))
self._davClient.connect(self._url.text(), self._user.text(), self._password.text())
self._calendarNames.blockSignals(True)
for i in self._davClient.getCalendars():
self._calendarNames.addItem(i)
self._calendarNames.blockSignals(False)
self._calendarNames.setEnabled(True)
if self._settings["calendar"] != '':
self._calendarNames.setCurrentText(self._settings["calendar"])
worker.stateChanged.emit("connected.")
except Exception as e:
worker.stateChanged.emit("connection failed.")
return False, str(e)
return True, "connect successful"
def __init_ui(self):
tlWidget = QWidget()
layout = QGridLayout()
tlWidget.setLayout(layout)
self.setCentralWidget(tlWidget)
groupBoxMyMuell = QGroupBox("MyMüll.de Cities")
layoutGroupBoxMyMuell = QGridLayout()
groupBoxMyMuell.setLayout(layoutGroupBoxMyMuell)
groupBoxCalDav = QGroupBox("CalDAV Settings")
layoutGroupBoxCalDav = QGridLayout()
groupBoxCalDav.setLayout(layoutGroupBoxCalDav)
groupBoxProgress = QGroupBox("Progress")
layoutGroupBoxProgress = QVBoxLayout()
groupBoxProgress.setLayout(layoutGroupBoxProgress)
layoutGroupBoxMyMuell.addWidget(self._citiesWidget, 0, 0, 4, 6)
layoutGroupBoxMyMuell.addWidget(QLabel("Filter Cities"), 4, 0, 1, 1)
layoutGroupBoxMyMuell.addWidget(self._filterText, 4, 1, 1, 5)
layout.addWidget(groupBoxMyMuell)
layout.addWidget(groupBoxCalDav)
layout.addWidget(groupBoxProgress)
self.setStatusBar(self._statusBar)
layoutGroupBoxCalDav.addWidget(QLabel("url"), 0, 0, 1, 1)
layoutGroupBoxCalDav.addWidget(self._url, 0, 1, 1, 5)
layoutGroupBoxCalDav.addWidget(QLabel("username"), 1, 0, 1, 1)
layoutGroupBoxCalDav.addWidget(self._user, 1, 1, 1, 5)
layoutGroupBoxCalDav.addWidget(QLabel("password"), 2, 0, 1, 1)
layoutGroupBoxCalDav.addWidget(self._password, 2, 1, 1, 5)
layoutGroupBoxCalDav.addWidget(QLabel("calendar"), 3, 0, 1, 1)
layoutGroupBoxCalDav.addWidget(self._calendarNames, 3, 1, 1, 5)
buttonLayout = QHBoxLayout()
buttonLayout.addWidget(self._connectButton)
buttonLayout.addWidget(self._syncButton)
buttonLayout.addWidget(self._deleteButton)
layoutGroupBoxCalDav.addLayout(buttonLayout, 4, 0, 6, 6)
layoutGroupBoxProgress.addWidget(self._progressBar)
layoutGroupBoxCalDav.setSpacing(0)
layoutGroupBoxCalDav.setContentsMargins(0, 0, 0, 0)
self._password.setEchoMode(QLineEdit.Password)
self._calendarNames.setEditable(True)
self._filterText.show()
self._citiesWidget.show()
self._syncButton.setEnabled(False)
self._calendarNames.setEnabled(False)
self._citiesWidget.currentItemChanged.connect(lambda cur, prev: self.entrySelected(cur))
self._filterText.textChanged.connect(self.__fill_cities)
self._url.textChanged.connect(self.saveSettings)
self._user.textChanged.connect(self.saveSettings)
self._password.textChanged.connect(self.saveSettings)
self._citiesWidget.itemSelectionChanged.connect(lambda: self.saveSettings(0))
self._workerSync.connectProgressBar(self._progressBar)
self._workerSync.connectButton(self._syncButton)
self._workerConnect.connectButton(self._connectButton)
self._workerDelete.connectButton(self._deleteButton)
self._workerSync.connectStatusBar(self._statusBar)
self._workerConnect.connectStatusBar(self._statusBar)
self._workerDelete.connectStatusBar(self._statusBar)
self._workerDelete.connectProgressBar(self._progressBar)
self._workerConnect.finished.connect(self.slot_process_finished)
self._workerSync.finished.connect(self.slot_process_finished)
self._workerDelete.finished.connect(self.slot_process_finished)
self._calendarNames.currentTextChanged.connect(self.slot_calendar_selected)
self._progressBar.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
groupBoxProgress.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
groupBoxCalDav.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
self._url.setContentsMargins(0, 10, 0, 10)
self._password.setContentsMargins(0, 10, 0, 10)
self._user.setContentsMargins(0, 10, 0, 10)
self._calendarNames.setContentsMargins(0, 10, 0, 10)
# self.setGeometry(300, 300, 1000, 800)
self.setMinimumWidth(500)
self.setWindowTitle("MyMuell DAV GUI")
def slot_process_finished(self, result: bool, msg: str):
if result:
self._errorMessage.information(self, "info", msg)
self._syncButton.setEnabled(True)
else:
self._errorMessage.critical(self, "error", msg)
self._syncButton.setEnabled(False)
def slot_calendar_selected(self):
if self._calendarNames.currentText() != '' and self._davClient.is_connected:
self.saveSettings(0)
self._syncButton.setEnabled(True)
else:
self._syncButton.setEnabled(False)
def __fill_cities(self, pattern=".+"):
self._citiesWidget.blockSignals(True)
self._citiesWidget.clear()
self._cities = self._dataModel.match_city(pattern)
for i in self._cities:
c = self._dataModel.get_city_by_index(i)
item = QListWidgetItem()
item.setData(QListWidgetItem.UserType, c["id"])
item.setText(c["name"])
self._citiesWidget.addItem(item)
self._citiesWidget.blockSignals(False)
if self._settings["mymuellcity"] != '':
items = self._citiesWidget.findItems(self._settings["mymuellcity"], Qt.MatchExactly)
if len(items) > 0:
self._citiesWidget.setCurrentItem(items[0])
def main():
app = QApplication(sys.argv)
w = MyMuell2CalDavGui()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,35 @@
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QProgressBar, QPushButton, QStatusBar
class GuiWorker(QThread):
finished = pyqtSignal(bool, str)
rangeChanged = pyqtSignal(int, int)
progressChanged = pyqtSignal(int)
stateChanged = pyqtSignal(str)
_button = None
def __init__(self, runnable, parent=None):
QThread.__init__(self, parent)
self._callable = runnable
def connectProgressBar(self, progress_bar: QProgressBar):
self.progressChanged.connect(progress_bar.setValue)
self.rangeChanged.connect(progress_bar.setRange)
progress_bar.setTextVisible(True)
def connectButton(self, button: QPushButton):
self._button = button
self._button.clicked.connect(self.start)
def connectStatusBar(self, statusbar: QStatusBar):
self.stateChanged.connect(lambda val: statusbar.showMessage(val))
def run(self):
if self._button:
self._button.setEnabled(False)
ret = self._callable(self)
self._button.setEnabled(True)
self.finished.emit(ret[0], ret[1])

View File