Sun, 28 Jan 2018 11:54:46 +0000
Better package-like organisation
# # Wake/sleep detection for scheduling adjustments # import Foundation import AppKit import platform import time import threading import weakref import datetime from . import loggers logger=loggers.get(__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()))