forked from tsc-vfl/hugo-page
		
	WIP Synchronization of local yaml with upstream server automation
This commit is contained in:
		
							parent
							
								
									2614357a12
								
							
						
					
					
						commit
						9148e1f469
					
				
							
								
								
									
										46
									
								
								scripts/nc-cal-sync/calendar_synchronizer/cal_helper.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								scripts/nc-cal-sync/calendar_synchronizer/cal_helper.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| import datetime as dt | ||||
| import re, logging | ||||
| 
 | ||||
| from .sync import Event | ||||
| 
 | ||||
| class IcalHelper: | ||||
|      | ||||
|     _WEEKDAY_MAP = { | ||||
|         'Mon': 0, | ||||
|         'Tue': 1, | ||||
|         'Wed': 2, | ||||
|         'Thu': 3, | ||||
|         'Fri': 4, | ||||
|         'Sat': 5, | ||||
|         'Sun': 6 | ||||
|     } | ||||
|      | ||||
|     def __init__(self): | ||||
|         self._l = logging.getLogger(__name__) | ||||
|         self.dDay = dt.timedelta(days=1) | ||||
|         self.firstDay = dt.datetime(year=dt.datetime.now().year, month=1, day=1) | ||||
|         self.reTime = re.compile(r'(\d{1,2}):(\d{2})') | ||||
|      | ||||
|     def getStart(self, event: Event, holidays): | ||||
|         self._getFirstWeekdayInYear(event.day) | ||||
|         self._getFirstOccurence(event, holidays) | ||||
|         pass | ||||
| 
 | ||||
|     def _getFirstWeekdayInYear(self, weekday): | ||||
|         candidate = self.firstDay | ||||
|         while candidate.weekday() != self._WEEKDAY_MAP[weekday]: | ||||
|             candidate += self.dDay | ||||
|          | ||||
|         self._l.log(5, 'First %s in year is %s', weekday, candidate) | ||||
| 
 | ||||
|         return candidate | ||||
|      | ||||
|     def _getSortedHolidays(self, holidays): | ||||
|         return sorted(holidays['holidays'], key=lambda h: h['from']) | ||||
| 
 | ||||
|     def _getSortedFeasts(self, holidays): | ||||
|         return sorted(holidays['feasts']) | ||||
| 
 | ||||
|     def _getFirstOccurence(self, event: Event, holidays): | ||||
|         firstWeekday = self._getFirstWeekdayInYear(event.day) | ||||
|         firstWeekday.tzinfo = dt.timezone.tzname('Europe/Berlin') | ||||
| @ -1,22 +1,281 @@ | ||||
| import logging, json | ||||
| import logging, yaml, pprint, json, re | ||||
| import hashlib | ||||
| import caldav | ||||
| import datetime as dt | ||||
| 
 | ||||
| from . import login | ||||
| 
 | ||||
| _l = logging.getLogger(__name__) | ||||
| 
 | ||||
| def buildSubparser(subparser): | ||||
|     subparser.add_argument('--schedule', default='../../data/schedule.json') | ||||
|     subparser.add_argument('--holidays', default='../../data/holidays.json') | ||||
|     subparser.add_argument('--schedule', default='../../data/schedule.yaml') | ||||
|     subparser.add_argument('--holidays', default='../../data/holidays.yaml') | ||||
|     subparser.add_argument('-n', '--dry', action='store_true') | ||||
|     subparser.add_argument('--recreate-all', action='store_true') | ||||
| 
 | ||||
| def run(args): | ||||
|     _l.info('Loading data from hard disc') | ||||
|     loginData, schedule, holidays = _loadRawData(args) | ||||
|     packedSchedule = _unpackSchedules(schedule) | ||||
|     fixedSchedule = _addHolidayExceptions(packedSchedule, holidays) | ||||
|     _synchonizeCalendars(args, fixedSchedule, loginData, holidays) | ||||
|      | ||||
| def _loadRawData(args): | ||||
|     _l.info('Loading data from hard disk') | ||||
|     loginData = login.loadLoginData() | ||||
| 
 | ||||
