Skip to content

Commit 8851d0b

Browse files
committed
Refactor Scheduler class to inherit from threading.Thread and update task execution logic
1 parent dff3486 commit 8851d0b

File tree

2 files changed

+254
-5
lines changed

2 files changed

+254
-5
lines changed

start_your_day/backend/gui.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
from tkinter import *
2+
from tkinter import ttk
3+
from emails import DailyEmail
4+
from scheduler import Scheduler
5+
6+
class GUI:
7+
# The GUI should enable the admin to...
8+
# - configure which content sources to include in email
9+
# - add recipients
10+
# - remove recipients
11+
# - schedule daily time to send email
12+
# - configure sender credentials
13+
14+
15+
def __init__(self, root):
16+
# build the GUI window
17+
self.__root = root
18+
self.__root.title('Daily Digest')
19+
title_label = ttk.Label(self.__root, text = ' \U0001F4DC START YOUR DAY \U0001F4DC',
20+
font = 'Algerian 32 bold', justify = CENTER)
21+
title_label.pack(padx = 5, pady = 5)
22+
23+
self.__style = ttk.Style()
24+
self.__style.configure('TButton', font = ('Arial', 12, 'bold'))
25+
self.__style.configure('Header.TLabel', font = ('Arial', 18, 'bold'))
26+
27+
# GUI listbox for recipients
28+
recipients_frame = ttk.Frame(self.__root)
29+
recipients_frame.pack(padx = 5, pady = 5)
30+
self.__add_recipient_var = StringVar()
31+
self.__recipient_list_var = Variable()
32+
self.__build_gui_recipients(recipients_frame,
33+
self.__add_recipient_var,
34+
self.__recipient_list_var)
35+
36+
# GUI elements to schedule delivery time
37+
schedule_frame = ttk.Frame(self.__root)
38+
schedule_frame.pack(padx = 5, pady = 5)
39+
self.__hour_var = StringVar()
40+
self.__minute_var = StringVar()
41+
self.__build_gui_schedule(schedule_frame,
42+
self.__hour_var,
43+
self.__minute_var)
44+
45+
# GUI checkboxes of content to include in email
46+
contents_frame = ttk.Frame(self.__root)
47+
contents_frame.pack(padx = 5, pady = 5)
48+
self.__quote_var = IntVar()
49+
self.__weather_var = IntVar()
50+
self.__wikipedia_var = IntVar()
51+
self.__build_gui_contents(contents_frame,
52+
self.__quote_var,
53+
self.__weather_var,
54+
self.__wikipedia_var)
55+
56+
# GUI fields for sender email/password credentials
57+
sender_frame = ttk.Frame(self.__root)
58+
sender_frame.pack(padx = 5, pady = 5)
59+
self.__sender_email_var = StringVar()
60+
self.__sender_password_var = StringVar()
61+
self.__build_gui_sender(sender_frame,
62+
self.__sender_email_var,
63+
self.__sender_password_var)
64+
65+
# GUI field for controls
66+
controls_frame = ttk.Frame(self.__root)
67+
controls_frame.pack(padx = 5, pady = 5)
68+
self.__build_gui_controls(controls_frame)
69+
70+
# set initial values for variables
71+
self.__email = DailyEmail()
72+
73+
self.__add_recipient_var.set('')
74+
self.__recipient_list_var.set(self.__email.recipient_list)
75+
76+
self.__hour_var.set('07') # defaul send time
77+
self.__minute_var.set('30')
78+
79+
self.__quote_var.set(self.__email.content['quote']['include'])
80+
self.__weather_var.set(self.__email.content['weather']['include'])
81+
self.__wikipedia_var.set(self.__email.content['wikipedia']['include'])
82+
83+
self.__sender_email_var.set(self.__email.sender_credentials['email'])
84+
self.__sender_password_var.set(self.__email.sender_credentials['password'])
85+
86+
# initialize scheduler
87+
self.__scheduler = Scheduler()
88+
self.__scheduler.start()
89+
self.__root.protocol("WM_DELETE_WINDOW", self.__shutdown) # shuts down the scheduler
90+
91+
"""
92+
Build GUI elements to add/remove recipients
93+
"""
94+
def __build_gui_recipients(self, master, add_recipient_var, recipient_list_var):
95+
# create GUI widgets
96+
header = ttk.Label(master, text = 'Recipients:', style = 'Header.TLabel')
97+
spacer_frame = ttk.Frame(master) # used as GUI spacer
98+
99+
recipients_entry = ttk.Entry(master, width = 40, textvariable = add_recipient_var)
100+
recipients_scrollbar = ttk.Scrollbar(master, orient=VERTICAL)
101+
recipients_scrollbar.grid(row = 4, column = 1, sticky = N+S+W+E)
102+
recipient_listbox = Listbox(master, listvariable = recipient_list_var,
103+
selectmode = 'multiple', width = 40, height = 5)
104+
recipient_listbox.configure(yscrollcommand=recipients_scrollbar.set)
105+
recipients_scrollbar.config(command=recipient_listbox.yview)
106+
107+
add_button = ttk.Button(master, text='Add Recipient', command=self.__add_recipient)
108+
remove_button = ttk.Button(master, text = 'Remove Selected',
109+
command = lambda: self.__remove_selected_recipients(recipient_listbox.curselection()))
110+
111+
# place GUI widgets using grid geometry manager
112+
header.grid(row = 0, column = 0)
113+
recipients_entry.grid(row = 1, column = 0)
114+
add_button.grid(row=2, column = 0)
115+
spacer_frame.grid(row = 3, column = 0, pady = 5)
116+
recipient_listbox.grid(row = 4, column = 0)
117+
remove_button.grid(row = 5, column = 0)
118+
119+
"""
120+
Build GUI elements to schedule send time
121+
"""
122+
def __build_gui_schedule(self, master, hour_var, minute_var):
123+
# create GUI widgets
124+
header = ttk.Label(master, text = 'Scheduled Time (24hr):', style = 'Header.TLabel')
125+
hour_spinbox = ttk.Spinbox(master, from_ = 0, to = 23, textvariable = hour_var,
126+
wrap = True, width = 3, justify = CENTER, font = 'Arial 12')
127+
minute_spinbox = ttk.Spinbox(master, from_ = 0, to = 59, textvariable = minute_var,
128+
wrap = True, width = 3, justify = CENTER, font = 'Arial 12')
129+
130+
# place GUI widgets using grid geometry manager
131+
header.grid(row = 0, column = 0, columnspan = 2)
132+
hour_spinbox.grid(row = 1, column = 0, sticky = E, padx = 2, pady= 5)
133+
minute_spinbox.grid(row = 1, column = 1, sticky = W, padx = 2, pady= 5)
134+
135+
"""
136+
Build GUI elements to select content to include
137+
"""
138+
def __build_gui_contents(self, master, quote_var, weather_var, wikipedia_var):
139+
# create GUI widgets
140+
header = ttk.Label(master, text = 'Digest Contents:', style = 'Header.TLabel')
141+
quote_checkbox = Checkbutton(master, text = 'Motivational Quote',
142+
onvalue = True, offvalue = False,
143+
variable = quote_var)
144+
weather_checkbox = Checkbutton(master, text = 'Weather Forecast',
145+
onvalue = True, offvalue = False,
146+
variable = weather_var)
147+
wikipedia_checkbox = Checkbutton(master, text = 'Wikipedia Article',
148+
onvalue = True, offvalue = False,
149+
variable = wikipedia_var)
150+
151+
# place GUI widgets using grid geometry manager
152+
header.grid(row = 0, column = 0, columnspan=2)
153+
quote_checkbox.grid(row = 1, column = 0, sticky = W)
154+
weather_checkbox.grid(row = 2, column = 0, sticky = W)
155+
wikipedia_checkbox.grid(row = 2, column = 1, sticky = W)
156+
157+
"""
158+
Build GUI elements to configure sender credentials
159+
"""
160+
def __build_gui_sender(self, master, sender_email_var, sender_password_var):
161+
# create GUI widgets
162+
header = ttk.Label(master, text = 'Sender Credentials:', style = 'Header.TLabel')
163+
email_label = ttk.Label(master, text = "Email:")
164+
email_entry = ttk.Entry(master, width = 40,
165+
textvariable = sender_email_var)
166+
password_label = ttk.Label(master, text = 'Password:')
167+
password_entry = ttk.Entry(master, width = 40, show = '*',
168+
textvariable = sender_password_var)
169+
170+
# place GUI widgets using grid geometry manager
171+
header.grid(row = 0, column = 0, columnspan = 2)
172+
email_label.grid(row = 1, column = 0, pady = 2, sticky = E)
173+
email_entry.grid(row = 1, column = 1, pady = 2, sticky = W)
174+
password_label.grid(row = 2, column = 0, pady = 2, sticky = E)
175+
password_entry.grid(row = 2, column = 1, pady = 2, sticky = W)
176+
177+
"""
178+
Build GUI elements to update settings & manually send digest email
179+
"""
180+
def __build_gui_controls(self, master):
181+
# create GUI widgets
182+
update_button = ttk.Button(master, text = 'Update Settings', command = self.__update_settings)
183+
send_button = ttk.Button(master, text = 'Manual Send', command = self.__manual_send)
184+
185+
# place GUI widgets using grid geometry manager
186+
update_button.grid(row = 0, column = 0, padx = 5, pady = 5)
187+
send_button.grid(row = 0, column = 1, padx = 5, pady = 5)
188+
189+
"""
190+
Callback function to add recipient
191+
"""
192+
def __add_recipient(self):
193+
new_recipient = self.__add_recipient_var.get()
194+
if new_recipient != '':
195+
recipient_list = self.__recipient_list_var.get()
196+
if recipient_list != '':
197+
self.__recipient_list_var.set(recipient_list + (new_recipient,))
198+
else:
199+
self.__recipient_list_var.set([new_recipient])
200+
self.__add_recipient_var.set('') # clear entry field
201+
202+
"""
203+
Callback function to remove selected recipient(s)
204+
"""
205+
def __remove_selected_recipients(self, selection):
206+
recipient_list = list(self.__recipient_list_var.get())
207+
for index in reversed(selection):
208+
recipient_list.pop(index)
209+
self.__recipient_list_var.set(recipient_list)
210+
211+
"""
212+
Callback function to update settings
213+
"""
214+
def __update_settings(self):
215+
print('Updating settings...')
216+
self.__email.recipient_list = list(self.__recipient_list_var.get())
217+
218+
self.__email.content['quote']['include'] = self.__quote_var.get()
219+
self.__email.content['weather']['include'] = self.__weather_var.get()
220+
self.__email.content['wikipedia']['include'] = self.__wikipedia_var.get()
221+
222+
self.__email.sender_credentials = {'email': self.__sender_email_var.get(),
223+
'password': self.__sender_password_var.get()}
224+
225+
self.__scheduler.schedule_task(int(self.__hour_var.get()),
226+
int(self.__minute_var.get()),
227+
self.__email.send_email)
228+
229+
"""
230+
Callback function to manually send digest email
231+
"""
232+
def __manual_send(self):
233+
# note: settings are not updated before manual send
234+
print('Manually sending email digest...')
235+
self.__email.send_email()
236+
237+
"""
238+
Shutdown the scheduler before closing the GUI window
239+
"""
240+
def __shutdown(self):
241+
print('Shutting down the scheduler...')
242+
self.__scheduler.stop()
243+
self.__scheduler.join()
244+
self.__root.destroy() # close the GUI
245+
246+
if __name__ == '__main__':
247+
root = Tk()
248+
app = GUI(root)
249+
root.mainloop()
250+

