2222import logging
2323import threading
2424import time
25+ import typing
2526
2627from safeeyes import utility
2728from safeeyes .model import BreakType
2829from safeeyes .model import BreakQueue
2930from safeeyes .model import EventHook
3031from safeeyes .model import State
32+ from safeeyes .model import Config
3133
3234
3335class SafeEyesCore :
3436 """Core of Safe Eyes runs the scheduler and notifies the breaks."""
3537
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 :
3749 """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
4650 # This event is fired before <time-to-prepare> for a break
4751 self .on_pre_break = EventHook ()
4852 # This event is fired just before the start of a break
@@ -64,32 +68,33 @@ def __init__(self, context):
6468 self .context ["postpone_button_disabled" ] = False
6569 self .context ["state" ] = State .WAITING
6670
67- def initialize (self , config ):
71+ def initialize (self , config : Config ):
6872 """Initialize the internal properties from configuration."""
6973 logging .info ("Initialize the core" )
7074 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 )
7276 self .default_postpone_duration = (
7377 config .get ("postpone_duration" ) * 60
7478 ) # Convert to seconds
7579 self .postpone_duration = self .default_postpone_duration
7680
77- def start (self , next_break_time = - 1 , reset_breaks = False ):
81+ def start (self , next_break_time = - 1 , reset_breaks = False ) -> None :
7882 """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" )
8085 return
8186 with self .lock :
8287 if not self .running :
8388 logging .info ("Start Safe Eyes core" )
8489 if reset_breaks :
8590 logging .info ("Reset breaks to start from the beginning" )
86- self .break_queue .reset ()
91+ self ._break_queue .reset ()
8792
8893 self .running = True
8994 self .scheduled_next_break_timestamp = int (next_break_time )
9095 utility .start_thread (self .__scheduler_job )
9196
92- def stop (self , is_resting = False ):
97+ def stop (self , is_resting = False ) -> None :
9398 """Stop Safe Eyes if it is running."""
9499 with self .lock :
95100 if not self .running :
@@ -105,11 +110,11 @@ def stop(self, is_resting=False):
105110 self .waiting_condition .notify_all ()
106111 self .waiting_condition .release ()
107112
108- def skip (self ):
113+ def skip (self ) -> None :
109114 """User skipped the break using Skip button."""
110115 self .context ["skipped" ] = True
111116
112- def postpone (self , duration = - 1 ):
117+ def postpone (self , duration = - 1 ) -> None :
113118 """User postponed the break using Postpone button."""
114119 if duration > 0 :
115120 self .postpone_duration = duration
@@ -118,37 +123,51 @@ def postpone(self, duration=-1):
118123 logging .debug ("Postpone the break for %d seconds" , self .postpone_duration )
119124 self .context ["postponed" ] = True
120125
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 ]:
122129 """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
126135 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
128137 )
129138 return time
130139
131- def take_break (self , break_type = None ):
140+ def take_break (self , break_type : typing . Optional [ BreakType ] = None ) -> None :
132141 """Calling this method stops the scheduler and show the next break
133142 screen.
134143 """
135- if self .break_queue . is_empty () :
144+ if self ._break_queue is None :
136145 return
137146 if not self .context ["state" ] == State .WAITING :
138147 return
139148 utility .start_thread (self .__take_break , break_type = break_type )
140149
141- def has_breaks (self , break_type = None ):
150+ def has_breaks (self , break_type : typing . Optional [ BreakType ] = None ) -> bool :
142151 """Check whether Safe Eyes has breaks or not.
143152
144153 Use the break_type to check for either short or long break.
145154 """
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
147160
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 :
149164 """Show the next break screen."""
150165 logging .info ("Take a break due to external request" )
151166
167+ if self ._break_queue is None :
168+ # This will only be called by self.take_break, which checks this
169+ return
170+
152171 with self .lock :
153172 if not self .running :
154173 return
@@ -163,33 +182,35 @@ def __take_break(self, break_type=None):
163182 time .sleep (1 ) # Wait for 1 sec to ensure the scheduler is dead
164183 self .running = True
165184
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 )
168187 utility .execute_main_thread (self .__fire_start_break )
169188
170- def __scheduler_job (self ):
189+ def __scheduler_job (self ) -> None :
171190 """Scheduler task to execute during every interval."""
172191 if not self .running :
173192 return
174193
194+ if self ._break_queue is None :
195+ # This will only be called by methods which check this
196+ return
197+
175198 current_time = datetime .datetime .now ()
176199 current_timestamp = current_time .timestamp ()
177200
178201 if self .context ["state" ] == State .RESTING and self .paused_time > - 1 :
179202 # Safe Eyes was resting
180203 paused_duration = int (current_timestamp - self .paused_time )
181204 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 :
186207 logging .info (
187208 "Skip next long break due to the pause %ds longer than break"
188209 " duration" ,
189210 paused_duration ,
190211 )
191212 # Skip the next long break
192- self .break_queue .reset ()
213+ self ._break_queue .reset ()
193214
194215 if self .context ["postponed" ]:
195216 # Previous break was postponed
@@ -204,7 +225,7 @@ def __scheduler_job(self):
204225 self .scheduled_next_break_timestamp = - 1
205226 else :
206227 # 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
208229
209230 self .scheduled_next_break_time = current_time + datetime .timedelta (
210231 seconds = time_to_wait
@@ -221,23 +242,31 @@ def __scheduler_job(self):
221242 logging .info ("Pre-break waiting is over" )
222243
223244 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]
225248 utility .execute_main_thread (self .__fire_pre_break )
226249
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 :
228251 """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 )
230256
231- def __fire_pre_break (self ):
257+ def __fire_pre_break (self ) -> None :
232258 """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
233262 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 ()):
235264 # Plugins wanted to ignore this break
236265 self .__start_next_break ()
237266 return
238267 utility .start_thread (self .__wait_until_prepare )
239268
240- def __wait_until_prepare (self ):
269+ def __wait_until_prepare (self ) -> None :
241270 logging .info (
242271 "Wait for %d seconds before the break" , self .pre_break_warning_time
243272 )
@@ -247,12 +276,15 @@ def __wait_until_prepare(self):
247276 return
248277 utility .execute_main_thread (self .__fire_start_break )
249278
250- def __postpone_break (self ):
279+ def __postpone_break (self ) -> None :
251280 self .__wait_for (self .postpone_duration )
252281 utility .execute_main_thread (self .__fire_start_break )
253282
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 ()
256288 # Show the break screen
257289 if not self .on_start_break .fire (break_obj ):
258290 # Plugins want to ignore this break
@@ -261,6 +293,10 @@ def __fire_start_break(self):
261293 if self .context ["postponed" ]:
262294 # Plugins want to postpone this break
263295 self .context ["postponed" ] = False
296+
297+ if self .scheduled_next_break_time is None :
298+ raise Exception ("this should never happen" )
299+
264300 # Update the next break time
265301 self .scheduled_next_break_time = (
266302 self .scheduled_next_break_time
@@ -273,10 +309,13 @@ def __fire_start_break(self):
273309 self .start_break .fire (break_obj )
274310 utility .start_thread (self .__start_break )
275311
276- def __start_break (self ):
312+ def __start_break (self ) -> None :
277313 """Start the break screen."""
314+ if self ._break_queue is None :
315+ # This will only be called by methods which check this
316+ return
278317 self .context ["state" ] = State .BREAK
279- break_obj = self .break_queue .get_break ()
318+ break_obj = self ._break_queue .get_break ()
280319 countdown = break_obj .duration
281320 total_break_time = countdown
282321
@@ -292,7 +331,7 @@ def __start_break(self):
292331 countdown -= 1
293332 utility .execute_main_thread (self .__fire_stop_break )
294333
295- def __fire_stop_break (self ):
334+ def __fire_stop_break (self ) -> None :
296335 # Loop terminated because of timeout (not skipped) -> Close the break alert
297336 if not self .context ["skipped" ] and not self .context ["postponed" ]:
298337 logging .info ("Break is terminated automatically" )
@@ -304,15 +343,18 @@ def __fire_stop_break(self):
304343 self .context ["postpone_button_disabled" ] = False
305344 self .__start_next_break ()
306345
307- def __wait_for (self , duration ) :
346+ def __wait_for (self , duration : int ) -> None :
308347 """Wait until someone wake up or the timeout happens."""
309348 self .waiting_condition .acquire ()
310349 self .waiting_condition .wait (duration )
311350 self .waiting_condition .release ()
312351
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
314356 if not self .context ["postponed" ]:
315- self .break_queue .next ()
357+ self ._break_queue .next ()
316358
317359 if self .running :
318360 # Schedule the break again
0 commit comments