|     with open(args.schedule, 'r') as f: | ||||
|         schedule = json.load(f) | ||||
|         schedule = yaml.safe_load(f.read()) | ||||
|      | ||||
|     _l.log(5, 'Schedule data:\n%s', pprint.pformat(schedule, indent=4, width=100)) | ||||
| 
 | ||||
|     with open(args.holidays, 'r') as f: | ||||
|         holidays = json.load(f) | ||||
|         holidays = yaml.safe_load(f.read()) | ||||
|      | ||||
|     _l.info('Data was read from hard disc') | ||||
|     _l.log(5, 'Holidays data:\n%s', pprint.pformat(holidays, indent=4, width=100)) | ||||
| 
 | ||||
|     _l.info('Data was read from hard disk') | ||||
|      | ||||
|     return loginData, schedule, holidays | ||||
| 
 | ||||
| class Event: | ||||
|     def __init__(self, day, start, duration, title): | ||||
|         self.day = day | ||||
|         self.start = start | ||||
|         self.duration = duration | ||||
|         self.title = title | ||||
|         self.age = None | ||||
|         self.external = False | ||||
|         self.description = '' | ||||
|         self.exceptions = [] | ||||
|      | ||||
|     def __repr__(self): | ||||
|         wAge = f' ({self.age})' if self.age is not None else '' | ||||
|         return f'Ev({self.title}{wAge}) [{self.day}, {self.start}, {self.duration}]' | ||||
|      | ||||
|     def getHash(self, holidays): | ||||
|         def fixHolidays(holidays): | ||||
|             def fixDate(d): | ||||
|                 return d.strftime('%Y-%m-%d') | ||||
|              | ||||
|             def fixFeast(f): | ||||
|                 return fixDate(f) | ||||
|              | ||||
|             def fixHoliday(h): | ||||
|                 return { | ||||
|                     'from': fixDate(h['from']), | ||||
|                     'to': fixDate(h['to']), | ||||
|                 } | ||||
| 
 | ||||
|             return { | ||||
|                 **holidays, | ||||
|                 'feasts': [fixFeast(f) for f in holidays['feasts']], | ||||
|                 'holidays': [fixHoliday(h) for h in holidays['holidays']], | ||||
|             } | ||||
| 
 | ||||
|         data = { | ||||
|             'day': self.day, | ||||
|             'start': self.start, | ||||
|             'duration': self.duration, | ||||
|             'title': self.title, | ||||
|             'age': self.age, | ||||
|             'external': self.external, | ||||
|             'description': self.description, | ||||
|             'exceptions': self.exceptions, | ||||
|             "holidays": fixHolidays(holidays), | ||||
|         } | ||||
|         # pprint.pprint(data, indent=4, width=100) | ||||
|         hasher = hashlib.sha1() | ||||
|         hasher.update(json.dumps(data, sort_keys=True).encode('utf-8')) | ||||
|         return hasher.hexdigest() | ||||
|      | ||||
|     dDay = dt.timedelta(days=1) | ||||
| 
 | ||||
|     def addToCalendar(self, calendar: caldav.Calendar, holidays): | ||||
|         from . import cal_helper | ||||
| 
 | ||||
|         def getFirstTimeInYear(): | ||||
|             now = dt.datetime.now() | ||||
|             d = dt.datetime(year=now.year, month=1, day=1) | ||||
|             pass | ||||
| 
 | ||||
|         helper = cal_helper.IcalHelper() | ||||
|         helper.getStart(self, holidays) | ||||
|          | ||||
|         icalData = { | ||||
|             'uid': self.getHash(holidays), | ||||
|             'DTSTART': dt.datetime.now(), | ||||
|             'DTEND': dt.datetime.now() + dt.timedelta(minutes=self.duration), | ||||
|             'SUMMARY': self.title | ||||
|         } | ||||
|         # calendar.add_event(**icalData) | ||||
| 
 | ||||
