borgend/dreamtime.py

changeset 80
a409242121d5
parent 79
b075b3db3044
child 86
2fe66644c50d
equal deleted inserted replaced
79:b075b3db3044 80:a409242121d5
1 #
2 # Wake/sleep detection for scheduling adjustments
3 #
4
5 import Foundation
6 import AppKit
7 import platform
8 import time
9 import threading
10 import weakref
11 import datetime
12
13 from . import loggers
14
15 logger=loggers.get(__name__)
16
17 _dreamtime_monitor=None
18
19 #
20 # Support classes for dealing with different times
21 #
22
23 # Return difference (delay) of "dreamtime" to monotonic time
24 def dreamtime_difference():
25 if _dreamtime_monitor:
26 return _dreamtime_monitor.diff()
27 else:
28 return time.monotonic()
29
30 # Return "dreamtime"
31 def dreamtime():
32 return max(0, time.monotonic()-dreamtime_difference())
33
34 class Time:
35 def realtime(self):
36 raise NotImplementedError
37
38 def monotonic(self):
39 raise NotImplementedError
40
41 @staticmethod
42 def _now():
43 raise NotImplementedError
44
45 @classmethod
46 def now(cls):
47 return cls(cls._now())
48
49 @classmethod
50 def from_realtime(cls, realtime):
51 return cls(realtime-time.time()+cls._now())
52
53 @classmethod
54 def from_monotonic(cls, monotonic):
55 return cls(monotonic-time.monotonic()+cls._now())
56
57 @classmethod
58 def after(cls, seconds):
59 return cls(cls._now()+seconds)
60
61 def datetime(self):
62 return datetime.datetime.fromtimestamp(self.realtime())
63
64 def seconds_to(self):
65 return self._value-self._now()
66
67 def __lt__(self, other):
68 return self.monotonic() < other.monotonic()
69
70 def __gt__(self, other):
71 return self.monotonic() > other.monotonic()
72
73 def __le__(self, other):
74 return self.monotonic() <= other.monotonic()
75
76 def __ge__(self, other):
77 return self.monotonic() >= other.monotonic()
78
79 def __eq__(self, other):
80 return self.monotonic() == other.realtime()
81
82 class RealTime(Time):
83 def __init__(self, when):
84 self._value=when
85
86 def realtime(self):
87 return self._value
88
89 def monotonic(self):
90 return self._value+(time.monotonic()-time.time())
91
92 @staticmethod
93 def _now():
94 return time.time()
95
96 class MonotonicTime(Time):
97 def __init__(self, when):
98 self._value=when
99
100 def realtime(self):
101 return self._value+(time.time()-time.monotonic())
102
103 def monotonic(self):
104 return self._value
105
106 @staticmethod
107 def _now():
108 return time.monotonic()
109
110 class DreamTime(Time):
111 def __init__(self, when):
112 self._value=when
113
114 def realtime(self):
115 return self._value+(time.time()-dreamtime())
116
117 # Important: monotonic is "static" within a wakeup period
118 # and does not need to call time.monotonic(), as it gets compared
119 # to a specific time.monotonic() realisation
120 def monotonic(self):
121 return self._value+dreamtime_difference()
122
123 @staticmethod
124 def _now():
125 return dreamtime()
126
127 #
128 # Wake up / sleep handling
129 #
130
131 class SleepHandler(Foundation.NSObject):
132 """ Handle wake/sleep notifications """
133
134 def init(self):
135 self.__sleeptime=None
136 self.__slept=0
137 self.__epoch=time.monotonic()
138 self.__lock=threading.Lock()
139 self.__callbacks=weakref.WeakKeyDictionary()
140
141 return self
142
143 def handleSleepNotification_(self, aNotification):
144 logger.info("System going to sleep")
145 now=time.monotonic()
146 with self.__lock:
147 self.__sleeptime=now
148
149 def handleWakeNotification_(self, aNotification):
150 logger.info("System waking up from sleep")
151 try:
152 now=time.monotonic()
153 with self.__lock:
154 if self.__sleeptime:
155 slept=max(0, now-self.__sleeptime)
156 logger.info("Slept %f seconds" % slept)
157 self.__slept=self.__slept+slept
158 self.__sleeptime=None
159 callbacks=self.__callbacks.copy()
160 except:
161 logger.exception("Bug in wakeup handler")
162
163 for callback in callbacks.values():
164 try:
165 callback()
166 except Exception:
167 logger.exception("Error in wake notification callback")
168
169 # Return difference to time.monotonic()
170 def diff(self):
171 with self.__lock:
172 diff=self.__epoch+self.__slept
173 return diff
174
175 # Weirdo (Py)ObjC naming to stop it form choking up
176 def addForObj_aCallback_(self, obj, callback):
177 with self.__lock:
178 self.__callbacks[obj]=callback
179
180 # obj is to use a a key in a weak key dictionary
181 def add_callback(obj, callback):
182 global _dreamtime_monitor
183
184 monitor=_dreamtime_monitor
185 if not monitor:
186 raise Exception("Dreamtime monitor not started")
187 else:
188 monitor.addForObj_aCallback_(obj, callback)
189
190 def start_monitoring():
191 global _dreamtime_monitor
192
193 if platform.system()=='Darwin':
194 logger.debug("Starting to monitor system sleep")
195 workspace = AppKit.NSWorkspace.sharedWorkspace()
196 notification_center = workspace.notificationCenter()
197 _dreamtime_monitor = SleepHandler.new()
198
199 notification_center.addObserver_selector_name_object_(
200 _dreamtime_monitor,
201 "handleSleepNotification:",
202 AppKit.NSWorkspaceWillSleepNotification,
203 None)
204
205 notification_center.addObserver_selector_name_object_(
206 _dreamtime_monitor,
207 "handleWakeNotification:",
208 AppKit.NSWorkspaceDidWakeNotification,
209 None)
210 else:
211 logger.warning(("No system sleep monitor implemented for '%s'"
212 % platform.system()))
213

mercurial