22
22
import logging
23
23
import threading
24
24
import time
25
+ import typing
25
26
26
27
from safeeyes import utility
27
28
from safeeyes .model import BreakType
28
29
from safeeyes .model import BreakQueue
29
30
from safeeyes .model import EventHook
30
31
from safeeyes .model import State
32
+ from safeeyes .model import Config
31
33
32
34
33
35
class SafeEyesCore :
34
36
"""Core of Safe Eyes runs the scheduler and notifies the breaks."""
35
37
36
- def __init__ (self , context ):
38
+ scheduled_next_break_time : typing .Optional [datetime .datetime ] = None
39
+ scheduled_next_break_timestamp : int = - 1
40
+ running : bool = False
41
+ paused_time : float = - 1
42
+ postpone_duration : int = 0
43
+ default_postpone_duration : int = 0
44
+ pre_break_warning_time : int = 0
45
+
46
+ _break_queue : typing .Optional [BreakQueue ] = None
47
+
48
+ def __init__ (self , context ) -> None :
37
49
"""Create an instance of SafeEyesCore and initialize the variables."""
38
- self .break_queue = None
39
- self .postpone_duration = 0
40
- self .default_postpone_duration = 0
41
- self .pre_break_warning_time = 0
42
- self .running = False
43
- self .scheduled_next_break_timestamp = - 1
44
- self .scheduled_next_break_time = None
45
- self .paused_time = - 1
46
50
# This event is fired before <time-to-prepare> for a break
47
51
self .on_pre_break = EventHook ()
48
52
# This event is fired just before the start of a break
@@ -64,32 +68,33 @@ def __init__(self, context):
64
68
self .context ["postpone_button_disabled" ] = False
65
69
self .context ["state" ] = State .WAITING
66
70
67
- def initialize (self , config ):
71
+ def initialize (self , config : Config ):
68
72
"""Initialize the internal properties from configuration."""
69
73
logging .info ("Initialize the core" )
70
74
self .pre_break_warning_time = config .get ("pre_break_warning_time" )
71
- self .break_queue = BreakQueue (config , self .context )
75
+ self ._break_queue = BreakQueue . create (config , self .context )
72
76
self .default_postpone_duration = (
73
77
config .get ("postpone_duration" ) * 60
74
78
) # Convert to seconds
75
79
self .postpone_duration = self .default_postpone_duration
76
80
77
- def start (self , next_break_time = - 1 , reset_breaks = False ):
81
+ def start (self , next_break_time = - 1 , reset_breaks = False ) -> None :
78
82
"""Start Safe Eyes is it is not running already."""
79
- if self .break_queue .is_empty ():
83
+ if self ._break_queue is None :
84
+ logging .info ("No breaks defined, not starting the core" )
80
85
return
81
86
with self .lock :
82
87
if not self .running :
83
88
logging .info ("Start Safe Eyes core" )
84
89
if reset_breaks :
85
90
logging .info ("Reset breaks to start from the beginning" )
86
- self .break_queue .reset ()
91
+ self ._break_queue .reset ()
87
92
88
93
self .running = True
89
94
self .scheduled_next_break_timestamp = int (next_break_time )
90
95
utility .start_thread (self .__scheduler_job )
91
96
92
- def stop (self , is_resting = False ):
97
+ def stop (self , is_resting = False ) -> None :
93
98
"""Stop Safe Eyes if it is running."""
94
99
with self .lock :
95
100
if not self .running :
@@ -105,11 +110,11 @@ def stop(self, is_resting=False):
105
110
self .waiting_condition .notify_all ()
106
111
self .waiting_condition .release ()
107
112
108
- def skip (self ):
113
+ def skip (self ) -> None :
109
114
"""User skipped the break using Skip button."""
110
115
self .context ["skipped" ] = True
111
116
112
- def postpone (self , duration = - 1 ):
117
+ def postpone (self , duration = - 1 ) -> None :
113
118
"""User postponed the break using Postpone button."""
114
119
if duration > 0 :
115
120
self .postpone_duration = duration
@@ -118,37 +123,51 @@ def postpone(self, duration=-1):
118
123
logging .debug ("Postpone the break for %d seconds" , self .postpone_duration )
119
124
self .context ["postponed" ] = True
120
125
121
- def get_break_time (self , break_type = None ):
126
+ def get_break_time (
127
+ self , break_type : typing .Optional [BreakType ] = None
128
+ ) -> typing .Optional [datetime .datetime ]:
122
129
"""Returns the next break time."""
123
- break_obj = self .break_queue .get_break (break_type )
124
- if not break_obj :
125
- return False
130
+ if self ._break_queue is None :
131
+ return None
132
+ break_obj = self ._break_queue .get_break_with_type (break_type )
133
+ if not break_obj or self .scheduled_next_break_time is None :
134
+ return None
126
135
time = self .scheduled_next_break_time + datetime .timedelta (
127
- minutes = break_obj .time - self .break_queue .get_break ().time
136
+ minutes = break_obj .time - self ._break_queue .get_break ().time
128
137
)
129
138
return time
130
139
131
- def take_break (self , break_type = None ):
140
+ def take_break (self , break_type : typing . Optional [ BreakType ] = None ) -> None :
132
141
"""Calling this method stops the scheduler and show the next break
133
142
screen.
134
143
"""
135
- if self .break_queue . is_empty () :
144
+ if self ._break_queue is None :
136
145
return
137
146
if not self .context ["state" ] == State .WAITING :
138
147
return
139
148
utility .start_thread (self .__take_break , break_type = break_type )
140
149
141
- def has_breaks (self , break_type = None ):
150
+ def has_breaks (self , break_type : typing . Optional [ BreakType ] = None ) -> bool :
142
151
"""Check whether Safe Eyes has breaks or not.
143
152
144
153
Use the break_type to check for either short or long break.
145
154
"""
146
- return not self .break_queue .is_empty (break_type )
155
+ if self ._break_queue is None :
156
+ return False
157
+
158
+ if break_type is None :
159
+ return True
147
160
148
- def __take_break (self , break_type = None ):
161
+ return not self ._break_queue .is_empty (break_type )
162
+
163
+ def __take_break (self , break_type : typing .Optional [BreakType ] = None ) -> None :
149
164
"""Show the next break screen."""
150
165
logging .info ("Take a break due to external request" )
151
166
167
+ if self ._break_queue is None :
168
+ # This will only be called by self.take_break, which checks this
169
+ return
170
+
152
171
with self .lock :
153
172
if not self .running :
154
173
return
@@ -163,33 +182,35 @@ def __take_break(self, break_type=None):
163
182
time .sleep (1 ) # Wait for 1 sec to ensure the scheduler is dead
164
183
self .running = True
165
184
166
- if break_type is not None and self .break_queue .get_break ().type != break_type :
167
- self .break_queue .next (break_type )
185
+ if break_type is not None and self ._break_queue .get_break ().type != break_type :
186
+ self ._break_queue .next (break_type )
168
187
utility .execute_main_thread (self .__fire_start_break )
169
188
170
- def __scheduler_job (self ):
189
+ def __scheduler_job (self ) -> None :
171
190
"""Scheduler task to execute during every interval."""
172
191
if not self .running :
173
192
return
174
193
194
+ if self ._break_queue is None :
195
+ # This will only be called by methods which check this
196
+ return
197
+
175
198
current_time = datetime .datetime .now ()
176
199
current_timestamp = current_time .timestamp ()
177
200
178
201
if self .context ["state" ] == State .RESTING and self .paused_time > - 1 :
179
202
# Safe Eyes was resting
180
203
paused_duration = int (current_timestamp - self .paused_time )
181
204
self .paused_time = - 1
182
- if (
183
- paused_duration
184
- > self .break_queue .get_break (BreakType .LONG_BREAK ).duration
185
- ):
205
+ next_long = self ._break_queue .get_break_with_type (BreakType .LONG_BREAK )
206
+ if next_long is not None and paused_duration > next_long .duration :
186
207
logging .info (
187
208
"Skip next long break due to the pause %ds longer than break"
188
209
" duration" ,
189
210
paused_duration ,
190
211
)
191
212
# Skip the next long break
192
- self .break_queue .reset ()
213
+ self ._break_queue .reset ()
193
214
194
215
if self .context ["postponed" ]:
195
216
# Previous break was postponed
@@ -204,7 +225,7 @@ def __scheduler_job(self):
204
225
self .scheduled_next_break_timestamp = - 1
205
226
else :
206
227
# Use next break, convert to seconds
207
- time_to_wait = self .break_queue .get_break ().time * 60
228
+ time_to_wait = self ._break_queue .get_break ().time * 60
208
229
209
230
self .scheduled_next_break_time = current_time + datetime .timedelta (
210
231
seconds = time_to_wait
@@ -221,23 +242,31 @@ def __scheduler_job(self):
221
242
logging .info ("Pre-break waiting is over" )
222
243
223
244
if not self .running :
224
- return
245
+ # This can be reached if another thread changed running while __wait_for was
246
+ # blocking
247
+ return # type: ignore[unreachable]
225
248
utility .execute_main_thread (self .__fire_pre_break )
226
249
227
- def __fire_on_update_next_break (self , next_break_time ) :
250
+ def __fire_on_update_next_break (self , next_break_time : datetime . datetime ) -> None :
228
251
"""Pass the next break information to the registered listeners."""
229
- self .on_update_next_break .fire (self .break_queue .get_break (), next_break_time )
252
+ if self ._break_queue is None :
253
+ # This will only be called by methods which check this
254
+ return
255
+ self .on_update_next_break .fire (self ._break_queue .get_break (), next_break_time )
230
256
231
- def __fire_pre_break (self ):
257
+ def __fire_pre_break (self ) -> None :
232
258
"""Show the notification and start the break after the notification."""
259
+ if self ._break_queue is None :
260
+ # This will only be called by methods which check this
261
+ return
233
262
self .context ["state" ] = State .PRE_BREAK
234
- if not self .on_pre_break .fire (self .break_queue .get_break ()):
263
+ if not self .on_pre_break .fire (self ._break_queue .get_break ()):
235
264
# Plugins wanted to ignore this break
236
265
self .__start_next_break ()
237
266
return
238
267
utility .start_thread (self .__wait_until_prepare )
239
268
240
- def __wait_until_prepare (self ):
269
+ def __wait_until_prepare (self ) -> None :
241
270
logging .info (
242
271
"Wait for %d seconds before the break" , self .pre_break_warning_time
243
272
)
@@ -247,12 +276,15 @@ def __wait_until_prepare(self):
247
276
return
248
277
utility .execute_main_thread (self .__fire_start_break )
249
278
250
- def __postpone_break (self ):
279
+ def __postpone_break (self ) -> None :
251
280
self .__wait_for (self .postpone_duration )
252
281
utility .execute_main_thread (self .__fire_start_break )
253
282
254
- def __fire_start_break (self ):
255
- break_obj = self .break_queue .get_break ()
283
+ def __fire_start_break (self ) -> None :
284
+ if self ._break_queue is None :
285
+ # This will only be called by methods which check this
286
+ return
287
+ break_obj = self ._break_queue .get_break ()
256
288
# Show the break screen
257
289
if not self .on_start_break .fire (break_obj ):
258
290
# Plugins want to ignore this break
@@ -261,6 +293,10 @@ def __fire_start_break(self):
261
293
if self .context ["postponed" ]:
262
294
# Plugins want to postpone this break
263
295
self .context ["postponed" ] = False
296
+
297
+ if self .scheduled_next_break_time is None :
298
+ raise Exception ("this should never happen" )
299
+
264
300
# Update the next break time
265
301
self .scheduled_next_break_time = (
266
302
self .scheduled_next_break_time
@@ -273,10 +309,13 @@ def __fire_start_break(self):
273
309
self .start_break .fire (break_obj )
274
310
utility .start_thread (self .__start_break )
275
311
276
- def __start_break (self ):
312
+ def __start_break (self ) -> None :
277
313
"""Start the break screen."""
314
+ if self ._break_queue is None :
315
+ # This will only be called by methods which check this
316
+ return
278
317
self .context ["state" ] = State .BREAK
279
- break_obj = self .break_queue .get_break ()
318
+ break_obj = self ._break_queue .get_break ()
280
319
countdown = break_obj .duration
281
320
total_break_time = countdown
282
321
@@ -292,7 +331,7 @@ def __start_break(self):
292
331
countdown -= 1
293
332
utility .execute_main_thread (self .__fire_stop_break )
294
333
295
- def __fire_stop_break (self ):
334
+ def __fire_stop_break (self ) -> None :
296
335
# Loop terminated because of timeout (not skipped) -> Close the break alert
297
336
if not self .context ["skipped" ] and not self .context ["postponed" ]:
298
337
logging .info ("Break is terminated automatically" )
@@ -304,15 +343,18 @@ def __fire_stop_break(self):
304
343
self .context ["postpone_button_disabled" ] = False
305
344
self .__start_next_break ()
306
345
307
- def __wait_for (self , duration ) :
346
+ def __wait_for (self , duration : int ) -> None :
308
347
"""Wait until someone wake up or the timeout happens."""
309
348
self .waiting_condition .acquire ()
310
349
self .waiting_condition .wait (duration )
311
350
self .waiting_condition .release ()
312
351
313
- def __start_next_break (self ):
352
+ def __start_next_break (self ) -> None :
353
+ if self ._break_queue is None :
354
+ # This will only be called by methods which check this
355
+ return
314
356
if not self .context ["postponed" ]:
315
- self .break_queue .next ()
357
+ self ._break_queue .next ()
316
358
317
359
if self .running :
318
360
# Schedule the break again
0 commit comments