borgend/dreamtime.py

Mon, 29 Jan 2018 09:38:53 +0000

author
Tuomo Valkonen <tuomov@iki.fi>
date
Mon, 29 Jan 2018 09:38:53 +0000
changeset 89
51cc2e25af38
parent 86
2fe66644c50d
child 94
2a11fd29c192
permissions
-rw-r--r--

Added author information headers and content information to source files

#
# Borgend by Tuomo Valkonen, 2018
#
# This file implements system wake/sleep detection for scheduling adjustments.
#

import Foundation
import AppKit
import platform
import time
import threading
import weakref
import datetime
import logging

logger=logging.getLogger(__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