Skip to content

Commit 3fba4f0

Browse files
authored
Merge pull request slgobinath#730 from deltragon/typing-breakqueue
typing: add types to Core and BreakQueue, refactor
2 parents 033ea9e + ce238c4 commit 3fba4f0

File tree

4 files changed

+510
-278
lines changed

4 files changed

+510
-278
lines changed

safeeyes/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def main():
112112
# Initialize the logging
113113
utility.initialize_logging(args.debug)
114114
utility.initialize_platform()
115-
config = Config()
115+
config = Config.load()
116116
utility.cleanup_old_user_stylesheet()
117117

118118
if __running():

safeeyes/core.py

Lines changed: 93 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,31 @@
2222
import logging
2323
import threading
2424
import time
25+
import typing
2526

2627
from safeeyes import utility
2728
from safeeyes.model import BreakType
2829
from safeeyes.model import BreakQueue
2930
from safeeyes.model import EventHook
3031
from safeeyes.model import State
32+
from safeeyes.model import Config
3133

3234

3335
class 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

Comments
 (0)