nowfocus 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl

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