borgend/dreamtime.py

changeset 80
a409242121d5
parent 79
b075b3db3044
child 86
2fe66644c50d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/borgend/dreamtime.py	Sun Jan 28 11:54:46 2018 +0000
@@ -0,0 +1,213 @@
+#
+# 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()))
+

mercurial