nowfocus 0.2.13__py3-none-any.whl → 0.4.2__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 +89 -83
- nowfocus/conf.py +4 -5
- nowfocus/connectors/todotxt.py +8 -3
- nowfocus/connectors/txt.py +14 -2
- nowfocus/connectors/vikunja.py +16 -17
- nowfocus/new_task_dialog.py +3 -4
- nowfocus/settings.py +10 -51
- nowfocus/styles.css +9 -11
- nowfocus/task_window.py +125 -66
- nowfocus/user_idle_time.py +82 -0
- nowfocus/utils.py +231 -127
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/METADATA +26 -24
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/RECORD +17 -16
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/WHEEL +0 -0
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/top_level.txt +0 -0
nowfocus/__main__.py
CHANGED
|
@@ -13,6 +13,7 @@ from playsound3 import playsound
|
|
|
13
13
|
import setproctitle
|
|
14
14
|
import psutil
|
|
15
15
|
import argparse
|
|
16
|
+
import traceback
|
|
16
17
|
|
|
17
18
|
import gi
|
|
18
19
|
gi.require_version('Gtk', '3.0')
|
|
@@ -28,7 +29,7 @@ except Exception as e:
|
|
|
28
29
|
gi.require_version('AppIndicator3', '0.1')
|
|
29
30
|
from gi.repository import AppIndicator3 as appindicator
|
|
30
31
|
|
|
31
|
-
from dbus_idle import IdleMonitor
|
|
32
|
+
# from dbus_idle import IdleMonitor
|
|
32
33
|
|
|
33
34
|
# Set working dir to file location
|
|
34
35
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
@@ -39,6 +40,8 @@ sys.path.append(os.path.dirname(__file__))
|
|
|
39
40
|
# from . import conf # this works in module context but not running as pile-of-files
|
|
40
41
|
import conf # this works running as pile-of-files but not in module context without sys.path.append
|
|
41
42
|
|
|
43
|
+
from user_idle_time import UserIdleTime
|
|
44
|
+
|
|
42
45
|
import utils
|
|
43
46
|
from utils import *
|
|
44
47
|
|
|
@@ -54,20 +57,24 @@ setproctitle.setproctitle(conf.app_name)
|
|
|
54
57
|
print(conf.app_name +" running from " + os.path.dirname(os.path.realpath(__file__)))
|
|
55
58
|
|
|
56
59
|
class Application(Gtk.Application):
|
|
60
|
+
icon_tick_number = 0
|
|
61
|
+
|
|
57
62
|
def __init__(self, *args, **kwargs):
|
|
58
|
-
super().__init__(*args, application_id="org.
|
|
63
|
+
super().__init__(*args, application_id="org.nowfocus.nowfocus", **kwargs)
|
|
64
|
+
|
|
65
|
+
# try:
|
|
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.
|
|
59
68
|
|
|
69
|
+
# except Exception as e:
|
|
70
|
+
# 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 ')
|
|
71
|
+
# traceback.print_tb(e.__traceback__)
|
|
72
|
+
# return None
|
|
73
|
+
|
|
60
74
|
self.window = None
|
|
61
75
|
|
|
62
76
|
self.is_running = False
|
|
63
|
-
self.session =
|
|
64
|
-
"label":"Randomness",
|
|
65
|
-
"extended_label": 'Randomness',
|
|
66
|
-
"start_time":datetime.now(),
|
|
67
|
-
"duration":0,
|
|
68
|
-
'task':{},
|
|
69
|
-
'notes':'',
|
|
70
|
-
}
|
|
77
|
+
self.session = default_session()
|
|
71
78
|
|
|
72
79
|
self.menu_tasks = {}
|
|
73
80
|
self.list_menus = {}
|
|
@@ -80,7 +87,7 @@ class Application(Gtk.Application):
|
|
|
80
87
|
# menu.set_reserve_toggle_size(False) # skip menu left padding, doesn't work
|
|
81
88
|
|
|
82
89
|
utils.db_init()
|
|
83
|
-
utils.
|
|
90
|
+
utils.db_schema_update()
|
|
84
91
|
|
|
85
92
|
|
|
86
93
|
# self.update_menu()
|
|
@@ -90,7 +97,6 @@ class Application(Gtk.Application):
|
|
|
90
97
|
|
|
91
98
|
self.indicator.set_menu(self.menu)
|
|
92
99
|
|
|
93
|
-
# main_tick_timer = GLib.timeout_add_seconds(conf.user['tick_interval'], self.tick)
|
|
94
100
|
main_tick_timer = GLib.timeout_add_seconds(1, self.tick)
|
|
95
101
|
|
|
96
102
|
try:
|
|
@@ -105,7 +111,7 @@ class Application(Gtk.Application):
|
|
|
105
111
|
except Exception as e:
|
|
106
112
|
dbg("Error resuming session",e,l=1)
|
|
107
113
|
|
|
108
|
-
|
|
114
|
+
self.UserIdleTime = UserIdleTime()
|
|
109
115
|
|
|
110
116
|
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
111
117
|
self.pipethread.daemon = True
|
|
@@ -133,7 +139,6 @@ class Application(Gtk.Application):
|
|
|
133
139
|
|
|
134
140
|
|
|
135
141
|
|
|
136
|
-
|
|
137
142
|
def print_time_totals(self = None, widget = None):
|
|
138
143
|
|
|
139
144
|
# SELECT extended_label, (SUM(duration) / 60 ) FROM sessions GROUP BY extended_label
|
|
@@ -157,39 +162,40 @@ class Application(Gtk.Application):
|
|
|
157
162
|
|
|
158
163
|
def quit(self, widget_or_signal_source=None, condition=None):
|
|
159
164
|
print("Adios ", conf.app_name)
|
|
160
|
-
# print("widget_or_signal_source ", widget_or_signal_source)
|
|
161
|
-
# print("condition ", condition)
|
|
162
165
|
|
|
163
166
|
if self.is_running:
|
|
164
|
-
|
|
167
|
+
dbg('Caching active session', self.session['label'])
|
|
165
168
|
db_set_session_cache(self.session)
|
|
169
|
+
|
|
166
170
|
try:
|
|
167
|
-
# print("before os.remove(conf.pipe)")
|
|
168
171
|
os.remove(conf.pipe)
|
|
169
|
-
print("Pipe removed")
|
|
170
172
|
except Exception as e:
|
|
171
|
-
|
|
173
|
+
dbd("Error removing conf.pipe in quit",e)
|
|
172
174
|
|
|
173
175
|
notify.uninit()
|
|
174
176
|
Gtk.main_quit()
|
|
175
177
|
exit()
|
|
176
178
|
|
|
177
179
|
|
|
178
|
-
def
|
|
180
|
+
def seconds_since_user_active(self):
|
|
179
181
|
# returns seconds of inactivity
|
|
180
|
-
# See https://stackoverflow.com/questions/67083083/how-to-get-idle-time-in-linux
|
|
182
|
+
# See https://stackoverflow.com/questions/67083083/how-to-get-idle-time-in-linux or
|
|
181
183
|
|
|
182
184
|
# Works on x11 but not wayland
|
|
183
185
|
# Requires xprintidle (sudo apt install xprintidle)
|
|
184
186
|
# idle_time = int(int(subprocess.getoutput('xprintidle')) / 1000)
|
|
185
187
|
|
|
186
|
-
#
|
|
188
|
+
# Version that works, using: https://github.com/bkbilly/dbus_idle
|
|
189
|
+
# but has many deps
|
|
187
190
|
# Requires:
|
|
188
191
|
# sudo apt install meson libdbus-glib-1-dev patchelf
|
|
189
192
|
# pip install dbus-idle
|
|
190
193
|
|
|
191
|
-
idle_time = int(int(IdleMonitor().get_dbus_idle()) / 1000)
|
|
192
|
-
return idle_time
|
|
194
|
+
# idle_time = int(int(IdleMonitor().get_dbus_idle()) / 1000)
|
|
195
|
+
# return idle_time
|
|
196
|
+
|
|
197
|
+
return self.UserIdleTime.get()
|
|
198
|
+
|
|
193
199
|
|
|
194
200
|
|
|
195
201
|
def toggle_do_not_disturb(self, widget):
|
|
@@ -210,7 +216,7 @@ class Application(Gtk.Application):
|
|
|
210
216
|
if 'do-not-disturb' in self.session:
|
|
211
217
|
return None
|
|
212
218
|
|
|
213
|
-
afk_time = self.
|
|
219
|
+
afk_time = self.seconds_since_user_active()
|
|
214
220
|
# print("Idle time: "+str(afk_time))
|
|
215
221
|
|
|
216
222
|
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 )
|
|
@@ -224,7 +230,7 @@ class Application(Gtk.Application):
|
|
|
224
230
|
|
|
225
231
|
if(self.is_running == False):
|
|
226
232
|
|
|
227
|
-
if
|
|
233
|
+
if num_is_multiple_of(minutes,conf.user['randomness_interrupt_interval']):
|
|
228
234
|
if afk_time < 30:
|
|
229
235
|
|
|
230
236
|
notify.Notification.new("What Am I doing?","Your randomness timer is at "+str(minutes)+" minutes. ", None).show()
|
|
@@ -243,7 +249,6 @@ class Application(Gtk.Application):
|
|
|
243
249
|
# only show this once
|
|
244
250
|
if afk_time < 181:
|
|
245
251
|
self.open_task_window(None,{'afk_time':afk_time})
|
|
246
|
-
# session_options_dialog(None, 'test input_data')
|
|
247
252
|
else:
|
|
248
253
|
if self.session['label'] in conf.user['custom_pomodoro_intervals']:
|
|
249
254
|
check = conf.user['custom_pomodoro_intervals'][self.session['label']]
|
|
@@ -260,7 +265,7 @@ class Application(Gtk.Application):
|
|
|
260
265
|
t = self.session['target']
|
|
261
266
|
|
|
262
267
|
t['percent'] = round(( (t['starting_value'] + minutes) / t['value'] ) * 100,1)
|
|
263
|
-
print("At
|
|
268
|
+
print("At", t['percent'], "% of ", t['scope'], " target")
|
|
264
269
|
|
|
265
270
|
if t['type'] == 'max':
|
|
266
271
|
if t['percent'] >= 100:
|
|
@@ -273,66 +278,80 @@ class Application(Gtk.Application):
|
|
|
273
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()
|
|
274
279
|
|
|
275
280
|
playsound('sound/xylophone-chord.mp3',False)
|
|
276
|
-
|
|
277
281
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
icon_tick_number = 0
|
|
281
|
-
|
|
282
|
+
|
|
282
283
|
def tick(self):
|
|
283
|
-
menu = self.menu
|
|
284
|
-
indicator = self.indicator
|
|
285
284
|
|
|
286
285
|
# check for suspend indicated by gap in tick intervals
|
|
287
|
-
time_since_last_tick = round(time_difference(self.session['start_time'])
|
|
286
|
+
time_since_last_tick = round(time_difference(self.session['start_time']) - self.session['duration'])
|
|
288
287
|
if time_since_last_tick > 10:
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
dbg(time_since_last_tick, " seconds since last tick. Probably just woke from suspend. ")
|
|
291
289
|
if self.is_running:
|
|
292
290
|
self.open_task_window(None,{'afk_time':time_since_last_tick})
|
|
293
291
|
else:
|
|
294
|
-
print("resetting randomness timer")
|
|
295
|
-
self.session['duration'] = 0
|
|
296
292
|
self.session['start_time'] = now()
|
|
297
293
|
|
|
298
|
-
|
|
299
|
-
self.session['duration'] = int(time_difference(self.session['start_time']))
|
|
294
|
+
duration = self.session['duration'] = int(time_difference(self.session['start_time']))
|
|
300
295
|
|
|
301
|
-
if
|
|
296
|
+
if num_is_multiple_of(duration,60):
|
|
302
297
|
self.tock()
|
|
303
298
|
|
|
304
|
-
if
|
|
305
|
-
self.icon_tick_number
|
|
299
|
+
if self.is_running == True:
|
|
300
|
+
self.icon_tick_number += 1
|
|
306
301
|
|
|
307
302
|
if self.icon_tick_number > 8:
|
|
308
303
|
self.icon_tick_number = 1
|
|
309
304
|
|
|
310
|
-
label = self.session['label'] + ": " + sec_to_time(
|
|
305
|
+
label = self.session['label'] + ": " + sec_to_time(duration)
|
|
306
|
+
|
|
307
|
+
icon = 'icon-'+str(self.icon_tick_number)+'.svg'
|
|
311
308
|
|
|
312
|
-
indicator.set_icon_full(os.path.abspath('icon/icon-'+str(self.icon_tick_number)+'.svg'),label)
|
|
313
|
-
|
|
314
309
|
else:
|
|
315
310
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if
|
|
319
|
-
|
|
311
|
+
label = conf.user['default_text'] +" "+ sec_to_time(duration)
|
|
312
|
+
|
|
313
|
+
if duration > 60 and num_is_multiple_of(duration, 2):
|
|
314
|
+
icon = 'icon-red.svg'
|
|
315
|
+
|
|
320
316
|
else:
|
|
321
|
-
|
|
317
|
+
icon = 'icon-1.svg'
|
|
322
318
|
|
|
323
319
|
# https://lazka.github.io/pgi-docs/#AyatanaAppIndicator3-0.1/classes/Indicator.html#AyatanaAppIndicator3.Indicator.set_label
|
|
324
|
-
indicator.set_label(label, "Wide")
|
|
325
320
|
|
|
326
|
-
|
|
327
|
-
|
|
321
|
+
self.indicator.set_icon_full(os.path.abspath('icon/'+icon),label)
|
|
322
|
+
self.indicator.set_label(label,label)
|
|
328
323
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
324
|
+
|
|
325
|
+
if num_is_multiple_of(self.icon_tick_number,3):
|
|
326
|
+
self.refresh_all_changed_todo_files()
|
|
327
|
+
|
|
333
328
|
return True
|
|
334
329
|
|
|
335
330
|
|
|
331
|
+
def refresh_all_changed_todo_files(self):
|
|
332
|
+
for id, todo_config in conf.user['todolists'].items():
|
|
333
|
+
if todo_config['status'] and 'watch_file' in todo_config:
|
|
334
|
+
self.refresh_todo_if_file_changed(todo_config)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def refresh_todo_if_file_changed(self, todo_config):
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
todo_m_time = round(os.stat(todo_config['file']).st_mtime)
|
|
341
|
+
|
|
342
|
+
if todo_config['id'] not in conf.todo_file_change_times:
|
|
343
|
+
conf.todo_file_change_times[todo_config['id']] = todo_m_time
|
|
344
|
+
|
|
345
|
+
elif todo_m_time != conf.todo_file_change_times[todo_config['id']]:
|
|
346
|
+
dbg(todo_config['id']+" file was changed!",s='todoloading')
|
|
347
|
+
self.async_refresh(None,todo_config)
|
|
348
|
+
conf.todo_file_change_times[todo_config['id']] = todo_m_time
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
# TODO: consider how to quietly handle errors when refresh is not prompted by the user and temporarily disabling
|
|
352
|
+
handle_todo_read_error(todo_config,e)
|
|
353
|
+
|
|
354
|
+
|
|
336
355
|
def start_task(self, w = None, task_data_or_id = None, transfer_current_session_time = False):
|
|
337
356
|
|
|
338
357
|
if isinstance(task_data_or_id, dict):
|
|
@@ -356,7 +375,10 @@ class Application(Gtk.Application):
|
|
|
356
375
|
|
|
357
376
|
if(self.is_running == True):
|
|
358
377
|
self.stop_task()
|
|
359
|
-
|
|
378
|
+
elif(self.session['duration'] > 60):
|
|
379
|
+
# Log randomness session (to internal db only) if longer than one minute
|
|
380
|
+
utils.db_save_session(self.session)
|
|
381
|
+
|
|
360
382
|
|
|
361
383
|
self.is_running = True
|
|
362
384
|
|
|
@@ -450,26 +472,20 @@ class Application(Gtk.Application):
|
|
|
450
472
|
try:
|
|
451
473
|
|
|
452
474
|
done_thread = threading.Thread(target=conf.todo_connectors[todolist_conf['type']].mark_task_done, args=(task,) )
|
|
475
|
+
|
|
453
476
|
conf.todo_sync_times[todolist_conf['id']] = now() # this is to avoid causing a refresh, perhaps not the best though
|
|
454
477
|
|
|
455
|
-
# Other Options:
|
|
456
|
-
# make a custom class extending Thread with callback method that runs del conf.file_watch_ignores[todolist_conf['id']]
|
|
457
|
-
# Complicated
|
|
458
|
-
# deal with file_watch_ignores in the connector
|
|
459
|
-
# poor seperation
|
|
460
|
-
#
|
|
461
478
|
done_thread.start()
|
|
462
479
|
|
|
463
480
|
db_query("UPDATE tasks set status = '0' WHERE id = ? ",(task['id'],) )
|
|
464
481
|
utils.reindex_one(task)
|
|
465
482
|
|
|
466
|
-
# print('remove menu item')
|
|
467
483
|
self.menu_tasks[task['id']].destroy()
|
|
468
|
-
|
|
484
|
+
|
|
469
485
|
playsound('sound/xylophone-chord.mp3',False)
|
|
470
486
|
|
|
471
487
|
except Exception as e:
|
|
472
|
-
error_notice('Error Marking Task Done'," Marking "+ task['label']+" as done in "+todolist_conf['label']+" had a serious failure",e )
|
|
488
|
+
error_notice('Error Marking Task Done'," Marking "+ task['label']+" as done in "+todolist_conf['label']+" had a serious failure", e=e )
|
|
473
489
|
|
|
474
490
|
|
|
475
491
|
|
|
@@ -533,19 +549,12 @@ class Application(Gtk.Application):
|
|
|
533
549
|
self.menu.show_all()
|
|
534
550
|
|
|
535
551
|
self.is_running = False
|
|
536
|
-
# print(utils.get_times(task))
|
|
537
552
|
if action != 'cancel':
|
|
538
553
|
notify.Notification.new("Focused on "+session['label']+" for "+sec_to_time(session['duration']),utils.pretty_dict(utils.get_times(task)), None).show()
|
|
539
554
|
# notify.Notification.new(action.capitalize()+" "+session['label']+" "+sec_to_time(session['duration']),utils.pretty_dict(utils.get_times(task)), None).show()
|
|
540
555
|
|
|
541
556
|
# Start randomness timer
|
|
542
|
-
self.session =
|
|
543
|
-
"label": 'Randomness',
|
|
544
|
-
"extended_label": 'Randomness',
|
|
545
|
-
"start_time": now(),
|
|
546
|
-
"duration":0,
|
|
547
|
-
"task":{}
|
|
548
|
-
}
|
|
557
|
+
self.session = default_session()
|
|
549
558
|
|
|
550
559
|
self.tick()
|
|
551
560
|
|
|
@@ -554,9 +563,6 @@ class Application(Gtk.Application):
|
|
|
554
563
|
self.menu.get_children()[0].destroy()
|
|
555
564
|
self.do_not_disturb_menu_item.set_label("Do Not Disturb")
|
|
556
565
|
|
|
557
|
-
else:
|
|
558
|
-
print('no task running!')
|
|
559
|
-
|
|
560
566
|
|
|
561
567
|
def task_running_menu_additions(self):
|
|
562
568
|
|
|
@@ -577,7 +583,7 @@ class Application(Gtk.Application):
|
|
|
577
583
|
|
|
578
584
|
def async_refresh(self, w=None, single_todo = None):
|
|
579
585
|
|
|
580
|
-
self.indicator.set_label("Refreshing Todolists", "
|
|
586
|
+
self.indicator.set_label("Refreshing Todolists", "Refreshing Todolists")
|
|
581
587
|
menu_item = Gtk.MenuItem.new_with_label("Refreshing Todolists")
|
|
582
588
|
self.menu.append(menu_item)
|
|
583
589
|
self.menu.show_all()
|
nowfocus/conf.py
CHANGED
|
@@ -52,20 +52,19 @@ connectors = {
|
|
|
52
52
|
todo_sync_time = datetime.now()
|
|
53
53
|
todo_sync_times = {}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
file_watch_ignores = {}
|
|
55
|
+
todo_file_change_times = {}
|
|
56
|
+
timers = {}
|
|
58
57
|
|
|
59
58
|
prototype_settings = {
|
|
60
59
|
"pomodoro_interval": 40,
|
|
61
60
|
"open_task_window_fullscreen": True,
|
|
61
|
+
"show_task_window_sidebars": False,
|
|
62
62
|
"randomness_interrupt_interval":5,
|
|
63
63
|
"default_text": "What am I doing?",
|
|
64
64
|
"todolist_refresh_interval":1,
|
|
65
|
-
"version":0.
|
|
65
|
+
"version":0.4,
|
|
66
66
|
"display_todolist_as_top_level_list":'auto',
|
|
67
67
|
'max_top_level_menu_items':10,
|
|
68
|
-
# 'tick_interval':1,
|
|
69
68
|
'hours_search_timeframe':'this year',
|
|
70
69
|
'invoice_hourly_rate':0,
|
|
71
70
|
|
nowfocus/connectors/todotxt.py
CHANGED
|
@@ -39,7 +39,6 @@ def add_new_task(user_conf,list,task_label):
|
|
|
39
39
|
|
|
40
40
|
todotxt.add(task)
|
|
41
41
|
todotxt.save()
|
|
42
|
-
# task.add_project
|
|
43
42
|
|
|
44
43
|
t = {
|
|
45
44
|
'id':task_label,
|
|
@@ -87,6 +86,7 @@ def get_todos(user_conf):
|
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
priority_letter_to_number_map = {'A':1,'B':2,'C':3,'D':4}
|
|
90
90
|
|
|
91
91
|
todotxt = pytodotxt.TodoTxt(user_conf['file'])
|
|
92
92
|
for t in todotxt.parse():
|
|
@@ -99,7 +99,6 @@ def get_todos(user_conf):
|
|
|
99
99
|
'parent_id':user_conf['id'],
|
|
100
100
|
'parent_label':user_conf['label'],
|
|
101
101
|
'status':1,
|
|
102
|
-
# 'priority':1 max, 5 min, 0 none,
|
|
103
102
|
'todolist':user_conf['id'],
|
|
104
103
|
'data':t.attributes #TODO: add other things like date etc
|
|
105
104
|
}
|
|
@@ -107,6 +106,12 @@ def get_todos(user_conf):
|
|
|
107
106
|
if t.is_completed:
|
|
108
107
|
tasks[id]['status'] = 0
|
|
109
108
|
|
|
109
|
+
if t.priority:
|
|
110
|
+
try:
|
|
111
|
+
tasks[id]['priority'] = priority_letter_to_number_map[t.priority]
|
|
112
|
+
except:
|
|
113
|
+
tasks[id]['priority'] = 5
|
|
114
|
+
|
|
110
115
|
|
|
111
116
|
if t.projects:
|
|
112
117
|
l = t.projects[0]
|
|
@@ -137,5 +142,5 @@ def get_todos(user_conf):
|
|
|
137
142
|
def launch(user_conf, item = None, category = None):
|
|
138
143
|
''' Open todolist '''
|
|
139
144
|
|
|
140
|
-
# It would b very nice to open
|
|
145
|
+
# It would b very nice to open the right line number but xdg-open doesn't support that...
|
|
141
146
|
utils.open_external(user_conf['file'])
|
nowfocus/connectors/txt.py
CHANGED
|
@@ -108,7 +108,7 @@ def mark_task_done(task):
|
|
|
108
108
|
if data[line_no].strip().startswith('[ ]'):
|
|
109
109
|
data[line_no] = data[line_no].replace('[ ]', '[x]')
|
|
110
110
|
else:
|
|
111
|
-
data[line_no] = data[line_no].replace(data[line_no].lstrip(), '[x]
|
|
111
|
+
data[line_no] = data[line_no].replace(data[line_no].lstrip(), '[x]'+ data[line_no].lstrip() )
|
|
112
112
|
|
|
113
113
|
# write everything back
|
|
114
114
|
with open(file_uri, 'w') as file:
|
|
@@ -176,7 +176,18 @@ def get_todos(user_conf):
|
|
|
176
176
|
status = 0
|
|
177
177
|
|
|
178
178
|
label = label.removeprefix('[ ]').removeprefix('[]').strip()
|
|
179
|
-
#TODO: use markdown title syntax as list name in addition to indentation
|
|
179
|
+
#TODO: use markdown title syntax as list name in addition to indentation?
|
|
180
|
+
|
|
181
|
+
# tags = ''
|
|
182
|
+
# # hashtag tagging
|
|
183
|
+
# if label.split("#").len() > 1:
|
|
184
|
+
# parts = label.split("#")
|
|
185
|
+
# print("label has hashtag",parts)
|
|
186
|
+
|
|
187
|
+
# label = parts[0].strip()
|
|
188
|
+
# del parts[0]
|
|
189
|
+
# tags = ",".join(parts)
|
|
190
|
+
|
|
180
191
|
|
|
181
192
|
indent = len(line) - len(line.lstrip())
|
|
182
193
|
indent_str = line[0:indent]
|
|
@@ -229,6 +240,7 @@ def get_todos(user_conf):
|
|
|
229
240
|
'todolist':user_conf['id'],
|
|
230
241
|
'status': status,
|
|
231
242
|
'priority': priority,
|
|
243
|
+
# 'tags':tags,
|
|
232
244
|
'data':{
|
|
233
245
|
'line_no':line_no,
|
|
234
246
|
'original_line':line,
|
nowfocus/connectors/vikunja.py
CHANGED
|
@@ -9,8 +9,6 @@ import requests
|
|
|
9
9
|
import conf
|
|
10
10
|
import utils
|
|
11
11
|
|
|
12
|
-
# UPDATE sessions SET task_id = "Vikunja_t" || task_id WHERE todolist = 'Vikunja';
|
|
13
|
-
# UPDATE sessions SET parent_id = "Vikunja_l" || parent_id WHERE todolist = 'Vikunja';
|
|
14
12
|
|
|
15
13
|
def vikunja_item_id(id, item_type, user_conf):
|
|
16
14
|
return user_conf['id'] +'_'+ item_type[0] + str(id)
|
|
@@ -20,7 +18,7 @@ def add_new_task(user_conf,parent_list,task_label):
|
|
|
20
18
|
headers = {'content-type': 'application/json'}
|
|
21
19
|
data = {'title': task_label}
|
|
22
20
|
|
|
23
|
-
utils.dbg('add_new_task() parent_list',parent_list, l
|
|
21
|
+
utils.dbg('add_new_task() parent_list',parent_list, l=-1)
|
|
24
22
|
|
|
25
23
|
response = requests.put(user_conf['url']+"api/v1/projects/"+str(parent_list['data']['id'])+"/tasks",json=data, headers=head)
|
|
26
24
|
|
|
@@ -104,7 +102,6 @@ def get_todos(user_conf):
|
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
|
|
107
|
-
priorities = {'DO NOW':1,'Urgent':2,'high':3}
|
|
108
105
|
headers = {"Authorization": "Bearer "+user_conf['token']}
|
|
109
106
|
projects_lists = requests.get(user_conf['url']+"api/v1/projects", headers=headers).json()
|
|
110
107
|
|
|
@@ -139,6 +136,9 @@ def get_todos(user_conf):
|
|
|
139
136
|
|
|
140
137
|
lists[id]['data']['accepts_tasks'] = True
|
|
141
138
|
|
|
139
|
+
# print(p['title']+' is_archived:',p['is_archived'])
|
|
140
|
+
# BUG: archived list aren't provided (But used to be ?) so sublists end up orphaned and dangerous...
|
|
141
|
+
|
|
142
142
|
if p['is_archived']:
|
|
143
143
|
lists[id]['status'] = -1
|
|
144
144
|
else:
|
|
@@ -152,6 +152,10 @@ def get_todos(user_conf):
|
|
|
152
152
|
if parent_id in lists:
|
|
153
153
|
lists[id]['parent_label'] = lists[parent_id]['label']
|
|
154
154
|
|
|
155
|
+
if lists[parent_id]['status'] == -1:
|
|
156
|
+
lists[id]['status'] = -1
|
|
157
|
+
|
|
158
|
+
|
|
155
159
|
|
|
156
160
|
# NOTE: Originally tasks where queried per list, however this no longer works well because it requires a 'view' id, which comes wih various un-predictable baked-in filters (like done=true). Using the tasks/all api endpoint avoids this but quickly hits the default maxitemsperpage limit (which could, less obviously, show up in the per list method).
|
|
157
161
|
|
|
@@ -204,7 +208,6 @@ def get_todos(user_conf):
|
|
|
204
208
|
# ?per_page=10000
|
|
205
209
|
|
|
206
210
|
|
|
207
|
-
|
|
208
211
|
response = requests.get(user_conf['url']+"api/v1/tasks/all", headers=headers)
|
|
209
212
|
all_tasks = response.json()
|
|
210
213
|
|
|
@@ -218,8 +221,6 @@ def get_todos(user_conf):
|
|
|
218
221
|
# print(json.dumps(all_tasks, indent=4))
|
|
219
222
|
utils.dbg("Got ",len(all_tasks),"Vikunja tasks", s='vikunja', l=2 )
|
|
220
223
|
|
|
221
|
-
utils.dbg("Got ",len(all_tasks),"Vikunja tasks", s='vikunja', l=2 )
|
|
222
|
-
|
|
223
224
|
for i, itemIter in enumerate(all_tasks):
|
|
224
225
|
|
|
225
226
|
t = all_tasks[i]
|
|
@@ -228,7 +229,7 @@ def get_todos(user_conf):
|
|
|
228
229
|
|
|
229
230
|
# utils.dbg(t, s='vikunja', l=3)
|
|
230
231
|
|
|
231
|
-
# Limit to
|
|
232
|
+
# Limit to tasks assigned, created by, or favorated by user
|
|
232
233
|
if user_conf["username"]:
|
|
233
234
|
if t['is_favorite'] != True:
|
|
234
235
|
|
|
@@ -264,13 +265,12 @@ def get_todos(user_conf):
|
|
|
264
265
|
tasks[id]['status'] = -1
|
|
265
266
|
|
|
266
267
|
except Exception as e:
|
|
267
|
-
|
|
268
|
+
utils.dbg("No list found for", t['title']," list_id",list_id, s='vikunja')
|
|
268
269
|
# raise e
|
|
269
270
|
|
|
270
271
|
if t['done'] == True:
|
|
271
272
|
tasks[id]['status'] = 0
|
|
272
273
|
|
|
273
|
-
# Prioritize if "due_date" is soon or passed (and not: "0001-01-01T00:00:00Z")
|
|
274
274
|
if 'due_date' in t and t['due_date'] != "0001-01-01T00:00:00Z":
|
|
275
275
|
|
|
276
276
|
due_date = datetime.strptime(t['due_date'],'%Y-%m-%dT%H:%M:%SZ')
|
|
@@ -282,14 +282,13 @@ def get_todos(user_conf):
|
|
|
282
282
|
if due_seconds > -86400:
|
|
283
283
|
tasks[id]['priority'] = 2
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|
|
285
|
+
if t['priority'] and t['priority'] > 1:
|
|
286
|
+
# Vikunja priority is 1 = lowest, 5 = highest
|
|
287
|
+
#
|
|
288
|
+
tasks[id]['priority'] = utils.invert_number_scale(utils.clamp(utils.force_number(t['priority']),0,5))
|
|
289
|
+
elif t['is_favorite'] == True:
|
|
287
290
|
tasks[id]['priority'] = 2
|
|
288
|
-
|
|
289
|
-
print("Priority task", t['title'])
|
|
290
|
-
tasks[id]['priority'] = priorities[t['priority']]
|
|
291
|
-
# print(tasks[id])
|
|
292
|
-
|
|
291
|
+
|
|
293
292
|
todos = {'lists':lists,'tasks':tasks}
|
|
294
293
|
return todos
|
|
295
294
|
|
nowfocus/new_task_dialog.py
CHANGED
|
@@ -87,9 +87,9 @@ class NewTaskWDialog(Gtk.Dialog):
|
|
|
87
87
|
todolist_conf = conf.user['todolists'][parent_list['todolist']]
|
|
88
88
|
try:
|
|
89
89
|
|
|
90
|
-
conf.file_watch_ignores[todolist_conf['id']] = True
|
|
91
90
|
task = conf.todo_connectors[todolist_conf['type']].add_new_task(todolist_conf,parent_list,task_label)
|
|
92
|
-
|
|
91
|
+
|
|
92
|
+
clear_todo_file_change_time(todolist_conf)
|
|
93
93
|
|
|
94
94
|
dbg('connector add task response',task)
|
|
95
95
|
|
|
@@ -121,9 +121,8 @@ class NewTaskWDialog(Gtk.Dialog):
|
|
|
121
121
|
if conf.debug_level > 1:
|
|
122
122
|
raise e
|
|
123
123
|
|
|
124
|
-
error_notice('Error adding tasks',"Adding "+ task_label+" to "+todolist_conf['label']+" had a serious failure",e
|
|
124
|
+
error_notice('Error adding tasks',"Adding "+ task_label+" to "+todolist_conf['label']+" had a serious failure",e) # NOTE: e, in the case of a key error, only prints the bad key, not the error type
|
|
125
125
|
|
|
126
|
-
|
|
127
126
|
|
|
128
127
|
elif response == Gtk.ResponseType.CANCEL:
|
|
129
128
|
print("Cancel button clicked")
|