--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dreamtime.py Sun Jan 28 11:04:52 2018 +0000 @@ -0,0 +1,212 @@ +# +# Wake/sleep detection for scheduling adjustments +# + +import Foundation +import AppKit +import platform +import time +import threading +import weakref +import datetime +import borgend + +logger=borgend.logger.getChild(__name__) + +_dreamtime_monitor=None + +# +# Support classes for dealing with different times +# + +# Return difference (delay) of "dreamtime" to monotonic time +def dreamtime_difference(): + if _dreamtime_monitor: + return _dreamtime_monitor.diff() + else: + return time.monotonic() + +# Return "dreamtime" +def dreamtime(): + return max(0, time.monotonic()-dreamtime_difference()) + +class Time: + def realtime(self): + raise NotImplementedError + + def monotonic(self): + raise NotImplementedError + + @staticmethod + def _now(): + raise NotImplementedError + + @classmethod + def now(cls): + return cls(cls._now()) + + @classmethod + def from_realtime(cls, realtime): + return cls(realtime-time.time()+cls._now()) + + @classmethod + def from_monotonic(cls, monotonic): + return cls(monotonic-time.monotonic()+cls._now()) + + @classmethod + def after(cls, seconds): + return cls(cls._now()+seconds) + + def datetime(self): + return datetime.datetime.fromtimestamp(self.realtime()) + + def seconds_to(self): + return self._value-self._now() + + def __lt__(self, other): + return self.monotonic() < other.monotonic() + + def __gt__(self, other): + return self.monotonic() > other.monotonic() + + def __le__(self, other): + return self.monotonic() <= other.monotonic() + + def __ge__(self, other): + return self.monotonic() >= other.monotonic() + + def __eq__(self, other): + return self.monotonic() == other.realtime() + +class RealTime(Time): + def __init__(self, when): + self._value=when + + def realtime(self): + return self._value + + def monotonic(self): + return self._value+(time.monotonic()-time.time()) + + @staticmethod + def _now(): + return time.time() + +class MonotonicTime(Time): + def __init__(self, when): + self._value=when + + def realtime(self): + return self._value+(time.time()-time.monotonic()) + + def monotonic(self): + return self._value + + @staticmethod + def _now(): + return time.monotonic() + +class DreamTime(Time): + def __init__(self, when): + self._value=when + + def realtime(self): + return self._value+(time.time()-dreamtime()) + + # Important: monotonic is "static" within a wakeup period + # and does not need to call time.monotonic(), as it gets compared + # to a specific time.monotonic() realisation + def monotonic(self): + return self._value+dreamtime_difference() + + @staticmethod + def _now(): + return dreamtime() + +# +# Wake up / sleep handling +# + +class SleepHandler(Foundation.NSObject): + """ Handle wake/sleep notifications """ + + def init(self): + self.__sleeptime=None + self.__slept=0 + self.__epoch=time.monotonic() + self.__lock=threading.Lock() + self.__callbacks=weakref.WeakKeyDictionary() + + return self + + def handleSleepNotification_(self, aNotification): + logger.info("System going to sleep") + now=time.monotonic() + with self.__lock: + self.__sleeptime=now + + def handleWakeNotification_(self, aNotification): + logger.info("System waking up from sleep") + try: + now=time.monotonic() + with self.__lock: + if self.__sleeptime: + slept=max(0, now-self.__sleeptime) + logger.info("Slept %f seconds" % slept) + self.__slept=self.__slept+slept + self.__sleeptime=None + callbacks=self.__callbacks.copy() + except: + logger.exception("Bug in wakeup handler") + + for callback in callbacks.values(): + try: + callback() + except Exception: + logger.exception("Error in wake notification callback") + + # Return difference to time.monotonic() + def diff(self): + with self.__lock: + diff=self.__epoch+self.__slept + return diff + + # Weirdo (Py)ObjC naming to stop it form choking up + def addForObj_aCallback_(self, obj, callback): + with self.__lock: + self.__callbacks[obj]=callback + +# obj is to use a a key in a weak key dictionary +def add_callback(obj, callback): + global _dreamtime_monitor + + monitor=_dreamtime_monitor + if not monitor: + raise Exception("Dreamtime monitor not started") + else: + monitor.addForObj_aCallback_(obj, callback) + +def start_monitoring(): + global _dreamtime_monitor + + if platform.system()=='Darwin': + logger.debug("Starting to monitor system sleep") + workspace = AppKit.NSWorkspace.sharedWorkspace() + notification_center = workspace.notificationCenter() + _dreamtime_monitor = SleepHandler.new() + + notification_center.addObserver_selector_name_object_( + _dreamtime_monitor, + "handleSleepNotification:", + AppKit.NSWorkspaceWillSleepNotification, + None) + + notification_center.addObserver_selector_name_object_( + _dreamtime_monitor, + "handleWakeNotification:", + AppKit.NSWorkspaceDidWakeNotification, + None) + else: + logger.warning(("No system sleep monitor implemented for '%s'" + % platform.system())) +