| def _unpackSchedules(schedule): | ||||
|     def packSingleCalendar(cal): | ||||
|         def parseSingleEvent(ev): | ||||
|             e = Event( | ||||
|                 day=ev['day'], | ||||
|                 start=ev['start'], | ||||
|                 duration=ev['duration'], | ||||
|                 title=ev['title'] | ||||
|             ) | ||||
| 
 | ||||
|             if 'age' in ev: | ||||
|                 e.age = ev['age'] | ||||
|             if ev.get('extern', False): | ||||
|                 e.external = True | ||||
|             if 'desc' in ev: | ||||
|                 e.description = ev['desc'] | ||||
|             if 'exceptions' in ev: | ||||
|                 raise Exception('Not yet implemented to have exceptions') | ||||
|              | ||||
|             return e | ||||
| 
 | ||||
|         _l.log(5, 'Unpacking calendar %s', cal) | ||||
|         ret = { **cal } | ||||
| 
 | ||||
|         if 'schedule' in ret: | ||||
|             ret['schedule'] = [ | ||||
|                 parseSingleEvent(e) for e in ret['schedule'] | ||||
|             ] | ||||
| 
 | ||||
|         _l.log(5, 'Unpacked calendar %s', ret) | ||||
|         return ret | ||||
| 
 | ||||
|     ret = {} | ||||
| 
 | ||||
|     for calName in schedule['calendars']: | ||||
|         if schedule['calendars'][calName].get('ignore', False): | ||||
|             _l.info('Ignoring calendar %s', calName) | ||||
|             continue | ||||
| 
 | ||||
|         ret[calName] = packSingleCalendar(schedule['calendars'][calName]) | ||||
|      | ||||
|     _l.log(5, 'Unpacked schedule:\n%s', pprint.pformat(ret, indent=4, width=100)) | ||||
|     return ret | ||||
| 
 | ||||
| def _addHolidayExceptions(schedule, holidays): | ||||
|     return schedule | ||||
| 
 | ||||
| class CalendarSynchonizer: | ||||
|     def __init__(self, args, calId, calName, loginData): | ||||
|         self.args = args | ||||
|         self.calId = calId | ||||
|         self.calName = calName | ||||
|         self.loginData = loginData | ||||
|         self._calDav = None | ||||
|      | ||||
|     def _getUrl(self): | ||||
|         return f'{self.loginData.base}/remote.php/dav/calendars' | ||||
| 
 | ||||
|     def _getCalDav(self): | ||||
|          | ||||
|         if self._calDav is None: | ||||
|             self._calDav = caldav.DAVClient( | ||||
|                 url=self._getUrl(), | ||||
|                 username=self.loginData.loginName, | ||||
|                 password=self.loginData.appPassword | ||||
|             ) | ||||
|         return self._calDav | ||||
|      | ||||
|     def _getCalendar(self): | ||||
|         cd = self._getCalDav() | ||||
|         return cd.principal().calendar(cal_id=self.calId) | ||||
| 
 | ||||
|     def synchonize(self, calendars, holidays): | ||||
|         downstreamEvents = self._getDownstreamEvents(calendars[self.calName]['schedule'], holidays) | ||||
|         _l.debug('Downstream events:\n%s', pprint.pformat(downstreamEvents, indent=4, width=100)) | ||||
| 
 | ||||
|         upstreamEvents = self._getUpstreamEvents() | ||||
|         _l.debug('Upstream events:\n%s', pprint.pformat(upstreamEvents, indent=4, width=100)) | ||||
| 
 | ||||
|         newEvents = self._getNewEvents(upstreamEvents, downstreamEvents) | ||||
|         _l.log(5, 'New events:\n%s', pprint.pformat(newEvents, indent=4, width=100)) | ||||
|         deletedEvents = self._getDeletedEvents(upstreamEvents, downstreamEvents) | ||||
|         _l.log(5, 'Events marked for deletion:\n%s', pprint.pformat(deletedEvents, indent=4, width=100)) | ||||
| 
 | ||||
