nowfocus 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nowfocus/__main__.py +158 -146
- nowfocus/conf.py +42 -49
- nowfocus/desktop-extras/nowfocus.desktop +1 -1
- nowfocus/example-todo.txt +1 -2
- nowfocus/install.py +92 -0
- nowfocus/session_options.py +1 -1
- nowfocus/task_window.py +6 -6
- nowfocus/utils.py +81 -82
- nowfocus/version_migrator.py +20 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.5.0.dist-info}/METADATA +2 -10
- {nowfocus-0.4.2.dist-info → nowfocus-0.5.0.dist-info}/RECORD +15 -13
- {nowfocus-0.4.2.dist-info → nowfocus-0.5.0.dist-info}/WHEEL +0 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.5.0.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.5.0.dist-info}/top_level.txt +0 -0
nowfocus/__main__.py
CHANGED
|
@@ -29,8 +29,6 @@ except Exception as e:
|
|
|
29
29
|
gi.require_version('AppIndicator3', '0.1')
|
|
30
30
|
from gi.repository import AppIndicator3 as appindicator
|
|
31
31
|
|
|
32
|
-
# from dbus_idle import IdleMonitor
|
|
33
|
-
|
|
34
32
|
# Set working dir to file location
|
|
35
33
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
36
34
|
|
|
@@ -62,81 +60,59 @@ class Application(Gtk.Application):
|
|
|
62
60
|
def __init__(self, *args, **kwargs):
|
|
63
61
|
super().__init__(*args, application_id="org.nowfocus.nowfocus", **kwargs)
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
# # To put everything here...
|
|
67
|
-
# # this doesn't work because as soon as an exception occurs ii jumps to the handler and breaks all the following code.
|
|
63
|
+
try:
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# return None
|
|
73
|
-
|
|
74
|
-
self.window = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
self.window = None
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
self.is_running = False
|
|
70
|
+
self.device_in_use = True
|
|
71
|
+
self.UserIdleTime = UserIdleTime()
|
|
72
|
+
self.last_user_idle_check_time = None
|
|
73
|
+
self.session = default_session()
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
self.menu = Gtk.Menu()
|
|
76
|
+
self.menu_tasks = {}
|
|
77
|
+
self.list_menus = {}
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
self.indicator = appindicator.Indicator.new(conf.app_name, os.path.abspath('icon/icon-1.svg'), appindicator.IndicatorCategory.APPLICATION_STATUS)
|
|
80
|
+
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
|
|
81
|
+
self.indicator.set_menu(self.menu)
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
if conf.is_first_load:
|
|
84
|
+
import install
|
|
85
|
+
install.run_first_load_actions()
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
# menu.set_reserve_toggle_size(False) # skip menu left padding, doesn't work
|
|
87
|
+
utils.db_schema_update()
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
utils.db_schema_update()
|
|
89
|
+
self.async_refresh()
|
|
91
90
|
|
|
91
|
+
# utils.db_cleanup()
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
self.async_refresh()
|
|
93
|
+
main_tick_timer = GLib.timeout_add_seconds(1, self.tick)
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
self.resume_session_from_db()
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
self.start_pipe()
|
|
98
|
+
self.open_task_window()
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
# Testing
|
|
101
|
+
# time.sleep(3)
|
|
102
|
+
# self.open_session_options_dialog('test_param')
|
|
103
|
+
# self.open_settings_window() #for testing
|
|
104
|
+
# self.open_new_task_dialog() #for testing
|
|
105
|
+
# self.print_time_totals()
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
signal.signal(signal.SIGINT, self.quit)
|
|
108
|
+
signal.signal(signal.SIGUSR1, self.signal_handler)
|
|
109
|
+
signal.signal(signal.SIGUSR2, self.signal_handler)
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
s = json.loads(db_session[0]['value'])
|
|
107
|
-
s['start_time'] = datetime.strptime(s['start_time'],'%Y-%m-%d %H:%M:%S.%f')
|
|
108
|
-
self.session = s
|
|
109
|
-
self.is_running = True
|
|
110
|
-
dbg("resuming session",s['label'],l=2, s='session')
|
|
111
|
+
Gtk.main()
|
|
111
112
|
except Exception as e:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
117
|
-
self.pipethread.daemon = True
|
|
118
|
-
self.pipethread.start()
|
|
119
|
-
|
|
120
|
-
# Testing
|
|
121
|
-
# time.sleep(3)
|
|
122
|
-
# self.open_session_options_dialog('test_param')
|
|
123
|
-
|
|
124
|
-
# self.open_settings_window() #for testing
|
|
125
|
-
# self.open_task_window() #for testing
|
|
126
|
-
|
|
127
|
-
# time.sleep(2)
|
|
128
|
-
# self.open_new_task_dialog() #for testing
|
|
129
|
-
|
|
130
|
-
# time.sleep(2)
|
|
131
|
-
# self.print_time_totals()
|
|
132
|
-
|
|
133
|
-
# signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
134
|
-
signal.signal(signal.SIGINT, self.quit)
|
|
135
|
-
signal.signal(signal.SIGUSR1, self.signal_handler)
|
|
136
|
-
signal.signal(signal.SIGUSR2, self.signal_handler)
|
|
137
|
-
|
|
138
|
-
Gtk.main()
|
|
139
|
-
|
|
113
|
+
print(conf.app_name +' had a pretty bad error. Please submit the following trace as an issue on the git repo or email it to the developers ')
|
|
114
|
+
traceback.print_tb(e.__traceback__)
|
|
115
|
+
self.quit()
|
|
140
116
|
|
|
141
117
|
|
|
142
118
|
def print_time_totals(self = None, widget = None):
|
|
@@ -177,25 +153,13 @@ class Application(Gtk.Application):
|
|
|
177
153
|
exit()
|
|
178
154
|
|
|
179
155
|
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
156
|
+
def get_seconds_since_user_active(self):
|
|
157
|
+
|
|
158
|
+
if self.last_user_idle_check_time != now().replace(microsecond=0):
|
|
159
|
+
self.last_user_idle_check_time = now().replace(microsecond=0)
|
|
160
|
+
self.seconds_since_user_active = self.UserIdleTime.get()
|
|
183
161
|
|
|
184
|
-
|
|
185
|
-
# Requires xprintidle (sudo apt install xprintidle)
|
|
186
|
-
# idle_time = int(int(subprocess.getoutput('xprintidle')) / 1000)
|
|
187
|
-
|
|
188
|
-
# Version that works, using: https://github.com/bkbilly/dbus_idle
|
|
189
|
-
# but has many deps
|
|
190
|
-
# Requires:
|
|
191
|
-
# sudo apt install meson libdbus-glib-1-dev patchelf
|
|
192
|
-
# pip install dbus-idle
|
|
193
|
-
|
|
194
|
-
# idle_time = int(int(IdleMonitor().get_dbus_idle()) / 1000)
|
|
195
|
-
# return idle_time
|
|
196
|
-
|
|
197
|
-
return self.UserIdleTime.get()
|
|
198
|
-
|
|
162
|
+
return self.seconds_since_user_active
|
|
199
163
|
|
|
200
164
|
|
|
201
165
|
def toggle_do_not_disturb(self, widget):
|
|
@@ -213,81 +177,106 @@ class Application(Gtk.Application):
|
|
|
213
177
|
|
|
214
178
|
def tock(self):
|
|
215
179
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
afk_time = self.seconds_since_user_active()
|
|
220
|
-
# print("Idle time: "+str(afk_time))
|
|
180
|
+
user_inactive_time = self.get_seconds_since_user_active()
|
|
181
|
+
# print("user_inactive_time: "+str(user_inactive_time))
|
|
221
182
|
|
|
222
183
|
dbg('Last todo todo_sync_time', conf.todo_sync_time, 'Time diff',int(time_difference(conf.todo_sync_time)),'Auto refresh interval * 60', (conf.user['todolist_refresh_interval'] * 60), s="todoloading", l=3 )
|
|
223
184
|
|
|
224
|
-
if (int(time_difference(conf.todo_sync_time)) / 60) > conf.user['todolist_refresh_interval'] * 60
|
|
185
|
+
if (int(time_difference(conf.todo_sync_time)) / 60) > conf.user['todolist_refresh_interval'] * 60:
|
|
186
|
+
# TODO: use individual todo_refresh_times
|
|
225
187
|
self.async_refresh()
|
|
226
188
|
|
|
227
|
-
# TODO: use individual todo_refresh_times
|
|
228
|
-
|
|
229
189
|
minutes = (int(self.session['duration']) / 60)
|
|
230
190
|
|
|
231
|
-
if
|
|
191
|
+
if self.check_device_became_inactive():
|
|
192
|
+
self.handle_device_became_inactive()
|
|
193
|
+
|
|
194
|
+
elif self.device_in_use == False or 'do-not-disturb' in self.session:
|
|
195
|
+
# Do nothing
|
|
196
|
+
dbg("Do not disturb is active")
|
|
197
|
+
|
|
198
|
+
elif self.is_running:
|
|
199
|
+
|
|
200
|
+
if self.session['label'] in conf.user['custom_pomodoro_intervals']:
|
|
201
|
+
check = conf.user['custom_pomodoro_intervals'][self.session['label']]
|
|
202
|
+
else:
|
|
203
|
+
check = conf.user['pomodoro_interval']
|
|
204
|
+
|
|
205
|
+
if float(minutes / check ).is_integer():
|
|
206
|
+
notify.Notification.new("Time for a Break?","You've been working on "+self.session['label']+" for "+str(minutes)+" minutes. ", None).show()
|
|
207
|
+
|
|
208
|
+
playsound('sound/bell-xylophone-g.mp3',False)
|
|
209
|
+
|
|
232
210
|
|
|
233
|
-
if
|
|
234
|
-
|
|
211
|
+
if 'target' in self.session:
|
|
212
|
+
t = self.session['target']
|
|
213
|
+
|
|
214
|
+
t['percent'] = round(( (t['starting_value'] + minutes) / t['value'] ) * 100,1)
|
|
215
|
+
print("At", t['percent'], "% of ", t['scope'], " target")
|
|
216
|
+
|
|
217
|
+
if t['type'] == 'max':
|
|
218
|
+
if t['percent'] >= 100:
|
|
219
|
+
|
|
220
|
+
notify.Notification.new("Time is up for this "+t['scope'],"You'r at "+str(round(t['percent']))+"% of your "+str(t['value'])+" minutes in the last "+str(t['within_value'])+" "+ t['within_unit'], None).show()
|
|
221
|
+
|
|
222
|
+
playsound('sound/dinner-bell.mp3',False)
|
|
223
|
+
|
|
224
|
+
elif t['type'] == 'min' and round(t['starting_value'] + minutes) == t['value']:
|
|
225
|
+
notify.Notification.new("Good job on doing "+self.session['label'],"You've reached your target of "+str(t['value'])+" minutes "+str(t['within_value'])+" "+ t['within_unit'], None).show()
|
|
226
|
+
|
|
227
|
+
playsound('sound/xylophone-chord.mp3',False)
|
|
228
|
+
|
|
229
|
+
elif num_is_multiple_of(minutes,conf.user['randomness_interrupt_interval']):
|
|
235
230
|
|
|
236
|
-
|
|
231
|
+
notify.Notification.new("What Am I doing?","Your randomness timer is at "+str(minutes)+" minutes. ", None).show()
|
|
237
232
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
self.open_task_window()
|
|
234
|
+
|
|
235
|
+
playsound('sound/dinner-bell.mp3',False)
|
|
241
236
|
|
|
242
|
-
elif afk_time > 120:
|
|
243
|
-
self.session['duration'] = 0
|
|
244
|
-
self.session['start_time'] = now()
|
|
245
|
-
print("Idle time reset. afk:", afk_time, self.session)
|
|
246
237
|
|
|
238
|
+
def check_device_became_inactive(self):
|
|
239
|
+
if self.get_seconds_since_user_active() > (conf.user['device_not_in_use_threshold'] * 60):
|
|
240
|
+
if self.device_in_use == True:
|
|
241
|
+
self.device_in_use = False
|
|
242
|
+
return True
|
|
247
243
|
else:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if afk_time < 181:
|
|
251
|
-
self.open_task_window(None,{'afk_time':afk_time})
|
|
252
|
-
else:
|
|
253
|
-
if self.session['label'] in conf.user['custom_pomodoro_intervals']:
|
|
254
|
-
check = conf.user['custom_pomodoro_intervals'][self.session['label']]
|
|
255
|
-
else:
|
|
256
|
-
check = conf.user['pomodoro_interval']
|
|
257
|
-
|
|
258
|
-
if float(minutes / check ).is_integer():
|
|
259
|
-
notify.Notification.new("Time for a Break?","You've been working on "+self.session['label']+" for "+str(minutes)+" minutes. ", None).show()
|
|
244
|
+
self.device_in_use = True
|
|
245
|
+
return False
|
|
260
246
|
|
|
261
|
-
playsound('sound/bell-xylophone-g.mp3',False)
|
|
262
247
|
|
|
248
|
+
def handle_device_became_inactive(self):
|
|
249
|
+
self.user_last_inactive = now()
|
|
263
250
|
|
|
264
|
-
|
|
265
|
-
|
|
251
|
+
user_inactive_time = self.get_seconds_since_user_active()
|
|
252
|
+
dbg('device_became_inactive ',l=-1)
|
|
266
253
|
|
|
267
|
-
|
|
268
|
-
print("At", t['percent'], "% of ", t['scope'], " target")
|
|
254
|
+
if self.is_running and 'do-not-disturb' not in self.session:
|
|
269
255
|
|
|
270
|
-
|
|
271
|
-
if t['percent'] >= 100:
|
|
256
|
+
self.open_task_window(None,{'user_inactive_time':user_inactive_time})
|
|
272
257
|
|
|
273
|
-
|
|
258
|
+
else:
|
|
259
|
+
self.session['duration'] = self.session['duration'] - user_inactive_time
|
|
260
|
+
if self.session['duration'] > 60:
|
|
261
|
+
utils.db_save_session(self.session)
|
|
262
|
+
|
|
263
|
+
self.session['duration'] = 0
|
|
264
|
+
self.session['start_time'] = now()
|
|
265
|
+
dbg("Recording:", user_inactive_time, self.session)
|
|
266
|
+
|
|
267
|
+
if 'do-not-disturb' not in self.session and conf.user['randomness_interrupt_interval'] > 0:
|
|
268
|
+
self.open_task_window(None)
|
|
274
269
|
|
|
275
|
-
playsound('sound/dinner-bell.mp3',False)
|
|
276
270
|
|
|
277
|
-
elif t['type'] == 'min' and round(t['starting_value'] + minutes) == t['value']:
|
|
278
|
-
notify.Notification.new("Good job on doing "+self.session['label'],"You've reached your target of "+str(t['value'])+" minutes "+str(t['within_value'])+" "+ t['within_unit'], None).show()
|
|
279
|
-
|
|
280
|
-
playsound('sound/xylophone-chord.mp3',False)
|
|
281
|
-
|
|
282
|
-
|
|
283
271
|
def tick(self):
|
|
284
272
|
|
|
285
273
|
# check for suspend indicated by gap in tick intervals
|
|
286
274
|
time_since_last_tick = round(time_difference(self.session['start_time']) - self.session['duration'])
|
|
287
275
|
if time_since_last_tick > 10:
|
|
276
|
+
|
|
288
277
|
dbg(time_since_last_tick, " seconds since last tick. Probably just woke from suspend. ")
|
|
289
278
|
if self.is_running:
|
|
290
|
-
self.open_task_window(None,{'
|
|
279
|
+
self.open_task_window(None,{'user_inactive_time':time_since_last_tick})
|
|
291
280
|
else:
|
|
292
281
|
self.session['start_time'] = now()
|
|
293
282
|
|
|
@@ -312,7 +301,6 @@ class Application(Gtk.Application):
|
|
|
312
301
|
|
|
313
302
|
if duration > 60 and num_is_multiple_of(duration, 2):
|
|
314
303
|
icon = 'icon-red.svg'
|
|
315
|
-
|
|
316
304
|
else:
|
|
317
305
|
icon = 'icon-1.svg'
|
|
318
306
|
|
|
@@ -321,13 +309,28 @@ class Application(Gtk.Application):
|
|
|
321
309
|
self.indicator.set_icon_full(os.path.abspath('icon/'+icon),label)
|
|
322
310
|
self.indicator.set_label(label,label)
|
|
323
311
|
|
|
312
|
+
if num_is_multiple_of(duration,5): # check every 5 seconds
|
|
313
|
+
if self.device_in_use:
|
|
314
|
+
self.refresh_all_changed_todo_files()
|
|
315
|
+
elif self.get_seconds_since_user_active() < 60:
|
|
316
|
+
self.device_in_use = True
|
|
317
|
+
self.handle_device_became_active()
|
|
318
|
+
else:
|
|
319
|
+
# TODO: incriment a user away timer
|
|
320
|
+
dbg("User is still away")
|
|
324
321
|
|
|
325
|
-
if num_is_multiple_of(self.icon_tick_number,3):
|
|
326
|
-
self.refresh_all_changed_todo_files()
|
|
327
|
-
|
|
328
322
|
return True
|
|
329
323
|
|
|
330
324
|
|
|
325
|
+
def handle_device_became_active(self):
|
|
326
|
+
dbg('device_became_active! ',l=-1)
|
|
327
|
+
self.user_last_active = now()
|
|
328
|
+
|
|
329
|
+
if self.is_running == False:
|
|
330
|
+
self.session['start_time'] = now()
|
|
331
|
+
self.session['duration'] = 0
|
|
332
|
+
|
|
333
|
+
|
|
331
334
|
def refresh_all_changed_todo_files(self):
|
|
332
335
|
for id, todo_config in conf.user['todolists'].items():
|
|
333
336
|
if todo_config['status'] and 'watch_file' in todo_config:
|
|
@@ -352,6 +355,20 @@ class Application(Gtk.Application):
|
|
|
352
355
|
handle_todo_read_error(todo_config,e)
|
|
353
356
|
|
|
354
357
|
|
|
358
|
+
def resume_session_from_db(self):
|
|
359
|
+
try:
|
|
360
|
+
db_session = db_query("SELECT value FROM system WHERE field = 'session'")
|
|
361
|
+
|
|
362
|
+
if db_session:
|
|
363
|
+
s = json.loads(db_session[0]['value'])
|
|
364
|
+
s['start_time'] = datetime.strptime(s['start_time'],'%Y-%m-%d %H:%M:%S.%f')
|
|
365
|
+
self.session = s
|
|
366
|
+
self.is_running = True
|
|
367
|
+
dbg("resuming session",s['label'],l=2, s='session')
|
|
368
|
+
except Exception as e:
|
|
369
|
+
dbg("Error resuming session",e,l=1)
|
|
370
|
+
|
|
371
|
+
|
|
355
372
|
def start_task(self, w = None, task_data_or_id = None, transfer_current_session_time = False):
|
|
356
373
|
|
|
357
374
|
if isinstance(task_data_or_id, dict):
|
|
@@ -420,7 +437,7 @@ class Application(Gtk.Application):
|
|
|
420
437
|
self.task_running_menu_additions()
|
|
421
438
|
|
|
422
439
|
self.menu.show_all()
|
|
423
|
-
|
|
440
|
+
dbg("Starting",task_data['extended_label'])
|
|
424
441
|
|
|
425
442
|
if task_data['id'] in conf.user['task_commands']:
|
|
426
443
|
command_data = conf.user['task_commands'][task_data['id']]
|
|
@@ -431,7 +448,7 @@ class Application(Gtk.Application):
|
|
|
431
448
|
|
|
432
449
|
|
|
433
450
|
def run_task_command(self,command):
|
|
434
|
-
|
|
451
|
+
dbg("running task command:", command,l=-1)
|
|
435
452
|
|
|
436
453
|
self.running_command_task_label = copy.copy(self.session['label'])
|
|
437
454
|
|
|
@@ -736,24 +753,20 @@ class Application(Gtk.Application):
|
|
|
736
753
|
dbg('Signal received',sig,s='signals')
|
|
737
754
|
|
|
738
755
|
if sig == signal.SIGUSR1:
|
|
739
|
-
# try:
|
|
740
|
-
# print('TaskWindow._instance',TaskWindow._instance)
|
|
741
|
-
# if TaskWindow._instance:
|
|
742
|
-
# self.taskwindow.destroy()
|
|
743
|
-
# else:
|
|
744
|
-
# self.open_task_window()
|
|
745
|
-
|
|
746
|
-
# except AttributeError:
|
|
747
756
|
self.open_task_window()
|
|
748
|
-
|
|
749
757
|
elif sig == signal.SIGUSR2:
|
|
750
758
|
self.open_session_options_dialog()
|
|
751
|
-
|
|
752
759
|
else:
|
|
753
760
|
dbg("no handler for received signal",s='signals',l=3)
|
|
754
761
|
|
|
762
|
+
|
|
763
|
+
def start_pipe(self):
|
|
764
|
+
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
765
|
+
self.pipethread.daemon = True
|
|
766
|
+
self.pipethread.start()
|
|
767
|
+
|
|
768
|
+
|
|
755
769
|
def check_pipe(self):
|
|
756
|
-
# print("Listening to pipe at ",conf.pipe)
|
|
757
770
|
|
|
758
771
|
try:
|
|
759
772
|
with open(conf.pipe, "r") as pipe:
|
|
@@ -823,7 +836,6 @@ def startup():
|
|
|
823
836
|
except Exception as e:
|
|
824
837
|
print(e)
|
|
825
838
|
|
|
826
|
-
|
|
827
839
|
try:
|
|
828
840
|
os.mkfifo(conf.pipe)
|
|
829
841
|
dbg("Named pipe created successfully!", s="cli")
|
|
@@ -839,7 +851,7 @@ def startup():
|
|
|
839
851
|
|
|
840
852
|
|
|
841
853
|
except FileExistsError:
|
|
842
|
-
|
|
854
|
+
print("Named pipe exists, application must be running (or improperly shut down.) ")
|
|
843
855
|
|
|
844
856
|
# if args: pass to pipe and exit
|
|
845
857
|
if args.task:
|
nowfocus/conf.py
CHANGED
|
@@ -18,11 +18,14 @@ user_data_dir = GLib.get_user_data_dir()+"/"+app_id
|
|
|
18
18
|
Path(user_data_dir).mkdir(parents=True, exist_ok=True)
|
|
19
19
|
|
|
20
20
|
user_settings_dir = GLib.get_user_config_dir()+"/"+app_id
|
|
21
|
-
|
|
21
|
+
''' Depreciated. use user_data_dir instead '''
|
|
22
22
|
|
|
23
|
+
db_file = user_data_dir+"/data.db"
|
|
24
|
+
settings_file = user_data_dir+"/nowfocus-settings.json"
|
|
23
25
|
debug_level = 1 # dev value
|
|
24
26
|
debug_systems = []
|
|
25
|
-
pipe = "/tmp/"+app_id+"-pipe"
|
|
27
|
+
pipe = "/tmp/"+app_id+"-pipe"
|
|
28
|
+
is_first_load = False
|
|
26
29
|
|
|
27
30
|
# key and type must be the same, (Seems redundant but it's is quite helpful)
|
|
28
31
|
connectors = {
|
|
@@ -59,9 +62,10 @@ prototype_settings = {
|
|
|
59
62
|
"pomodoro_interval": 40,
|
|
60
63
|
"open_task_window_fullscreen": True,
|
|
61
64
|
"show_task_window_sidebars": False,
|
|
62
|
-
"randomness_interrupt_interval":
|
|
65
|
+
"randomness_interrupt_interval":7, #minutes
|
|
66
|
+
"device_not_in_use_threshold":3.5, #minutes
|
|
63
67
|
"default_text": "What am I doing?",
|
|
64
|
-
"todolist_refresh_interval":1,
|
|
68
|
+
"todolist_refresh_interval":1, #hours
|
|
65
69
|
"version":0.4,
|
|
66
70
|
"display_todolist_as_top_level_list":'auto',
|
|
67
71
|
'max_top_level_menu_items':10,
|
|
@@ -69,7 +73,7 @@ prototype_settings = {
|
|
|
69
73
|
'invoice_hourly_rate':0,
|
|
70
74
|
|
|
71
75
|
"custom_pomodoro_intervals": {
|
|
72
|
-
"email":7
|
|
76
|
+
"email":7 #minutes
|
|
73
77
|
},
|
|
74
78
|
|
|
75
79
|
'prompts':'What am I doing?\nWhy am I here?\nWhat could I do?\nWhat do I wish to accomplish?\nWhat is my aim?\nWhat\'s next',
|
|
@@ -84,11 +88,11 @@ prototype_settings = {
|
|
|
84
88
|
},
|
|
85
89
|
|
|
86
90
|
"todolists": {
|
|
87
|
-
"
|
|
88
|
-
"id":"
|
|
91
|
+
"Nowfocus Todo":{
|
|
92
|
+
"id":"Nowfocus Todo",
|
|
89
93
|
"type": "txt",
|
|
90
|
-
"label": "
|
|
91
|
-
"file":
|
|
94
|
+
"label": "Nowfocus Todo",
|
|
95
|
+
"file": user_data_dir+'/nowfocus-todo.txt',
|
|
92
96
|
"timetracker":"Example CSV",
|
|
93
97
|
"status":True
|
|
94
98
|
}
|
|
@@ -98,36 +102,43 @@ prototype_settings = {
|
|
|
98
102
|
"id": "Example CSV",
|
|
99
103
|
"label": "Example CSV",
|
|
100
104
|
"type":"csv",
|
|
101
|
-
"file":
|
|
105
|
+
"file":user_data_dir+'/nowfocus-timetracking-spreadsheet.csv',
|
|
102
106
|
"status":True
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
if os.path.isfile(
|
|
111
|
+
if os.path.isfile(settings_file):
|
|
112
|
+
with open(settings_file, "r") as file:
|
|
113
|
+
settings = json.load(file)
|
|
114
|
+
|
|
115
|
+
# Backward compatibility, check in old settings dir
|
|
116
|
+
elif os.path.isfile(user_settings_dir+"/user_settings.json"):
|
|
108
117
|
with open(user_settings_dir+"/user_settings.json", "r") as file:
|
|
109
|
-
|
|
118
|
+
settings = json.load(file)
|
|
110
119
|
else:
|
|
120
|
+
|
|
111
121
|
print("Setting up initial settings file")
|
|
112
|
-
|
|
122
|
+
is_first_load = True
|
|
123
|
+
settings = {}
|
|
113
124
|
|
|
114
125
|
|
|
115
126
|
user = {}
|
|
116
127
|
|
|
117
|
-
# Merge
|
|
128
|
+
# Merge settings with prototype settings
|
|
118
129
|
for key, val in prototype_settings.items():
|
|
119
130
|
|
|
120
|
-
if
|
|
131
|
+
if settings and key in connectors:
|
|
121
132
|
user[key] = {}
|
|
122
133
|
|
|
123
134
|
# merge and validate todolist and timetracker settings against connector prototypes
|
|
124
|
-
for c_key, c_val in
|
|
135
|
+
for c_key, c_val in settings[key].items():
|
|
125
136
|
|
|
126
137
|
if c_val['type'] in connectors[key]:
|
|
127
138
|
proto = copy.copy(connectors[key][c_val['type']])
|
|
128
139
|
user[key][c_key] = proto
|
|
129
140
|
else:
|
|
130
|
-
print('ERROR: no connector for type '+c_val['type']+' in
|
|
141
|
+
print('ERROR: no connector for type '+c_val['type']+' in settings_file','skipping that connector',c_val)
|
|
131
142
|
continue
|
|
132
143
|
|
|
133
144
|
# print('validate user_settings connector against prototype.','user',c_val,'proto',proto)
|
|
@@ -142,46 +153,28 @@ for key, val in prototype_settings.items():
|
|
|
142
153
|
else:
|
|
143
154
|
print("Adding missing: connection field "+p_field+")")
|
|
144
155
|
|
|
145
|
-
elif key in
|
|
146
|
-
user[key] =
|
|
156
|
+
elif key in settings and type(settings[key]) == type(val):
|
|
157
|
+
user[key] = settings[key]
|
|
147
158
|
else:
|
|
148
159
|
user[key] = val
|
|
149
160
|
|
|
150
161
|
# print(json.dumps(user, indent=4))
|
|
151
162
|
# print(json.dumps(connectors['todolists'], indent=4))
|
|
152
163
|
|
|
153
|
-
# update time_target format
|
|
154
|
-
for id, tt in user['time_targets']['lists'].items():
|
|
155
|
-
if 'within_value' not in tt:
|
|
156
|
-
print("Updating time target to new format ",tt)
|
|
157
|
-
tt['within_value'] = tt['num_days']
|
|
158
|
-
tt['within_unit'] = 'days'
|
|
159
|
-
print(tt)
|
|
160
|
-
if 'status' not in tt:
|
|
161
|
-
tt['status'] = True
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
for id, tt in user['time_targets']['tasks'].items():
|
|
165
|
-
tt
|
|
166
|
-
if 'within_value' not in tt:
|
|
167
|
-
print("Updating time target to new format ",tt)
|
|
168
|
-
tt['within_value'] = tt['num_days']
|
|
169
|
-
tt['within_unit'] = 'days'
|
|
170
|
-
print(tt)
|
|
171
|
-
if 'status' not in tt:
|
|
172
|
-
tt['status'] = True
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
with open(user_settings_dir+"/user_settings.json","w") as settings_file:
|
|
176
|
-
json.dump(user, settings_file)
|
|
177
164
|
|
|
165
|
+
with open(settings_file,"w") as file:
|
|
166
|
+
json.dump(user, file)
|
|
178
167
|
|
|
179
168
|
todo_connectors = {}
|
|
180
169
|
timetracker_connectors = {}
|
|
181
170
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
171
|
+
try:
|
|
172
|
+
|
|
173
|
+
for id, todolist in user['todolists'].items():
|
|
174
|
+
todo_connectors[todolist['type']] = importlib.import_module('connectors.'+todolist['type'])
|
|
175
|
+
|
|
176
|
+
for id, timetracker in user['timetrackers'].items():
|
|
177
|
+
timetracker_connectors[timetracker['type']] = importlib.import_module('connectors.'+timetracker['type'])
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print("error loading connector")
|
|
180
|
+
print(e)
|
nowfocus/example-todo.txt
CHANGED
nowfocus/install.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
import json
|
|
3
|
+
import importlib
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import copy
|
|
7
|
+
import subprocess, sys
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
import gi
|
|
11
|
+
gi.require_version('Gtk', '3.0')
|
|
12
|
+
from gi.repository import Gtk, GLib, Gdk, Gio
|
|
13
|
+
|
|
14
|
+
# Set working dir to file location
|
|
15
|
+
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
16
|
+
|
|
17
|
+
# Add working dir to path
|
|
18
|
+
sys.path.append(os.path.dirname(__file__))
|
|
19
|
+
|
|
20
|
+
import conf
|
|
21
|
+
from utils import *
|
|
22
|
+
|
|
23
|
+
def run_first_load_actions():
|
|
24
|
+
db_init()
|
|
25
|
+
create_default_timetracking_csv()
|
|
26
|
+
create_default_todo()
|
|
27
|
+
copy_desktop_integration_files()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_default_timetracking_csv():
|
|
31
|
+
target_file = conf.user_data_dir+'/nowfocus-timetracking-spreadsheet.csv'
|
|
32
|
+
|
|
33
|
+
data = 'date,duration,project,task,start time'
|
|
34
|
+
|
|
35
|
+
if not os.path.isfile(target_file):
|
|
36
|
+
with open(target_file, 'w') as file:
|
|
37
|
+
file.writelines(data)
|
|
38
|
+
print("Created default timetracker at "+target_file)
|
|
39
|
+
else:
|
|
40
|
+
print("Default timetracker already exists at "+target_file)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_default_todo():
|
|
44
|
+
source_file = 'example-todo.txt'
|
|
45
|
+
target_file = conf.user_data_dir+'/nowfocus-todo.txt'
|
|
46
|
+
|
|
47
|
+
if not os.path.isfile(target_file):
|
|
48
|
+
with open(source_file, 'r') as file:
|
|
49
|
+
data = file.readlines()
|
|
50
|
+
|
|
51
|
+
with open(target_file, 'w') as file:
|
|
52
|
+
file.writelines(data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def db_init():
|
|
56
|
+
if not os.path.isfile(conf.db_file):
|
|
57
|
+
print('initializing database')
|
|
58
|
+
|
|
59
|
+
db_query("CREATE TABLE lists (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT DEFAULT '', data TEXT DEFAULT '{}');")
|
|
60
|
+
|
|
61
|
+
db_query("CREATE TABLE tasks (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT, data TEXT DEFAULT '{}');")
|
|
62
|
+
|
|
63
|
+
# , tags TEXT
|
|
64
|
+
|
|
65
|
+
db_query("CREATE TABLE sessions (start_time TEXT, duration INTEGER, task_id TEXT, parent_id TEXT, todolist TEXT, extended_label TEXT, notes TEXT, priority INTEGER DEFAULT 0, timetracker TEXT);")
|
|
66
|
+
|
|
67
|
+
db_query("CREATE TABLE system (field TEXT PRIMARY KEY NOT NULL, value TEXT);")
|
|
68
|
+
|
|
69
|
+
db_query("INSERT INTO system(field, value) VALUES('db_schema_version', '0.5')")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def copy_desktop_integration_files():
|
|
73
|
+
|
|
74
|
+
home = GLib.get_home_dir()
|
|
75
|
+
|
|
76
|
+
files = (
|
|
77
|
+
(home+'/.local/share/icons/hicolor/scalable/apps','nowfocus.svg'),
|
|
78
|
+
(home+'/.local/share/icons','nowfocus.png'),
|
|
79
|
+
(home+'/.local/share/applications/','nowfocus.desktop'),
|
|
80
|
+
(home+'/.config/autostart/','nowfocus.desktop')
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
for file in files:
|
|
85
|
+
try:
|
|
86
|
+
Path(file[0]).mkdir(parents=True, exist_ok=True)
|
|
87
|
+
shutil.copy("desktop-extras/"+file[1],file[0])
|
|
88
|
+
print("Copied "+file[1]+" to "+file[0])
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print("Error Copying "+file[1]+" to "+file[0],e)
|
|
92
|
+
|
nowfocus/session_options.py
CHANGED
|
@@ -139,7 +139,7 @@ class SessionOptionsDialog(Gtk.Dialog):
|
|
|
139
139
|
|
|
140
140
|
print(' start_time + ', session['start_time'])
|
|
141
141
|
|
|
142
|
-
# Or change session[duration] value
|
|
142
|
+
# Or change session[duration] value to reflect dropped session
|
|
143
143
|
if response == 3:
|
|
144
144
|
# Create custom end time
|
|
145
145
|
custom_end_time = session['start_time'] + timedelta(seconds = (self.duration_input.get_value_as_int() * 60))
|
nowfocus/task_window.py
CHANGED
|
@@ -117,15 +117,15 @@ class TaskWindow(Gtk.Window):
|
|
|
117
117
|
|
|
118
118
|
if passed_data:
|
|
119
119
|
dbg('taskwindow passed_data',passed_data,s='taskwindow')
|
|
120
|
-
if '
|
|
120
|
+
if 'user_inactive_time' in passed_data:
|
|
121
121
|
|
|
122
|
-
last_active_time = datetime.now() - timedelta(seconds=passed_data['
|
|
122
|
+
last_active_time = datetime.now() - timedelta(seconds=passed_data['user_inactive_time'])
|
|
123
123
|
|
|
124
124
|
last_active_str = time.strftime('%H:%M', last_active_time.timetuple())
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
self.header.add(
|
|
126
|
+
user_inactive_label = Gtk.Label()
|
|
127
|
+
user_inactive_label.set_markup("<b>Inactive Since "+" "+str(last_active_str) +"</b>")
|
|
128
|
+
self.header.add(user_inactive_label)
|
|
129
129
|
|
|
130
130
|
pause_then_button = Gtk.Button(label="Finish Then")
|
|
131
131
|
|
|
@@ -390,7 +390,7 @@ class TaskWindow(Gtk.Window):
|
|
|
390
390
|
self.r_sidebar.set_size_request(350, -1)
|
|
391
391
|
|
|
392
392
|
focus_percent_label = Gtk.Label()
|
|
393
|
-
focus_percent_label.set_markup("<b>
|
|
393
|
+
focus_percent_label.set_markup("<b>"+str(utils.get_percent_time_focused())+"% Focused. "+str(utils.get_percent_time_priority())+"% Priority Tasks</b>")
|
|
394
394
|
self.r_sidebar.pack_start(focus_percent_label, False, True, 25)
|
|
395
395
|
|
|
396
396
|
self.sessions_scrolledwindow = Gtk.ScrolledWindow()
|
nowfocus/utils.py
CHANGED
|
@@ -29,75 +29,46 @@ notify.init(conf.app_name)
|
|
|
29
29
|
|
|
30
30
|
lists = {}
|
|
31
31
|
|
|
32
|
-
db_file = conf.user_data_dir+"/data.db"
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
If the optional 'notify' arg is true, a notification will be generated.
|
|
42
|
-
|
|
43
|
-
if the optional 'e' arg is present and an exception object its traceback will be printed
|
|
33
|
+
def dbg(*data, s="", l=2, e=None, notify=None):
|
|
34
|
+
''' Any number of positional args optionally
|
|
35
|
+
l: level {-1: Default output, 0:Error, 1:Warning, 2:Info, 3:Details}
|
|
36
|
+
s: system (Debuggable systems: 'taskwindow','signals','todoloading','user_settings', 'targets', 'performance')
|
|
37
|
+
notify: Show a notification with the content of notify.
|
|
38
|
+
e (Exception object) traceback will be printed
|
|
44
39
|
|
|
45
40
|
'''
|
|
46
41
|
|
|
47
42
|
levels = {-1:'', 0:'Error', 1:'Warning', 2:'Info', 3:'Details'}
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
level = 2
|
|
52
|
-
|
|
53
|
-
if 's' in kwargs: system = kwargs['s']
|
|
44
|
+
system = s
|
|
45
|
+
level = int(l)
|
|
54
46
|
|
|
55
|
-
if 'l' in kwargs: level = int(kwargs['l'])
|
|
56
|
-
|
|
57
|
-
if 'e' in kwargs and isinstance(kwargs['e'],Exception):
|
|
58
|
-
print(data)
|
|
59
|
-
traceback.print_tb(kwargs['e'].__traceback__)
|
|
60
47
|
|
|
61
48
|
if "all" in conf.debug_systems or system in conf.debug_systems or level <= conf.debug_level:
|
|
62
49
|
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
notify.Notification.new(conf.app_name+" "+levels[level], str(kwargs['notify']), None).show()
|
|
50
|
+
if notify:
|
|
51
|
+
notify.Notification.new(conf.app_name+" "+levels[level], str(notify), None).show()
|
|
66
52
|
|
|
67
|
-
# o = "Debug "+system+" "+levels[level], tuple(filter(None,data))
|
|
68
53
|
filter(None,data)
|
|
69
54
|
if len(data) == 1:
|
|
70
55
|
data = data[0]
|
|
71
56
|
if system:
|
|
72
57
|
system = "- "+system
|
|
73
58
|
|
|
74
|
-
print(data, system+" "+levels[level])
|
|
75
|
-
|
|
76
|
-
# if isinstance(data,str):
|
|
77
|
-
# # print('str dbg data')
|
|
78
|
-
# print(data, system+" "+levels[level])
|
|
79
|
-
# else:
|
|
80
|
-
# print('dbg data',type(data),len(data))
|
|
81
|
-
# print(data)
|
|
82
|
-
# # o = tuple(filter(None,data)), "("+system+" "+levels[level]+")"
|
|
83
|
-
# o = data, system+" "+levels[level]
|
|
84
|
-
# pretty_print(o)
|
|
59
|
+
print(json.dumps(data,indent=2), system+" "+levels[level])
|
|
85
60
|
|
|
86
61
|
# else:
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
# # pretty_print(data)
|
|
62
|
+
# print("Not displaying dbg s:",system,'conf.debug_systems',conf.debug_systems)
|
|
63
|
+
# pretty_print(data)
|
|
90
64
|
|
|
65
|
+
if e and isinstance(e,Exception):
|
|
66
|
+
traceback.print_tb(e.__traceback__)
|
|
91
67
|
|
|
92
68
|
def error_notice(title, details = None, e = None):
|
|
93
69
|
print('ERROR',title,details,e)
|
|
94
|
-
|
|
95
70
|
notify.Notification.new(str(title),str(details), None).show()
|
|
96
71
|
|
|
97
|
-
# try:
|
|
98
|
-
# raise FileExistsError("This is not except-able!")
|
|
99
|
-
# except Exception as e:
|
|
100
|
-
# dbg('test exception',e=e)
|
|
101
72
|
|
|
102
73
|
def pretty_dict(i, item_sep = ", ", use_indents=True, indent=''):
|
|
103
74
|
o = ''
|
|
@@ -130,15 +101,19 @@ def pretty_print(i):
|
|
|
130
101
|
|
|
131
102
|
|
|
132
103
|
def timeit(process=None):
|
|
104
|
+
# if 'performance' in conf.debug_systems or conf.debug_level >= 2:
|
|
133
105
|
if not process:
|
|
134
106
|
process = inspect.currentframe().f_back.f_code.co_name
|
|
135
107
|
|
|
136
108
|
if process in conf.timers.keys():
|
|
137
|
-
|
|
109
|
+
timer = time.time() - conf.timers[process]
|
|
110
|
+
dbg(process, 'took', timer, 'seconds', s='performance',l=2)
|
|
138
111
|
del conf.timers[process]
|
|
112
|
+
return timer
|
|
139
113
|
else:
|
|
140
|
-
|
|
114
|
+
dbg(process, 'performance timer started',s='performance',l=3)
|
|
141
115
|
conf.timers[process] = time.time()
|
|
116
|
+
return process
|
|
142
117
|
|
|
143
118
|
|
|
144
119
|
def time_to_sec(time=None):
|
|
@@ -318,7 +293,7 @@ def first(i, default = None):
|
|
|
318
293
|
def save_user_settings():
|
|
319
294
|
print("Save updated user_settings")
|
|
320
295
|
dbg(conf.user)
|
|
321
|
-
with open(conf.
|
|
296
|
+
with open(conf.settings_file,"w") as settings_file:
|
|
322
297
|
json.dump(conf.user, settings_file)
|
|
323
298
|
|
|
324
299
|
|
|
@@ -326,8 +301,7 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
|
326
301
|
'''error_handling: 2 = raise error, 1 = notify error, 0 = ignore errors '''
|
|
327
302
|
# print('db_query parameters type: ',type(parameters))
|
|
328
303
|
# print('first(parameters)',first(parameters))
|
|
329
|
-
|
|
330
|
-
c = sqlite3.connect(db_file)
|
|
304
|
+
c = sqlite3.connect(conf.db_file)
|
|
331
305
|
c.row_factory = sqlite3.Row
|
|
332
306
|
try:
|
|
333
307
|
with c:
|
|
@@ -368,23 +342,6 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
|
368
342
|
return []
|
|
369
343
|
|
|
370
344
|
|
|
371
|
-
def db_init():
|
|
372
|
-
if not os.path.isfile(db_file):
|
|
373
|
-
print('initializing database')
|
|
374
|
-
|
|
375
|
-
db_query("CREATE TABLE lists (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT DEFAULT '', data TEXT DEFAULT '{}');")
|
|
376
|
-
|
|
377
|
-
db_query("CREATE TABLE tasks (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT, data TEXT DEFAULT '{}');")
|
|
378
|
-
|
|
379
|
-
# , tags TEXT
|
|
380
|
-
|
|
381
|
-
db_query("CREATE TABLE sessions (start_time TEXT, duration INTEGER, task_id TEXT, parent_id TEXT, todolist TEXT, extended_label TEXT, notes TEXT, timetracker TEXT);")
|
|
382
|
-
|
|
383
|
-
db_query("CREATE TABLE system (field TEXT PRIMARY KEY NOT NULL, value TEXT);")
|
|
384
|
-
|
|
385
|
-
db_query("INSERT INTO system(field, value) VALUES('db_schema_version', '0.4')")
|
|
386
|
-
|
|
387
|
-
|
|
388
345
|
def db_schema_update():
|
|
389
346
|
|
|
390
347
|
try:
|
|
@@ -405,14 +362,12 @@ def db_schema_update():
|
|
|
405
362
|
|
|
406
363
|
db_query("ALTER TABLE sessions ADD COLUMN extended_label TEXT")
|
|
407
364
|
|
|
408
|
-
# add default values for priority and data
|
|
409
365
|
# Since these column are just a cache, replace instead of copying columns
|
|
410
366
|
db_query("ALTER TABLE tasks DROP COLUMN priority")
|
|
411
367
|
db_query("ALTER TABLE tasks DROP COLUMN data")
|
|
412
368
|
db_query("ALTER TABLE lists DROP COLUMN priority")
|
|
413
369
|
db_query("ALTER TABLE lists DROP COLUMN data")
|
|
414
370
|
|
|
415
|
-
# add new columns
|
|
416
371
|
db_query("ALTER TABLE lists ADD COLUMN priority INTEGER DEFAULT 0")
|
|
417
372
|
db_query("ALTER TABLE lists ADD COLUMN data TEXT DEFAULT '{}'")
|
|
418
373
|
db_query("ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0")
|
|
@@ -439,16 +394,31 @@ def db_schema_update():
|
|
|
439
394
|
print('adding timetracker column to session table')
|
|
440
395
|
|
|
441
396
|
for todolist_id, todo in conf.user['todolists'].items():
|
|
442
|
-
print("set timetracker to ",todo['timetracker'], " for sessions from ",todolist_id)
|
|
443
|
-
|
|
444
397
|
db_query("UPDATE sessions SET timetracker = ? WHERE todolist = ?",(todo['timetracker'],todolist_id) )
|
|
445
398
|
|
|
446
399
|
db_schema_version = 0.4
|
|
447
400
|
|
|
448
|
-
|
|
401
|
+
if db_schema_version == 0.4:
|
|
402
|
+
print('Adding priority column to session table')
|
|
403
|
+
|
|
404
|
+
db_query("ALTER TABLE sessions ADD COLUMN priority INTEGER DEFAULT 0")
|
|
405
|
+
|
|
406
|
+
for session in db_query("SELECT DISTINCT task_id, extended_label FROM sessions"):
|
|
407
|
+
t = db_get_item_by_id(session['task_id'],dgb_error_level_for_failure=3)
|
|
408
|
+
if t and t['priority'] > 0:
|
|
409
|
+
print("setting",session['extended_label']," session priority to ",t['priority'] )
|
|
410
|
+
|
|
411
|
+
db_query("UPDATE sessions SET priority = ? WHERE task_id = ?",(t['priority'],session['task_id']))
|
|
412
|
+
|
|
413
|
+
print("Total hours priority sessions",round(divide(db_query("SELECT SUM(duration) as total FROM sessions WHERE priority > 0")[0]['total'],3600),2))
|
|
414
|
+
db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
|
|
415
|
+
|
|
416
|
+
db_schema_version = 0.5
|
|
417
|
+
|
|
418
|
+
# if db_schema_version == 0.5:
|
|
449
419
|
# db_query("ALTER TABLE tasks ADD COLUMN tags TEXT DEFAULT '{}'")
|
|
450
420
|
# db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
|
|
451
|
-
# db_schema_version = 0.
|
|
421
|
+
# db_schema_version = 0.6
|
|
452
422
|
|
|
453
423
|
|
|
454
424
|
dbg('db_schema_version', db_schema_version,s='db')
|
|
@@ -714,10 +684,10 @@ def db_cleanup(widget = None):
|
|
|
714
684
|
print('Relinking',s['task_id'],'to', match['id'])
|
|
715
685
|
db_query("UPDATE sessions SET task_id = ?, parent_id = ? WHERE task_id = ?",(match['id'],match['parent_id'],s['task_id']))
|
|
716
686
|
# print(pretty_print(possible_matches))
|
|
717
|
-
else:
|
|
687
|
+
# else:
|
|
718
688
|
# not very good yet...
|
|
719
|
-
search = db_query("SELECT *, rank FROM taskindex WHERE extended_label MATCH ? ORDER BY rank DESC",(s['extended_label'].replace('>','').replace('?',''),))
|
|
720
|
-
print('fuzzy search results',(search,))
|
|
689
|
+
# search = db_query("SELECT *, rank FROM taskindex WHERE extended_label MATCH ? ORDER BY rank DESC",(s['extended_label'].replace('>','').replace('?',''),))
|
|
690
|
+
# print('fuzzy search results',(search,))
|
|
721
691
|
|
|
722
692
|
# update extended_labels
|
|
723
693
|
# mismatched_sessions = db_query("SELECT * FROM sessions WHERE extended_label NOT IN (SELECT extended_label FROM tasks)")
|
|
@@ -730,7 +700,7 @@ def db_cleanup(widget = None):
|
|
|
730
700
|
|
|
731
701
|
# exit()
|
|
732
702
|
|
|
733
|
-
def db_get_item_by_id(id,table = 'tasks'):
|
|
703
|
+
def db_get_item_by_id(id,table = 'tasks',dgb_error_level_for_failure=0):
|
|
734
704
|
|
|
735
705
|
if table in ['task','list']:
|
|
736
706
|
table = table+'s'
|
|
@@ -744,11 +714,11 @@ def db_get_item_by_id(id,table = 'tasks'):
|
|
|
744
714
|
if data:
|
|
745
715
|
return proc_db_item(data[0],table)
|
|
746
716
|
else:
|
|
747
|
-
dbg('db_get_item_by_id from '+table+' failed for id
|
|
717
|
+
dbg('db_get_item_by_id from '+table+' failed for id',id,l=dgb_error_level_for_failure)
|
|
748
718
|
return {}
|
|
749
719
|
|
|
750
720
|
except Exception as e:
|
|
751
|
-
dbg('db_get_item_by_id failed',e,l=
|
|
721
|
+
dbg('db_get_item_by_id failed',e,l=dgb_error_level_for_failure)
|
|
752
722
|
|
|
753
723
|
|
|
754
724
|
def get_todo_by_id(todo_or_todo_id = None):
|
|
@@ -785,6 +755,7 @@ def db_get_todolist(todolist_id):
|
|
|
785
755
|
|
|
786
756
|
|
|
787
757
|
def db_save_session(session):
|
|
758
|
+
dbg("db_save_session",session)
|
|
788
759
|
prepared_session = {
|
|
789
760
|
'start_time' : str(session['start_time'].strftime("%Y-%m-%d %H:%M:%S")),
|
|
790
761
|
'duration': round(session['duration']),
|
|
@@ -794,9 +765,13 @@ def db_save_session(session):
|
|
|
794
765
|
'extended_label': str(session['extended_label']),
|
|
795
766
|
'timetracker': str(session['timetracker']),
|
|
796
767
|
'notes': str(session['notes']),
|
|
768
|
+
'priority': "0",
|
|
797
769
|
}
|
|
770
|
+
|
|
771
|
+
if 'priority' in session['task']:
|
|
772
|
+
prepared_session['priority']: str(session['task']['priority'])
|
|
798
773
|
|
|
799
|
-
db_query("INSERT INTO sessions(start_time, duration, task_id, parent_id, todolist, extended_label,timetracker, notes) VALUES(:start_time, :duration, :task_id, :parent_id, :todolist, :extended_label, :timetracker, :notes )",prepared_session)
|
|
774
|
+
db_query("INSERT INTO sessions(start_time, duration, task_id, parent_id, todolist, extended_label,timetracker, notes, priority) VALUES(:start_time, :duration, :task_id, :parent_id, :todolist, :extended_label, :timetracker, :notes, :priority )",prepared_session)
|
|
800
775
|
|
|
801
776
|
reindex(session['task'])
|
|
802
777
|
|
|
@@ -823,6 +798,18 @@ def default_session():
|
|
|
823
798
|
return o
|
|
824
799
|
|
|
825
800
|
|
|
801
|
+
def get_sessions(conditions_sql = None, order_by = 'start_time', limit = 35, use_sessions_timeframe_setting = False):
|
|
802
|
+
|
|
803
|
+
conditions = ''
|
|
804
|
+
|
|
805
|
+
if conditions_sql:
|
|
806
|
+
conditions += " AND "+conditions_sql
|
|
807
|
+
|
|
808
|
+
if use_sessions_timeframe_setting:
|
|
809
|
+
conditions += " AND "+sessions_timeframe_sql()
|
|
810
|
+
|
|
811
|
+
return db_query(" SELECT * FROM sessions WHERE 1=1 "+conditions+" ORDER BY "+order_by+" DESC LIMIT ? ",(limit,))
|
|
812
|
+
|
|
826
813
|
|
|
827
814
|
def show_sessions(widget = None, self = None, sessions_box = None, label_text = "Most Recent Sessions", order_by = 'start_time', limit = 35, passed_sessions = None, truncate = None ):
|
|
828
815
|
|
|
@@ -864,6 +851,7 @@ def show_sessions(widget = None, self = None, sessions_box = None, label_text =
|
|
|
864
851
|
|
|
865
852
|
sessions_box.show_all()
|
|
866
853
|
|
|
854
|
+
|
|
867
855
|
def db_set_session_cache(s):
|
|
868
856
|
''' Add active session to system db table. Not to be confused with db_save_session '''
|
|
869
857
|
db_session = copy.deepcopy(s)
|
|
@@ -909,12 +897,24 @@ def get_percent_time_focused():
|
|
|
909
897
|
|
|
910
898
|
focus_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE sessions.task_id != 'Randomness' AND "+ sessions_timeframe_sql())[0]['seconds']
|
|
911
899
|
|
|
912
|
-
percent = round(divide(focus_seconds,(focus_seconds + rand_seconds)) * 100
|
|
900
|
+
percent = round(divide(focus_seconds,(focus_seconds + rand_seconds)) * 100)
|
|
913
901
|
|
|
914
902
|
dbg('get_percent_time_focused', str(percent)+"%", 'focus_seconds', focus_seconds, 'rand_seconds', rand_seconds)
|
|
915
903
|
|
|
916
904
|
return percent
|
|
917
905
|
|
|
906
|
+
def get_percent_time_priority():
|
|
907
|
+
|
|
908
|
+
priority_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE sessions.task_id = 'Randomness' AND "+ sessions_timeframe_sql())[0]['seconds']
|
|
909
|
+
|
|
910
|
+
total_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE "+ sessions_timeframe_sql())[0]['seconds']
|
|
911
|
+
|
|
912
|
+
percent = round(divide(priority_seconds,(total_seconds)) * 100)
|
|
913
|
+
|
|
914
|
+
dbg('get_percent_time_focused', str(percent)+"%", 'focus_seconds', priority_seconds, 'rand_seconds', total_seconds)
|
|
915
|
+
|
|
916
|
+
return percent
|
|
917
|
+
|
|
918
918
|
|
|
919
919
|
def get_recent_tasks(count = 15):
|
|
920
920
|
data = db_query("SELECT DISTINCT tasks.* FROM tasks JOIN sessions ON sessions.task_id = tasks.id WHERE tasks.status = 1 GROUP BY tasks.id ORDER BY MAX(sessions.start_time) DESC LIMIT ?",(count,))
|
|
@@ -1087,7 +1087,6 @@ def handle_todo_file_read_error(todo_conf,e):
|
|
|
1087
1087
|
dbg(todo_conf['label']+ " Deactivated",e,l=-1,notify=True)
|
|
1088
1088
|
|
|
1089
1089
|
|
|
1090
|
-
|
|
1091
1090
|
def get_todolists(use_db_cache = False):
|
|
1092
1091
|
tasks = {}
|
|
1093
1092
|
lists = {}
|
|
@@ -1281,7 +1280,7 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1281
1280
|
list_menus[l['parent_id']].append(list_menu_items[list_id])
|
|
1282
1281
|
|
|
1283
1282
|
except Exception as e:
|
|
1284
|
-
dbg('Error adding list to menu',l,e=e,l=0)
|
|
1283
|
+
dbg('Error adding list to menu',l['extended_label'],e=e,l=0,s='menu')
|
|
1285
1284
|
|
|
1286
1285
|
|
|
1287
1286
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
# update time_target format
|
|
3
|
+
for id, tt in user['time_targets']['lists'].items():
|
|
4
|
+
if 'within_value' not in tt:
|
|
5
|
+
print("Updating time target to new format ",tt)
|
|
6
|
+
tt['within_value'] = tt['num_days']
|
|
7
|
+
tt['within_unit'] = 'days'
|
|
8
|
+
print(tt)
|
|
9
|
+
if 'status' not in tt:
|
|
10
|
+
tt['status'] = True
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
for id, tt in user['time_targets']['tasks'].items():
|
|
14
|
+
if 'within_value' not in tt:
|
|
15
|
+
print("Updating time target to new format ",tt)
|
|
16
|
+
tt['within_value'] = tt['num_days']
|
|
17
|
+
tt['within_unit'] = 'days'
|
|
18
|
+
print(tt)
|
|
19
|
+
if 'status' not in tt:
|
|
20
|
+
tt['status'] = True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nowfocus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: nowfocus: the open source task-tracking self-control panel.
|
|
5
5
|
Author: AltruistEnterprises
|
|
6
6
|
Project-URL: Homepage, https://www.nowfocus.org
|
|
@@ -112,13 +112,6 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
|
|
|
112
112
|
# Install nowfocus
|
|
113
113
|
pipx install nowfocus
|
|
114
114
|
|
|
115
|
-
# Enter application directory
|
|
116
|
-
cd ~/.local/share/pipx/venvs/nowfocus/lib/python3.12/site-packages/nowfocus/desktop-extras
|
|
117
|
-
|
|
118
|
-
# Copy desktop file and icon to /usr/share
|
|
119
|
-
sudo cp nowfocus.desktop /usr/share/applications/nowfocus.desktop
|
|
120
|
-
sudo cp nowfocus.svg /usr/share/icons/hicolor/scalable/apps/nowfocus.svg
|
|
121
|
-
|
|
122
115
|
# and now Focus!
|
|
123
116
|
nowfocus
|
|
124
117
|
|
|
@@ -130,7 +123,6 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
|
|
|
130
123
|
```
|
|
131
124
|
|
|
132
125
|
|
|
133
|
-
|
|
134
126
|
3. Add the following command to your startup applications:
|
|
135
127
|
```
|
|
136
128
|
nowfocus --force
|
|
@@ -178,7 +170,7 @@ Open nowfocus **Settings** from the indicator menu or tasks window and connect y
|
|
|
178
170
|
|
|
179
171
|
### Install From Source
|
|
180
172
|
```
|
|
181
|
-
git clone https://codeberg.org/AltruistEnterprises/nowfocus
|
|
173
|
+
git clone https://codeberg.org/AltruistEnterprises/nowfocus.git
|
|
182
174
|
cd nowfocus
|
|
183
175
|
python3 -m venv .venv/nowfocus-build
|
|
184
176
|
source .venv/nowfocus-build/bin/activate
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
nowfocus/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
-
nowfocus/__main__.py,sha256=
|
|
3
|
-
nowfocus/conf.py,sha256=
|
|
4
|
-
nowfocus/example-todo.txt,sha256=
|
|
2
|
+
nowfocus/__main__.py,sha256=pJhJsaKUFVbXTjZiQBxHZJ_aIcxkkVzm-FIhITtfc_Y,33014
|
|
3
|
+
nowfocus/conf.py,sha256=KV0iouYSSpYUITLlG-lPvmk1sHFVQsykwqmvb9wyTdk,6693
|
|
4
|
+
nowfocus/example-todo.txt,sha256=o-ZRNiTlSGFbTK9jpdDIi07qHBrpvP0JhBZSk0VlpQU,246
|
|
5
|
+
nowfocus/install.py,sha256=tcds4l6b492HTC7DfapatafgeMmDWvBfy8nwi1CjFXk,3040
|
|
5
6
|
nowfocus/new_task_dialog.py,sha256=GG49tOAwXiUKAHeaKnCG8Q3SZlL5pJijRZEcSjMwQng,4350
|
|
6
7
|
nowfocus/session_edit_dialog.py,sha256=V2QWSdNaxsQHRcG28CJBQM2sa45m5RNcu_suQF26mkM,6912
|
|
7
|
-
nowfocus/session_options.py,sha256=
|
|
8
|
+
nowfocus/session_options.py,sha256=mOnyEM3-usKgVvBhESY0TNBcy4FTG-AHP6ETMgB1fQ4,5071
|
|
8
9
|
nowfocus/sessions.csv,sha256=kYpr06yQg_J86NQ4AiYw4RnQchcw3ouPKVYa1lYDUNo,39
|
|
9
10
|
nowfocus/settings.py,sha256=Ufabuqd90tTDlzXB68jsbDz7233CEMsjnmPzaTSEHLI,35150
|
|
10
11
|
nowfocus/styles.css,sha256=PG1SrLkwSSay8M2VKeRcE0UdK54ndsEDFnRLRkmP-9M,510
|
|
11
|
-
nowfocus/task_window.py,sha256=
|
|
12
|
+
nowfocus/task_window.py,sha256=FgmjAIJqDJXAx2WkkAeTFSE3O0uednHSHh2j2v60Rdo,28478
|
|
12
13
|
nowfocus/user_idle_time.py,sha256=kPZ_bhoBdVMIBJXn2602FUfS4r-u8FnTq5Ze9D5QfHE,2363
|
|
13
|
-
nowfocus/utils.py,sha256=
|
|
14
|
+
nowfocus/utils.py,sha256=3oDo6F46F4j060wkQuhHCMvX4Ui_RNc1QSBE5JessAQ,50024
|
|
15
|
+
nowfocus/version_migrator.py,sha256=q8T1C8-DLOwUQUM5IPcMjPbVbsLTO4VsqADlAAXd9gw,628
|
|
14
16
|
nowfocus/connectors/activitywatch.py,sha256=QbkOmjIOiVwccWc2xhhePd0Abww5vEiVpCNjeqOyYGg,921
|
|
15
17
|
nowfocus/connectors/caldav.py,sha256=PeM_9yJC8W17L8Y5AyS75o6GfzTrPoMYKIvetND8T78,5089
|
|
16
18
|
nowfocus/connectors/csv.py,sha256=FwMpHM5lPIT90HKBCQUncpaW7zqFjlHjMwKR0-XWg-4,821
|
|
@@ -22,7 +24,7 @@ nowfocus/connectors/todotxt.py,sha256=QCZjbIhY4Lm37YD0GsKJQUqbj7s3eYmZGgRwUCndZ5
|
|
|
22
24
|
nowfocus/connectors/trello.py,sha256=VqwnvHGXXcljmdf6kRZcE6sfeBQYhped_KVBEBOzWXM,6072
|
|
23
25
|
nowfocus/connectors/txt.py,sha256=iskJsw3dZnI4bIeEDtZCY-aQfKRKtoGATEJ0k13npxI,8125
|
|
24
26
|
nowfocus/connectors/vikunja.py,sha256=Lg1lgF3C5B606CUf3-U8pVtPt7Cp6IKkT5wFN779V8w,10799
|
|
25
|
-
nowfocus/desktop-extras/nowfocus.desktop,sha256=
|
|
27
|
+
nowfocus/desktop-extras/nowfocus.desktop,sha256=PyCDMgm-aicRJkG2p1czLjBBp3BJz8vfLG7TjgJjZfY,297
|
|
26
28
|
nowfocus/desktop-extras/nowfocus.png,sha256=P5rn6-0EAJa2WXf4SJoaNtLRUfiV3LdsOroPKsR6GfA,15148
|
|
27
29
|
nowfocus/desktop-extras/nowfocus.svg,sha256=nps7naZzuhWWuKzQbpvxr9wLyzjmzMPzNHSBQMVetOo,2137
|
|
28
30
|
nowfocus/icon/cancel.png,sha256=Hy9A7KdO13MsXguIgoTI276mECN07rzd6hGbIqxSl7c,506
|
|
@@ -50,9 +52,9 @@ nowfocus/icon/settings.svg,sha256=fgkGJouPPtZLxZn2nr_5pEp9MdhRSRaW9mtdxhJHDuQ,39
|
|
|
50
52
|
nowfocus/sound/bell-xylophone-g.mp3,sha256=1OBcRWvD87AGNcq1uZFR8HqG0nanJykImERfVDVxHD4,53891
|
|
51
53
|
nowfocus/sound/dinner-bell.mp3,sha256=hjjO0xqA4uXpYw9KLwwlBnrVfRhVq1K5OXzwlMXhRn4,113620
|
|
52
54
|
nowfocus/sound/xylophone-chord.mp3,sha256=gwgBSqhMt5PMzT5N03Z6TvDgipQZfnkEz_o81Rq5Z1U,131806
|
|
53
|
-
nowfocus-0.
|
|
54
|
-
nowfocus-0.
|
|
55
|
-
nowfocus-0.
|
|
56
|
-
nowfocus-0.
|
|
57
|
-
nowfocus-0.
|
|
58
|
-
nowfocus-0.
|
|
55
|
+
nowfocus-0.5.0.dist-info/licenses/LICENSE,sha256=fSJzoHs1EOCwEd7FIyokFeGEma7NKmTVEdHkCr5OIV4,35127
|
|
56
|
+
nowfocus-0.5.0.dist-info/METADATA,sha256=OxqZcEYVb9dL-XLuMppeH3jC3DAeFzwnEpBB-ZntrIw,6805
|
|
57
|
+
nowfocus-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
58
|
+
nowfocus-0.5.0.dist-info/entry_points.txt,sha256=RbYY19-irSoNVglNeNnL9D36cHft7aKsaEGEYoSH3pA,51
|
|
59
|
+
nowfocus-0.5.0.dist-info/top_level.txt,sha256=3uLd9BwmfarZwqVUxkSJuVwJ8qHzjThte8rt_UYG7tE,9
|
|
60
|
+
nowfocus-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|