nowfocus 0.2.8__py3-none-any.whl → 0.2.13__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/__init__.py +0 -67
- nowfocus/__main__.py +45 -27
- nowfocus/conf.py +12 -5
- nowfocus/connectors/todotxt.py +2 -19
- nowfocus/connectors/txt.py +51 -37
- nowfocus/connectors/vikunja.py +6 -4
- nowfocus/new_task_dialog.py +4 -1
- nowfocus/settings.py +55 -25
- nowfocus/task_window.py +16 -10
- nowfocus/utils.py +147 -22
- {nowfocus-0.2.8.dist-info → nowfocus-0.2.13.dist-info}/METADATA +57 -37
- {nowfocus-0.2.8.dist-info → nowfocus-0.2.13.dist-info}/RECORD +16 -16
- {nowfocus-0.2.8.dist-info → nowfocus-0.2.13.dist-info}/WHEEL +0 -0
- {nowfocus-0.2.8.dist-info → nowfocus-0.2.13.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.2.8.dist-info → nowfocus-0.2.13.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.2.8.dist-info → nowfocus-0.2.13.dist-info}/top_level.txt +0 -0
nowfocus/settings.py
CHANGED
|
@@ -17,7 +17,7 @@ from utils import *
|
|
|
17
17
|
from session_edit_dialog import SessionEditDialog
|
|
18
18
|
|
|
19
19
|
def update_user_setting(widget = None, conf_ref = None, key = None, val = None, callback = None):
|
|
20
|
-
''' Update conf.user by passing
|
|
20
|
+
''' Update conf.user by passing a conf_ref user.conf pointer (exe. ' conf.user['task_commands']['task_id_email']) plus key for the value to be updated and a new value for it
|
|
21
21
|
val can be a callable (with no required parameters), (so a widgets get_active can be used) in which case it's return value will be used '''
|
|
22
22
|
|
|
23
23
|
print("update user_setting")
|
|
@@ -37,13 +37,6 @@ def update_user_setting(widget = None, conf_ref = None, key = None, val = None,
|
|
|
37
37
|
if conf_ref and key:
|
|
38
38
|
conf_ref[key] = val
|
|
39
39
|
|
|
40
|
-
# print('object with updated '+key)
|
|
41
|
-
# print(conf_ref)
|
|
42
|
-
|
|
43
|
-
# print('(hopefully) updated conf.user before saving')
|
|
44
|
-
# pretty_print(conf.user)
|
|
45
|
-
|
|
46
|
-
# Disabled just for test
|
|
47
40
|
save_user_settings()
|
|
48
41
|
|
|
49
42
|
if callback:
|
|
@@ -57,13 +50,35 @@ def update_user_settings_value(widget = None, key_val = None):
|
|
|
57
50
|
|
|
58
51
|
if key_val:
|
|
59
52
|
conf.user[key_val['key']] = key_val['value']
|
|
60
|
-
|
|
61
53
|
save_user_settings()
|
|
62
54
|
|
|
63
|
-
|
|
55
|
+
|
|
56
|
+
def update_connector_status(widget, app, connector_category, id, key, widget_getter = "Add this feature! :)"):
|
|
64
57
|
conf.user[connector_category][id][key] = widget.get_active()
|
|
65
58
|
save_user_settings()
|
|
66
|
-
app
|
|
59
|
+
after_todo_settings_change(app,id)
|
|
60
|
+
# app.async_refresh()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def after_todo_settings_change(app, todo = None):
|
|
64
|
+
if todo:
|
|
65
|
+
todo = utils.get_todo_by_id(todo)
|
|
66
|
+
if todo['status']:
|
|
67
|
+
|
|
68
|
+
app.async_refresh(todo)
|
|
69
|
+
utils.stop_todo_file_watchers(todo)
|
|
70
|
+
utils.start_todo_file_watchers(todo)
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
utils.db_deactivate_todo(todo['id'])
|
|
74
|
+
utils.reindex()
|
|
75
|
+
utils.stop_todo_file_watchers(todo)
|
|
76
|
+
app.update_menu()
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
app.async_refresh()
|
|
80
|
+
utils.stop_todo_file_watchers()
|
|
81
|
+
utils.start_todo_file_watchers()
|
|
67
82
|
|
|
68
83
|
|
|
69
84
|
class SettingsWindow(Gtk.Window):
|
|
@@ -344,9 +359,12 @@ class SettingsWindow(Gtk.Window):
|
|
|
344
359
|
print_time_totals_button.connect("clicked", self.app.print_time_totals)
|
|
345
360
|
self.sessions_page.add(print_time_totals_button)
|
|
346
361
|
|
|
347
|
-
|
|
348
|
-
|
|
362
|
+
show_recent_sessions_button = Gtk.Button(label="Show recent sessions")
|
|
363
|
+
show_recent_sessions_button.connect("clicked", self.show_sessions, 'Recent Sessions', 'start_time')
|
|
364
|
+
self.sessions_page.add(show_recent_sessions_button)
|
|
349
365
|
|
|
366
|
+
show_long_sessions_button = Gtk.Button(label="Show long sessions")
|
|
367
|
+
show_long_sessions_button.connect("clicked", self.show_sessions, 'Longest Sessions', 'duration')
|
|
350
368
|
self.sessions_page.add(show_long_sessions_button)
|
|
351
369
|
|
|
352
370
|
self.sessions_scroller = self.scroll_box(self.sessions_page)
|
|
@@ -539,7 +557,7 @@ class SettingsWindow(Gtk.Window):
|
|
|
539
557
|
|
|
540
558
|
checkbutton = Gtk.CheckButton(label=data['label'])
|
|
541
559
|
checkbutton.set_active(data['status'])
|
|
542
|
-
checkbutton.connect("toggled",
|
|
560
|
+
checkbutton.connect("toggled", update_connector_status, self.app, connector_category, id, 'status')
|
|
543
561
|
box.pack_start(checkbutton,False,False,5)
|
|
544
562
|
|
|
545
563
|
edit_button = Gtk.Button(label="Edit")
|
|
@@ -566,9 +584,13 @@ class SettingsWindow(Gtk.Window):
|
|
|
566
584
|
if connector_category == 'todolists':
|
|
567
585
|
db_query("DELETE FROM tasks WHERE todolist = ? AND id NOT IN (SELECT task_id FROM sessions)",(id,))
|
|
568
586
|
db_query("DELETE FROM lists WHERE todolist = ?",(id,))
|
|
587
|
+
utils.reindex()
|
|
588
|
+
utils.stop_todo_file_watchers(connector)
|
|
589
|
+
self.app.update_menu()
|
|
569
590
|
|
|
570
591
|
del conf.user[connector_category][id]
|
|
571
592
|
save_user_settings()
|
|
593
|
+
|
|
572
594
|
self.show_connectors(connector_category)
|
|
573
595
|
|
|
574
596
|
|
|
@@ -596,7 +618,7 @@ class SettingsWindow(Gtk.Window):
|
|
|
596
618
|
else:
|
|
597
619
|
connector_type = self.inputs['new_'+connector_category+'_type'].get_active_text()
|
|
598
620
|
|
|
599
|
-
print("type in edit_add_connector %s" % (connector_type))
|
|
621
|
+
# print("type in edit_add_connector %s" % (connector_type))
|
|
600
622
|
|
|
601
623
|
dialog = EditAddConnectorDialog(self, connector_category, connector_type, id)
|
|
602
624
|
response = dialog.run()
|
|
@@ -606,11 +628,6 @@ class SettingsWindow(Gtk.Window):
|
|
|
606
628
|
print("The OK button was clicked")
|
|
607
629
|
self.show_connectors(connector_category)
|
|
608
630
|
|
|
609
|
-
# TODO: Show connector errors
|
|
610
|
-
|
|
611
|
-
self.app.async_refresh()
|
|
612
|
-
|
|
613
|
-
|
|
614
631
|
elif response == Gtk.ResponseType.CANCEL:
|
|
615
632
|
print("The Cancel button was clicked")
|
|
616
633
|
|
|
@@ -912,6 +929,10 @@ class EditAddConnectorDialog(Gtk.Dialog):
|
|
|
912
929
|
|
|
913
930
|
self.entries = {}
|
|
914
931
|
|
|
932
|
+
self.app = parent.app
|
|
933
|
+
self.set_default_size(400,400)
|
|
934
|
+
|
|
935
|
+
|
|
915
936
|
self.set_border_width(15)
|
|
916
937
|
|
|
917
938
|
box = self.get_content_area()
|
|
@@ -932,7 +953,7 @@ class EditAddConnectorDialog(Gtk.Dialog):
|
|
|
932
953
|
|
|
933
954
|
for key in fields:
|
|
934
955
|
|
|
935
|
-
if key in ['id','type','timetracker','status']:
|
|
956
|
+
if key in ['id','type','timetracker','status','watch_file']:
|
|
936
957
|
continue
|
|
937
958
|
|
|
938
959
|
label = Gtk.Label(label=key)
|
|
@@ -979,13 +1000,13 @@ class EditAddConnectorDialog(Gtk.Dialog):
|
|
|
979
1000
|
)
|
|
980
1001
|
|
|
981
1002
|
self.show_all()
|
|
982
|
-
self.connect("response", self.on_response, connector_category, connector_type, id)
|
|
1003
|
+
self.connect("response", self.on_response, connector_category, connector_type, id, data)
|
|
983
1004
|
|
|
984
1005
|
|
|
985
|
-
def on_response(self, widget, response, connector_category, connector_type, id):
|
|
1006
|
+
def on_response(self, widget, response, connector_category, connector_type, id, initial_data):
|
|
986
1007
|
if response == Gtk.ResponseType.OK:
|
|
987
1008
|
|
|
988
|
-
new =
|
|
1009
|
+
new = initial_data
|
|
989
1010
|
|
|
990
1011
|
for key in self.entries:
|
|
991
1012
|
|
|
@@ -1013,7 +1034,16 @@ class EditAddConnectorDialog(Gtk.Dialog):
|
|
|
1013
1034
|
save_user_settings()
|
|
1014
1035
|
|
|
1015
1036
|
# load the connector module
|
|
1016
|
-
|
|
1037
|
+
if connector_category == 'timetrackers':
|
|
1038
|
+
conf.timetracker_connectors[new['type']] = importlib.import_module('connectors.'+new['type'])
|
|
1039
|
+
|
|
1040
|
+
if connector_category == 'todolists':
|
|
1041
|
+
|
|
1042
|
+
conf.todo_connectors[new['type']] = importlib.import_module('connectors.'+new['type'])
|
|
1043
|
+
|
|
1044
|
+
utils.stop_todo_file_watchers(new)
|
|
1045
|
+
utils.start_todo_file_watchers(new)
|
|
1046
|
+
self.app.async_refresh(new)
|
|
1017
1047
|
|
|
1018
1048
|
|
|
1019
1049
|
elif response == Gtk.ResponseType.CANCEL:
|
nowfocus/task_window.py
CHANGED
|
@@ -231,12 +231,12 @@ class TaskWindow(Gtk.Window):
|
|
|
231
231
|
self.search_cache = {}
|
|
232
232
|
self.search_cache_refresh_time = now().strftime("%Y-%m-%d %H:%M:%S")
|
|
233
233
|
self.task_search(self.taskEntry)
|
|
234
|
-
|
|
234
|
+
|
|
235
235
|
|
|
236
236
|
def open_new_task_dialog(self,w = None):
|
|
237
237
|
passed_data = {"label":self.taskEntry.get_text()}
|
|
238
238
|
self.app.open_new_task_dialog(self, passed_data)
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
|
|
241
241
|
def tick(self):
|
|
242
242
|
|
|
@@ -245,7 +245,7 @@ class TaskWindow(Gtk.Window):
|
|
|
245
245
|
self.recreate_header()
|
|
246
246
|
|
|
247
247
|
if utils.taskindex_updated_time() > self.search_cache_refresh_time:
|
|
248
|
-
print("taskindex was updated, utils.taskindex_updated_time()", utils.taskindex_updated_time(), 'self.search_cache_refresh_time',self.search_cache_refresh_time)
|
|
248
|
+
# print("taskindex was updated, utils.taskindex_updated_time()", utils.taskindex_updated_time(), 'self.search_cache_refresh_time',self.search_cache_refresh_time)
|
|
249
249
|
self.refresh_search_cache()
|
|
250
250
|
# else:
|
|
251
251
|
# print("taskindex was not updated",current_data_version)
|
|
@@ -292,13 +292,13 @@ class TaskWindow(Gtk.Window):
|
|
|
292
292
|
pause_button.connect("clicked", self.recreate_header)
|
|
293
293
|
|
|
294
294
|
pause_button.connect("clicked", self.refresh_search_cache)
|
|
295
|
-
pause_button.set_property("tooltip-text", "Pause Task (
|
|
295
|
+
pause_button.set_property("tooltip-text", "Pause Task (Control S)")
|
|
296
296
|
pause_button.set_relief(Gtk.ReliefStyle.NONE)
|
|
297
297
|
self.header.add(pause_button)
|
|
298
298
|
|
|
299
299
|
done_button = Gtk.Button()
|
|
300
300
|
done_button.set_image(Gtk.Image.new_from_file(os.path.abspath('icon/mark-done.png')))
|
|
301
|
-
done_button.set_property("tooltip-text", "Mark Task Done (
|
|
301
|
+
done_button.set_property("tooltip-text", "Mark Task Done (Control D)")
|
|
302
302
|
done_button.connect("clicked", self.app.stop_task,'mark_done')
|
|
303
303
|
done_button.connect("clicked", self.recreate_header)
|
|
304
304
|
done_button.connect("clicked", self.refresh_search_cache) # doesn't work, probably because it happens before stop_task
|
|
@@ -310,14 +310,17 @@ class TaskWindow(Gtk.Window):
|
|
|
310
310
|
cancel_button.set_image(Gtk.Image.new_from_file(os.path.abspath('icon/cancel.png')))
|
|
311
311
|
cancel_button.connect("clicked", self.app.stop_task,"cancel")
|
|
312
312
|
cancel_button.connect("clicked", self.recreate_header)
|
|
313
|
-
cancel_button.set_property("tooltip-text", "Discard timer (
|
|
313
|
+
cancel_button.set_property("tooltip-text", "Discard timer (Control Q)")
|
|
314
314
|
cancel_button.set_relief(Gtk.ReliefStyle.NONE)
|
|
315
315
|
self.header.add(cancel_button)
|
|
316
316
|
|
|
317
|
-
key, mod = Gtk.accelerator_parse('<Control>p')
|
|
317
|
+
key, mod = Gtk.accelerator_parse('<Control>p') # for backward compatibility
|
|
318
|
+
pause_button.add_accelerator("clicked", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
|
|
319
|
+
|
|
320
|
+
key, mod = Gtk.accelerator_parse('<Control>S')
|
|
318
321
|
pause_button.add_accelerator("clicked", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
|
|
319
322
|
|
|
320
|
-
key, mod = Gtk.accelerator_parse('<Control>
|
|
323
|
+
key, mod = Gtk.accelerator_parse('<Control>q')
|
|
321
324
|
cancel_button.add_accelerator("clicked", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
|
|
322
325
|
|
|
323
326
|
key, mod = Gtk.accelerator_parse('<Control>d')
|
|
@@ -415,6 +418,10 @@ class TaskWindow(Gtk.Window):
|
|
|
415
418
|
|
|
416
419
|
# button_context = self.shown_tasks[t['id']].get_style_context().add_class("large")
|
|
417
420
|
extended_label = GLib.markup_escape_text(t['extended_label'],)
|
|
421
|
+
|
|
422
|
+
# Truncate excessively long task labels
|
|
423
|
+
if len(extended_label) > 110:
|
|
424
|
+
extended_label = extended_label[:110]+"..."
|
|
418
425
|
|
|
419
426
|
if len(search_str) > 1:
|
|
420
427
|
|
|
@@ -462,7 +469,7 @@ class TaskWindow(Gtk.Window):
|
|
|
462
469
|
print("select_task task id",tid)
|
|
463
470
|
t = utils.db_get_item_by_id(tid)
|
|
464
471
|
# print('event',event)
|
|
465
|
-
print("select_task self.modifyer_keys", self.modifyer_keys)
|
|
472
|
+
# print("select_task self.modifyer_keys", self.modifyer_keys)
|
|
466
473
|
|
|
467
474
|
if self.modifyer_keys['control'] or event and event.button == 3: # Right-click (button 3) or control click context menu popover
|
|
468
475
|
|
|
@@ -612,7 +619,6 @@ class TaskWindow(Gtk.Window):
|
|
|
612
619
|
# utils.dbg("key_press",key,s="taskwindow",l=1)
|
|
613
620
|
# utils.dbg("key_press event.state",event.state,s="taskwindow",l=1)
|
|
614
621
|
# utils.dbg("key_press_event",event,s="taskwindow",l=1)
|
|
615
|
-
# utils.dbg("ModifierType is CONTROL_MASK",Gdk.ModifierType.CONTROL_MASK,s="taskwindow",l=3)
|
|
616
622
|
|
|
617
623
|
if (event.state & Gdk.ModifierType.CONTROL_MASK) or key == 'Control_L':
|
|
618
624
|
self.modifyer_keys['control'] = True
|
nowfocus/utils.py
CHANGED
|
@@ -8,7 +8,6 @@ from dateutil.relativedelta import relativedelta
|
|
|
8
8
|
import copy
|
|
9
9
|
import threading
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
import sqlite3
|
|
13
12
|
from contextlib import closing
|
|
14
13
|
from pathlib import Path
|
|
@@ -19,6 +18,9 @@ from gi.repository import Gtk, GLib, Gdk
|
|
|
19
18
|
gi.require_version('Notify', '0.7')
|
|
20
19
|
from gi.repository import Notify as notify
|
|
21
20
|
|
|
21
|
+
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
|
22
|
+
from watchdog.observers import Observer
|
|
23
|
+
|
|
22
24
|
import conf
|
|
23
25
|
notify.init(conf.app_name)
|
|
24
26
|
|
|
@@ -32,7 +34,7 @@ def dbg(*data, **kwargs):
|
|
|
32
34
|
l=level {0:Error, 1:Warning, 2:Info, 3:Details}
|
|
33
35
|
s=system (Debuggable systems: 'taskwindow','signals','todoloading','user_settings', 'targets')'''
|
|
34
36
|
|
|
35
|
-
levels = {0:'Error', 1:'Warning', 2:'Info', 3:'Details'}
|
|
37
|
+
levels = {-1: 'CLI output', 0:'Error', 1:'Warning', 2:'Info', 3:'Details'}
|
|
36
38
|
|
|
37
39
|
# default system and level
|
|
38
40
|
system = ''
|
|
@@ -104,6 +106,7 @@ def time_to_sec(time=None):
|
|
|
104
106
|
sec += int(v) * 60 ** k
|
|
105
107
|
return sec
|
|
106
108
|
|
|
109
|
+
|
|
107
110
|
def sec_to_time(sec):
|
|
108
111
|
|
|
109
112
|
try:
|
|
@@ -115,14 +118,16 @@ def sec_to_time(sec):
|
|
|
115
118
|
time = str("{:02d}".format(int(sec // 3600))) + ':' + str("{:02d}".format(int((sec % 3600) // 60))) + ':' + str("{:02d}".format(int(sec % 60)))
|
|
116
119
|
return time.lstrip('0').lstrip(':')
|
|
117
120
|
|
|
121
|
+
|
|
118
122
|
def now():
|
|
119
123
|
return datetime.now()
|
|
120
|
-
# return datetime.now(timezone.utc)
|
|
121
124
|
# return datetime.now(timezone.utc).astimezone().tzinfo
|
|
122
125
|
|
|
126
|
+
|
|
123
127
|
def start_of_day():
|
|
124
128
|
return now().replace(hour=0,minute=0,second=0,microsecond=0)
|
|
125
129
|
|
|
130
|
+
|
|
126
131
|
def time_difference(earlier, later = None):
|
|
127
132
|
''' Return seconds (float), later defaults to now'''
|
|
128
133
|
# date_format = "%Y-%m-%dT%H:%M:%S.%f%z" # This can probably be updated / tossed (ActivityWatch format )
|
|
@@ -136,6 +141,7 @@ def time_difference(earlier, later = None):
|
|
|
136
141
|
|
|
137
142
|
return difference.total_seconds()
|
|
138
143
|
|
|
144
|
+
|
|
139
145
|
def validate_start_time_str(start_time_string):
|
|
140
146
|
try:
|
|
141
147
|
return datetime.strptime(start_time_string,'%Y-%m-%d %H:%M:%S')
|
|
@@ -144,6 +150,7 @@ def validate_start_time_str(start_time_string):
|
|
|
144
150
|
error_notice('Incorrect Start Time Format', start_time_string+" does no match %Y-%m-%d %H:%M:%S", e)
|
|
145
151
|
return False
|
|
146
152
|
|
|
153
|
+
|
|
147
154
|
def open_todo(w=None, i=None, item_type = 'tasks'):
|
|
148
155
|
''' run the launch() function from the todo connector for the provided i(tem) '''
|
|
149
156
|
if not isinstance(i, dict):
|
|
@@ -156,15 +163,13 @@ def open_todo(w=None, i=None, item_type = 'tasks'):
|
|
|
156
163
|
|
|
157
164
|
except Exception as e:
|
|
158
165
|
# error_notice('Bonk', "error with "+ c['type']+ " open function ")
|
|
159
|
-
dbg(
|
|
166
|
+
dbg('open_todo excption, falling back to get_connector_openable ',e,s=todo['type'],l=1)
|
|
160
167
|
|
|
161
168
|
get_connector_openable(None,todo)
|
|
162
|
-
|
|
169
|
+
|
|
163
170
|
|
|
164
171
|
def get_connector_openable(widget = None, connector_dict = None, open_it = True):
|
|
165
172
|
|
|
166
|
-
# if conf.todo_connectors[connector_dict['type']].launch(todo,i,item_type)
|
|
167
|
-
|
|
168
173
|
for option in ['file','front_end_url','url','uri','open_command']:
|
|
169
174
|
|
|
170
175
|
if option in connector_dict:
|
|
@@ -174,8 +179,8 @@ def get_connector_openable(widget = None, connector_dict = None, open_it = True)
|
|
|
174
179
|
else:
|
|
175
180
|
return connector_dict[option]
|
|
176
181
|
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
dbg('get_connector_openable failed for',l=1,s=connector_dict['type'])
|
|
183
|
+
dbg('connector_dict passed to get_connector_openable',connector_dict,l=3,s=connector_dict['type'])
|
|
179
184
|
return False
|
|
180
185
|
|
|
181
186
|
|
|
@@ -274,6 +279,15 @@ def save_user_settings():
|
|
|
274
279
|
json.dump(conf.user, settings_file)
|
|
275
280
|
|
|
276
281
|
|
|
282
|
+
def get_todo_by_file(file):
|
|
283
|
+
for id, todo in conf.user['todolists'].items():
|
|
284
|
+
if 'file' in todo and todo['file'] == file:
|
|
285
|
+
return todo
|
|
286
|
+
|
|
287
|
+
print('get_todo_by_file failed for', file)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
277
291
|
def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
278
292
|
'''error_handling: 2 = raise error, 1 = notify error, 0 = ignore errors '''
|
|
279
293
|
# print('db_query parameters type: ',type(parameters))
|
|
@@ -356,7 +370,6 @@ def db_update():
|
|
|
356
370
|
db_query("ALTER TABLE sessions ADD COLUMN extended_label TEXT")
|
|
357
371
|
|
|
358
372
|
# add default values for priority and data
|
|
359
|
-
|
|
360
373
|
# Since these column are just a cache, replace instead of copying columns
|
|
361
374
|
db_query("ALTER TABLE tasks DROP COLUMN priority")
|
|
362
375
|
db_query("ALTER TABLE tasks DROP COLUMN data")
|
|
@@ -422,6 +435,7 @@ def reindex_all():
|
|
|
422
435
|
db_query("UPDATE taskindex set priority = ? WHERE id = ?",(t['priority'],t['id']))
|
|
423
436
|
|
|
424
437
|
db_query("REPLACE INTO system(field, value) VALUES(?,?)",('taskindex_update_time',now().strftime("%Y-%m-%d %H:%M:%S")))
|
|
438
|
+
return True
|
|
425
439
|
|
|
426
440
|
|
|
427
441
|
def reindex_one(t):
|
|
@@ -437,10 +451,11 @@ def reindex_one(t):
|
|
|
437
451
|
print("reindexed",t['label'])
|
|
438
452
|
# print(db_query("SELECT * FROM taskindex where id = ?",(t['id'],)))
|
|
439
453
|
db_query("REPLACE INTO system(field, value) VALUES(?,?)",('taskindex_update_time',now().strftime("%Y-%m-%d %H:%M:%S")))
|
|
454
|
+
return True
|
|
440
455
|
|
|
441
456
|
|
|
442
457
|
def taskindex_updated_time():
|
|
443
|
-
''' returns
|
|
458
|
+
''' returns system db taskindex_update_time string formatted as %Y-%m-%d %H:%M:%S '''
|
|
444
459
|
update_time = db_query("SELECT value FROM system WHERE field = 'taskindex_update_time'")[0]['value']
|
|
445
460
|
|
|
446
461
|
# print('taskindex_updated_time',update_time)
|
|
@@ -673,10 +688,6 @@ def db_cleanup(widget = None):
|
|
|
673
688
|
|
|
674
689
|
db_query("UPDATE sessions SET extended_label = (SELECT extended_label FROM tasks WHERE id = task_id), parent_id = (SELECT parent_id FROM tasks WHERE id = task_id) WHERE task_id IN (SELECT id FROM tasks) AND extended_label != (SELECT extended_label FROM tasks WHERE id = task_id)")
|
|
675
690
|
|
|
676
|
-
|
|
677
|
-
# SELECT * FROM sessions WHERE extended_label IS NULL;
|
|
678
|
-
# UPDATE sessions SET extended_label = (SELECT extended_label FROM tasks WHERE id = task_id) WHERE extended_label IS NULL
|
|
679
|
-
|
|
680
691
|
# db_query("SELECT label, todolist FROM tasks WHERE todolist NOT IN ?",(ids_sql,))
|
|
681
692
|
# db_query("DELETE FROM tasks WHERE todolist NOT IN "+ids_sql)
|
|
682
693
|
# db_query("DELETE FROM lists WHERE todolist NOT IN "+ids_sql)
|
|
@@ -704,6 +715,17 @@ def db_get_item_by_id(id,table = 'tasks'):
|
|
|
704
715
|
dbg('db_get_item_by_id failed',e,l=0)
|
|
705
716
|
|
|
706
717
|
|
|
718
|
+
def get_todo_by_id(todo_or_todo_id = None):
|
|
719
|
+
''' get user_conf todo connector data, does not validate '''
|
|
720
|
+
if todo_or_todo_id and isinstance(todo_or_todo_id, str):
|
|
721
|
+
if todo_or_todo_id in conf.user['todolists']:
|
|
722
|
+
return conf.user['todolists'][todo_or_todo_id]
|
|
723
|
+
else:
|
|
724
|
+
return None
|
|
725
|
+
|
|
726
|
+
return todo_or_todo_id
|
|
727
|
+
|
|
728
|
+
|
|
707
729
|
def proc_db_item(i,table='tasks'):
|
|
708
730
|
''' Parse task or list data json and apply time target priority '''
|
|
709
731
|
i['data'] = json.loads(i['data'])
|
|
@@ -867,6 +889,77 @@ def get_times(task):
|
|
|
867
889
|
return o
|
|
868
890
|
|
|
869
891
|
|
|
892
|
+
class TodoModifiedEventHandler(FileSystemEventHandler):
|
|
893
|
+
def __init__(self,todo = None):
|
|
894
|
+
# print('todo in TodoModifiedEventHandler',todo)
|
|
895
|
+
self.todo = todo
|
|
896
|
+
|
|
897
|
+
# Can't use on_modified because of swap-in save method used by editors
|
|
898
|
+
# def on_modified(self, event: FileModifiedEvent) -> None:
|
|
899
|
+
|
|
900
|
+
def on_any_event(self, event: FileSystemEvent) -> None:
|
|
901
|
+
|
|
902
|
+
# print(event)
|
|
903
|
+
# src_path=PATH/todo.txt', dest_path='', event_type='modified', is_directory=False, is_synthetic=False
|
|
904
|
+
# print(event.src_path,event.event_type)
|
|
905
|
+
|
|
906
|
+
if event.src_path == self.todo['file'] and event.event_type not in ('closed','opened') and not event.is_directory:
|
|
907
|
+
if self.todo['id'] not in conf.file_watch_ignores:
|
|
908
|
+
# print("Refresh", event.src_path, event.event_type)
|
|
909
|
+
conf.todo_sync_required[self.todo['id']] = self.todo['id']
|
|
910
|
+
# else:
|
|
911
|
+
# print('currently ignoring changes to '+self.todo['label'])
|
|
912
|
+
|
|
913
|
+
return True
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def watch_todo_file(todo):
|
|
917
|
+
|
|
918
|
+
dbg("Setting up file modification observer for", todo['file'],s=todo['type'])
|
|
919
|
+
|
|
920
|
+
event_handler = TodoModifiedEventHandler(todo)
|
|
921
|
+
|
|
922
|
+
observer = Observer()
|
|
923
|
+
observer.daemon = True
|
|
924
|
+
path,filename=todo['file'].rsplit('/',1)
|
|
925
|
+
observer.schedule(event_handler, path+'/', recursive=False)
|
|
926
|
+
observer.start()
|
|
927
|
+
|
|
928
|
+
return observer
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def start_todo_file_watchers(todo = None):
|
|
932
|
+
print("starting todo watchers. todo",todo)
|
|
933
|
+
if todo:
|
|
934
|
+
if todo['status'] and 'watch_file' in todo:
|
|
935
|
+
conf.file_watchers[todo['id']] = watch_todo_file(todo)
|
|
936
|
+
else:
|
|
937
|
+
print('todo passed to start_todo_file_watchers is ineligable', todo)
|
|
938
|
+
else:
|
|
939
|
+
for id, todo in conf.user['todolists'].items():
|
|
940
|
+
|
|
941
|
+
if todo['status'] and 'watch_file' in todo:
|
|
942
|
+
|
|
943
|
+
conf.file_watchers[id] = watch_todo_file(todo)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def stop_todo_file_watchers(todo = None):
|
|
947
|
+
|
|
948
|
+
if todo:
|
|
949
|
+
if todo['id'] in conf.file_watchers:
|
|
950
|
+
conf.file_watchers[todo['id']].stop()
|
|
951
|
+
conf.file_watchers[todo['id']].join()
|
|
952
|
+
del conf.file_watchers[todo['id']]
|
|
953
|
+
else:
|
|
954
|
+
for id, watcher in conf.file_watchers.items():
|
|
955
|
+
watcher.stop()
|
|
956
|
+
watcher.join()
|
|
957
|
+
del conf.file_watchers[id]
|
|
958
|
+
|
|
959
|
+
print('file_watchers after stop_todo_file_watchers')
|
|
960
|
+
print(conf.file_watchers.keys())
|
|
961
|
+
|
|
962
|
+
|
|
870
963
|
def db_deactivate_todo(id):
|
|
871
964
|
dbg("deactivating todolist: "+id, s='todoloading')
|
|
872
965
|
# Delete inactive todo items (except where there are sessions for them)
|
|
@@ -876,6 +969,35 @@ def db_deactivate_todo(id):
|
|
|
876
969
|
db_query("UPDATE tasks SET status = -1 WHERE todolist = ?",(id,))
|
|
877
970
|
|
|
878
971
|
|
|
972
|
+
def refresh_todolist(todo, catch_errors = False):
|
|
973
|
+
|
|
974
|
+
''' Refresh a single todo. runs db_set_todolist and returns todos. Exceptions must be handled my caller '''
|
|
975
|
+
|
|
976
|
+
try:
|
|
977
|
+
dbg('Refreshing '+todo['label'],l=-1)
|
|
978
|
+
conf.file_watch_ignores[todo['id']] = True # get_todos can cause the same event that saving a file cases
|
|
979
|
+
todos = conf.todo_connectors[todo['type']].get_todos(todo)
|
|
980
|
+
del conf.file_watch_ignores[todo['id']]
|
|
981
|
+
|
|
982
|
+
dbg('Done Refreshing '+todo['label'],l=-1)
|
|
983
|
+
|
|
984
|
+
db_set_todolist(todo['id'],todos['lists'],todos['tasks'])
|
|
985
|
+
|
|
986
|
+
except Exception as e:
|
|
987
|
+
if catch_errors:
|
|
988
|
+
error_notice('Error Loading '+todo['label']+' i refresh_todolist',e)
|
|
989
|
+
todos = {'lists': {}, 'tasks':{}}
|
|
990
|
+
|
|
991
|
+
else:
|
|
992
|
+
raise e
|
|
993
|
+
|
|
994
|
+
if todo['id'] in conf.todo_sync_required:
|
|
995
|
+
del conf.todo_sync_required[todo['id']]
|
|
996
|
+
|
|
997
|
+
conf.todo_sync_times[todo['id']] = now() # It may seem foolish to do this even on an error, but otherwise it will constantly try to update when offline
|
|
998
|
+
return todos
|
|
999
|
+
|
|
1000
|
+
|
|
879
1001
|
def get_todolists(use_db_cache = False):
|
|
880
1002
|
tasks = {}
|
|
881
1003
|
lists = {}
|
|
@@ -897,17 +1019,15 @@ def get_todolists(use_db_cache = False):
|
|
|
897
1019
|
dbg('Refreshing '+todo['type']+' todo '+id, s='todoloading')
|
|
898
1020
|
|
|
899
1021
|
try:
|
|
900
|
-
todos =
|
|
901
|
-
conf.todo_sync_time = now()
|
|
902
|
-
db_set_todolist(todo['id'],todos['lists'],todos['tasks'])
|
|
1022
|
+
todos = refresh_todolist(todo,False)
|
|
903
1023
|
except Exception as e:
|
|
904
1024
|
# This will happen if offline
|
|
905
1025
|
|
|
906
|
-
|
|
907
|
-
if conf.debug_systems == 3 or 'todoloading' in conf.debug_systems or todo['type'] in conf.debug_systems :
|
|
1026
|
+
if conf.debug_level == 3 or 'todoloading' in conf.debug_systems or todo['type'] in conf.debug_systems :
|
|
908
1027
|
raise e
|
|
909
1028
|
|
|
910
|
-
error_notice('Error
|
|
1029
|
+
error_notice('Error refreshing '+todo['label'],e)
|
|
1030
|
+
|
|
911
1031
|
try:
|
|
912
1032
|
todos = db_get_todolist(todo['id'])
|
|
913
1033
|
except Exception as e:
|
|
@@ -919,6 +1039,8 @@ def get_todolists(use_db_cache = False):
|
|
|
919
1039
|
|
|
920
1040
|
if not use_db_cache:
|
|
921
1041
|
reindex()
|
|
1042
|
+
conf.todo_sync_time = now()
|
|
1043
|
+
|
|
922
1044
|
|
|
923
1045
|
lists_cache(lists)
|
|
924
1046
|
|
|
@@ -1063,10 +1185,13 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1063
1185
|
list_menus[l['parent_id']].append(list_menu_items[list_id])
|
|
1064
1186
|
|
|
1065
1187
|
|
|
1066
|
-
|
|
1067
1188
|
for id, t in tasks.items():
|
|
1068
1189
|
if t['status'] == 1:
|
|
1069
1190
|
|
|
1191
|
+
# Truncate excessively long task labels
|
|
1192
|
+
if len(t['label']) > 60:
|
|
1193
|
+
t['label'] = t['label'][:60]+"..."
|
|
1194
|
+
|
|
1070
1195
|
menu_tasks[t['id']] = Gtk.MenuItem.new_with_label(str(t['label']))
|
|
1071
1196
|
menu_tasks[t['id']].connect("activate", activate_callback, t)
|
|
1072
1197
|
|