|         self._deleteEvents(deletedEvents) | ||||
|         self._addEvents(newEvents, holidays) | ||||
| 
 | ||||
|     def _getUpstreamEvents(self): | ||||
|         _l.debug('Fetching upstream calendar entries') | ||||
|         cd = self._getCalDav() | ||||
|          | ||||
|         # principal = cd.principal() | ||||
|         # _l.log(5, 'Principal %s', principal) | ||||
| 
 | ||||
|         # calendar = principal.calendar(cal_id=self.calId) | ||||
|         # _l.log(5, 'Calendar %s', calendar) | ||||
| 
 | ||||
|         calendar = self._getCalendar() | ||||
| 
 | ||||
|         # children = calendar.children() | ||||
|         # _l.debug('Getting children for calendar %s:\n%s', self.calId, children) | ||||
| 
 | ||||
|         events = calendar.events() | ||||
| 
 | ||||
|         ret = {} | ||||
| 
 | ||||
|         for e in events: | ||||
|             uid = str(e.icalendar_component['uid']) | ||||
|             _l.log(5, 'Event with uid %s was found.', uid) | ||||
|             ret[uid] = e | ||||
| 
 | ||||
|         return ret | ||||
|      | ||||
|     def _getDownstreamEvents(self, schedule, holidays): | ||||
|         _l.debug('Preparing local calendar events') | ||||
|          | ||||
|         ret = {} | ||||
| 
 | ||||
|         for e in schedule: | ||||
|             uid = e.getHash(holidays) | ||||
|             _l.log(5, 'Event with uid %s was found.', uid) | ||||
|             ret[uid] = e | ||||
| 
 | ||||
|         return ret | ||||
|      | ||||
|     def _getNewEvents(self, upstreamEvents, downstreamEvents): | ||||
|         if self.args.recreate_all: | ||||
|             return downstreamEvents.values() | ||||
|          | ||||
|         upstreamUids = set(upstreamEvents.keys()) | ||||
|         downstreamUids = set(downstreamEvents.keys()) | ||||
|         newUids = downstreamUids - upstreamUids | ||||
| 
 | ||||
|         return [downstreamEvents[uid] for uid in newUids] | ||||
|      | ||||
|     def _getDeletedEvents(self, upstreamEvents, downstreamEvents): | ||||
|         if self.args.recreate_all: | ||||
|             return upstreamEvents.values() | ||||
|          | ||||
|         upstreamUids = set(upstreamEvents.keys()) | ||||
|         downstreamUids = set(downstreamEvents.keys()) | ||||
|         toDeleteUids = upstreamUids - downstreamUids | ||||
| 
 | ||||
|         return [upstreamEvents[uid] for uid in toDeleteUids] | ||||
| 
 | ||||
|     def _addEvents(self, newEvents, holidays): | ||||
|         cal = self._getCalendar() | ||||
| 
 | ||||
|         for e in newEvents: | ||||
|             _l.debug('Adding event %s', e) | ||||
|             if self.args.dry: | ||||
|                 _l.info('Skipping add event %s (dry mode)', e) | ||||
|             else: | ||||
|                 # e.save() | ||||
|                 e.addToCalendar(cal, holidays) | ||||
|      | ||||
|     def _deleteEvents(self, oldEvents): | ||||
|         for e in oldEvents: | ||||
|             _l.debug('Deleting event %s', e) | ||||
|             if self.args.dry: | ||||
|                 _l.info('Skipping delete event %s (dry mode)', e) | ||||
|             else: | ||||
|                 e.delete() | ||||
| 
 | ||||
| def _synchonizeCalendars(args, calendars, loginData, holidays): | ||||
|     for calName in calendars: | ||||
|         calendar = calendars[calName] | ||||
|         _l.info('Synching calendar %s', calName) | ||||
|         calendarSynchonizer = CalendarSynchonizer(args, calendar['id'], calName, loginData) | ||||
|         calendarSynchonizer.synchonize(calendars, holidays) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user