nowfocus 0.2.7__py3-none-any.whl → 0.2.12__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
@@ -126,7 +126,6 @@ class TaskWindow(Gtk.Window):
126
126
  self.taskEntry.set_text(passed_data['task']['label'])
127
127
 
128
128
 
129
- # box.add(Gtk.Box(border_width=10)) # Spacer
130
129
 
131
130
  self.scrolled_window = Gtk.ScrolledWindow()
132
131
  self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
@@ -134,7 +133,7 @@ class TaskWindow(Gtk.Window):
134
133
  self.scrolled_window.set_size_request(-1, 350)
135
134
 
136
135
  self.scrolled_window.connect('scroll-event', self.on_scroll) # This doesn't catch scrollbar moves
137
- self.scrolled_window.connect('edge-reached', self.on_scroll) # This is a little late
136
+ self.scrolled_window.connect('edge-reached', self.on_scroll) # This is a little late and may not fire if the list is short?
138
137
 
139
138
  self.tasks_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=7)
140
139
  self.tasks_box.set_halign(Gtk.Align.START)
@@ -164,10 +163,6 @@ class TaskWindow(Gtk.Window):
164
163
  self.buttons_box.set_halign(Gtk.Align.CENTER)
165
164
  self.buttons_box.set_name("Footer")
166
165
 
167
-
168
- # box.add(Gtk.Box(border_width=10)) # Spacer
169
-
170
- # box.add(self.buttons_box)
171
166
  box.pack_start(self.buttons_box,False, False, 0)
172
167
 
173
168
  self.settings_button = Gtk.Button(label="Settings")
@@ -180,19 +175,12 @@ class TaskWindow(Gtk.Window):
180
175
  self.buttons_box.add(self.refresh_button)
181
176
 
182
177
  # Todolist openables
183
- openables = []
184
178
  for id, todo in conf.user['todolists'].items():
185
179
  if todo['status']:
186
180
  try:
187
181
  openable = conf.todo_connectors[todo['type']].launch
188
- # if openable not in openables:
189
-
190
- openables.append(openable)
191
-
192
182
  openable_button = Gtk.Button(label=GLib.markup_escape_text(todo['label']))
193
-
194
- openable_button.connect("clicked", lambda todo: openable(todo))
195
- # openable_button.connect("clicked", openable, todo) # Doesn't work because of passing widget as first argument
183
+ openable_button.connect("clicked", lambda button_widget,todo=todo, openable=openable: openable(todo))
196
184
  openable_button.set_property("tooltip-text","Open (new) "+GLib.markup_escape_text(todo['label']))
197
185
  self.buttons_box.add(openable_button)
198
186
 
@@ -243,12 +231,12 @@ class TaskWindow(Gtk.Window):
243
231
  self.search_cache = {}
244
232
  self.search_cache_refresh_time = now().strftime("%Y-%m-%d %H:%M:%S")
245
233
  self.task_search(self.taskEntry)
246
-
234
+
247
235
 
248
236
  def open_new_task_dialog(self,w = None):
249
237
  passed_data = {"label":self.taskEntry.get_text()}
250
238
  self.app.open_new_task_dialog(self, passed_data)
251
-
239
+
252
240
 
253
241
  def tick(self):
254
242
 
@@ -257,7 +245,7 @@ class TaskWindow(Gtk.Window):
257
245
  self.recreate_header()
258
246
 
259
247
  if utils.taskindex_updated_time() > self.search_cache_refresh_time:
260
- 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)
261
249
  self.refresh_search_cache()
262
250
  # else:
263
251
  # print("taskindex was not updated",current_data_version)
@@ -474,7 +462,7 @@ class TaskWindow(Gtk.Window):
474
462
  print("select_task task id",tid)
475
463
  t = utils.db_get_item_by_id(tid)
476
464
  # print('event',event)
477
- print("select_task self.modifyer_keys", self.modifyer_keys)
465
+ # print("select_task self.modifyer_keys", self.modifyer_keys)
478
466
 
479
467
  if self.modifyer_keys['control'] or event and event.button == 3: # Right-click (button 3) or control click context menu popover
480
468
 
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nowfocus
3
- Version: 0.2.7
3
+ Version: 0.2.12
4
4
  Summary: nowfocus: the open source task-tracking self-control panel.
5
5
  Author: GitFr33
6
6
  Project-URL: Homepage, https://gitlab.com/GitFr33/nowfocus
@@ -42,6 +42,7 @@ Requires-Dist: tzlocal
42
42
  Requires-Dist: urllib3
43
43
  Requires-Dist: vobject
44
44
  Requires-Dist: x-wr-timezone
45
+ Requires-Dist: watchdog
45
46
  Dynamic: license-file
46
47
 
47
48
  <div align="center"><img src="https://gitlab.com/GitFr33/nowfocus/-/raw/main/nowfocus.svg" width="60" align="center">
@@ -52,15 +53,15 @@ Dynamic: license-file
52
53
 
53
54
  </div>
54
55
 
55
- nowfocus is a clean, keyboard-driven project time tracker build with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
56
+ nowfocus is a clean, keyboard-driven project time tracker built with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
56
57
 
57
58
  ## Features
58
59
  - Unlimited flexible combinations of to-do lists and time tracking systems
59
60
  - Infinitely nestable lists
60
61
  - Inactivity detection that automatically pauses time tracking
61
62
  - Pomodoro timer
62
- - Task prioritization
63
- - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
63
+ - Task prioritization
64
+ - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
64
65
  - Randomness interrupt bell (optional) to keep you on track with tracking your time
65
66
  - Keyboard-driven interface
66
67
  - Offline to-do list cache
@@ -76,7 +77,7 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
76
77
 
77
78
  ### Currently Supported To-do List Backends
78
79
 
79
- - Simple text or markdown file with indentation based sub-lists
80
+ - Simple text file with indentation based sub-lists
80
81
  - Any to-do list that supports [CalDav todos](https://en.wikipedia.org/wiki/CalDAV)
81
82
  - [todotxt format](http://todotxt.org/)
82
83
  - [TaskWarrior](https://taskwarrior.org/)
@@ -99,7 +100,9 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
99
100
  # Install dependencies
100
101
  sudo apt install pipx gir1.2-appindicator3-0.1 meson libdbus-glib-1-dev patchelf libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev
101
102
 
102
- # Set up pipx
103
+ # note gir1.2-ayatanaappindicator3-0.1 can be substituted for gir1.2-appindicator3-0.1
104
+
105
+ # Set up pipx
103
106
  pipx ensurepath
104
107
 
105
108
  # At this point you may need to restart your terminal window