start_your_day/backend/scheduler.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import time
33
import schedule
44

5-
class Scheduler:
5+
class Scheduler(threading.Thread):
66
def __init__(self):
77
super().__init__()
88
self.__stop_running = threading.Event()
@@ -13,7 +13,7 @@ def schedule_task(self, hour, minute, task):
1313
schedule.every().day.at(f"{hour:02d}:{minute:02d}").do(task)
1414

1515
# start the scheduler as background thread...
16-
def start(self):
16+
def run(self):
1717
self.__stop_running.clear()
1818
while not self.__stop_running.is_set():
1919
schedule.run_pending()
@@ -28,15 +28,14 @@ def stop(self):
2828
if __name__ == '__main__':
2929
# creating a custom task for the scheduler to run
3030
def task():
31-
print("Task is running...\n")
32-
print("Task completed successfully!\n")
31+
print("My custom task...\n")
3332

3433
scheduler = Scheduler() # create a scheduler instance
3534
scheduler.start()
3635
hour = time.localtime().tm_hour # get the current hour
3736
minute = time.localtime().tm_min + 1 # get the current minute and add 1 to run the task in the next minute
3837
print(f"Scheduling the task to run every day at {hour:02d}:{minute:02d}...\n")
3938
scheduler.schedule_task(hour, minute, task) # schedule the task to run every day at the current hour and the next minute
40-
time.sleep(5) # wait for 20 seconds before stopping the scheduler, ensure the task runs at least once
39+
time.sleep(20) # wait for 20 seconds before stopping the scheduler, ensure the task runs at least once
4140
scheduler.stop()
4241

0 commit comments

Comments
 (0)