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