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/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 an 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
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
- def update_connector_setting(widget, app, connector_category, id, key, widget_getter = "Add this feature! :)"):
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.async_refresh()
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
- show_long_sessions_button = Gtk.Button(label="Review long sessions")
348
- show_long_sessions_button.connect("clicked", self.show_sessions, 'Longest Sessions', 'duration')
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", update_connector_setting, self.app, connector_category, id, 'status')
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 = {'type':connector_type,'status':True}
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
- conf.todo_connectors[new['type']] = importlib.import_module('connectors.'+new['type'])
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 (Ctrl + P)")
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 (Ctrl + D)")
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 (Ctrl + X)")
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>x')
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(('open_todo excption, trying get_connector_openable ',e))
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
- print('ERROR: get_connector_openable failed to find an openable')
178
- print(connector_dict)
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 String (no datetime obj '''
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 = conf.todo_connectors[todo['type']].get_todos(todo)
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
- # For testing
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 Loading '+todo['label'],e)
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