nowfocus 0.5.6__tar.gz → 0.5.7__tar.gz

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.
Files changed (66) hide show
  1. {nowfocus-0.5.6/src/nowfocus.egg-info → nowfocus-0.5.7}/PKG-INFO +2 -2
  2. {nowfocus-0.5.6 → nowfocus-0.5.7}/README.md +1 -1
  3. {nowfocus-0.5.6 → nowfocus-0.5.7}/pyproject.toml +1 -1
  4. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/__main__.py +26 -16
  5. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/conf.py +1 -0
  6. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/vikunja.py +9 -51
  7. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/desktop-extras/nowfocus.desktop +1 -1
  8. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/install.py +0 -1
  9. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/new_task_dialog.py +5 -4
  10. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/settings.py +1 -1
  11. nowfocus-0.5.7/src/nowfocus/upgrade.py +99 -0
  12. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/utils.py +110 -127
  13. {nowfocus-0.5.6 → nowfocus-0.5.7/src/nowfocus.egg-info}/PKG-INFO +2 -2
  14. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus.egg-info/SOURCES.txt +1 -1
  15. nowfocus-0.5.6/src/nowfocus/version_migrator.py +0 -20
  16. {nowfocus-0.5.6 → nowfocus-0.5.7}/LICENSE +0 -0
  17. {nowfocus-0.5.6 → nowfocus-0.5.7}/setup.cfg +0 -0
  18. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/__init__.py +0 -0
  19. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/activitywatch.py +0 -0
  20. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/caldav.py +0 -0
  21. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/csv.py +0 -0
  22. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/psc_timetracker.py +0 -0
  23. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/taskwarrior.py +0 -0
  24. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/timewarrior.py +0 -0
  25. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/todo_template.py +0 -0
  26. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/todotxt.py +0 -0
  27. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/trello.py +0 -0
  28. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/connectors/txt.py +0 -0
  29. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/desktop-extras/nowfocus.png +0 -0
  30. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/desktop-extras/nowfocus.svg +0 -0
  31. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/example-todo.txt +0 -0
  32. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/cancel.png +0 -0
  33. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/cancel.svg +0 -0
  34. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/edit.png +0 -0
  35. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/edit.svg +0 -0
  36. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-0.svg +0 -0
  37. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-1.svg +0 -0
  38. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-2.svg +0 -0
  39. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-3.svg +0 -0
  40. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-4.svg +0 -0
  41. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-5.svg +0 -0
  42. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-6.svg +0 -0
  43. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-7.svg +0 -0
  44. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-8.svg +0 -0
  45. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-9.svg +0 -0
  46. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon-red.svg +0 -0
  47. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/icon.svg +0 -0
  48. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/mark-done.png +0 -0
  49. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/mark-done.svg +0 -0
  50. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/pause.png +0 -0
  51. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/pause.svg +0 -0
  52. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/settings.png +0 -0
  53. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/icon/settings.svg +0 -0
  54. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/session_edit_dialog.py +0 -0
  55. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/session_options.py +0 -0
  56. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/sessions.csv +0 -0
  57. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/sound/bell-xylophone-g.mp3 +0 -0
  58. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/sound/dinner-bell.mp3 +0 -0
  59. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/sound/xylophone-chord.mp3 +0 -0
  60. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/styles.css +0 -0
  61. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/task_window.py +0 -0
  62. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus/user_idle_time.py +0 -0
  63. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus.egg-info/dependency_links.txt +0 -0
  64. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus.egg-info/entry_points.txt +0 -0
  65. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus.egg-info/requires.txt +0 -0
  66. {nowfocus-0.5.6 → nowfocus-0.5.7}/src/nowfocus.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nowfocus
3
- Version: 0.5.6
3
+ Version: 0.5.7
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
@@ -58,7 +58,7 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
58
58
  - Task prioritization
59
59
  - Infinitely nestable lists
60
60
  - Pomodoro timer
61
- - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
61
+ - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow-through
62
62
  - Randomness interrupt bell (optional) to keep you on track with tracking your time
63
63
  - Keyboard-driven interface
64
64
  - Offline to-do list cache
@@ -15,7 +15,7 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
15
15
  - Task prioritization
16
16
  - Infinitely nestable lists
17
17
  - Pomodoro timer
18
- - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
18
+ - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow-through
19
19
  - Randomness interrupt bell (optional) to keep you on track with tracking your time
20
20
  - Keyboard-driven interface
21
21
  - Offline to-do list cache
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nowfocus"
3
- version = "0.5.6"
3
+ version = "0.5.7"
4
4
  authors = [
5
5
  { name="AltruistEnterprises" },
6
6
  ]
@@ -62,7 +62,7 @@ class Application(Gtk.Application):
62
62
 
63
63
  try:
64
64
 
65
- self.window = None
65
+ # self.window = None # Think this is unused
66
66
 
67
67
  self.is_running = False
68
68
  self.device_in_use = True
@@ -83,7 +83,17 @@ class Application(Gtk.Application):
83
83
  import install
84
84
  install.run_first_load_actions()
85
85
 
86
- utils.db_schema_update()
86
+ try:
87
+ self.version = importlib.metadata.version(conf.app_id)
88
+ print('nowfocus version', self.version)
89
+ except Exception as e:
90
+ print(e)
91
+
92
+ if get_system_db_value('db_schema_version') != self.version:
93
+
94
+ from upgrade import do_upgrades
95
+ do_upgrades(self)
96
+
87
97
 
88
98
  self.async_refresh()
89
99
 
@@ -358,11 +368,8 @@ class Application(Gtk.Application):
358
368
 
359
369
  def resume_session_from_db(self):
360
370
  try:
361
- db_session = db_query("SELECT value FROM system WHERE field = 'session'")
362
-
363
- if db_session:
364
- s = json.loads(db_session[0]['value'])
365
- s['start_time'] = datetime.strptime(s['start_time'],'%Y-%m-%d %H:%M:%S.%f')
371
+ s = get_session_from_system_db()
372
+ if s:
366
373
  self.session = s
367
374
  self.is_running = True
368
375
  dbg("resuming session",s['label'],l=2, s='session')
@@ -470,7 +477,7 @@ class Application(Gtk.Application):
470
477
  process.wait()
471
478
  else:
472
479
  print("Launching command with subprocess.run", command)
473
- subprocess.run(command)
480
+ subprocess.run(command,shell=True)
474
481
 
475
482
  print("running task command complete:", command)
476
483
 
@@ -524,14 +531,17 @@ class Application(Gtk.Application):
524
531
 
525
532
  # Get time tracker for this tasks todolist
526
533
  todolist_conf = conf.user['todolists'][task['todolist']]
527
- timetracker_conf = conf.user['timetrackers'][todolist_conf['timetracker']]
528
-
529
- dbg("Save Session to "+ todolist_conf['timetracker'])
530
- try:
531
- save_thread = threading.Thread(target=conf.timetracker_connectors[timetracker_conf['type']].save_session, args=(session,timetracker_conf) )
532
- save_thread.start()
533
- except Exception as e:
534
- error_notice('Error Saving Time Data'," Recording timetracking for "+ task['label']+" in "+timetracker_conf['label']+" had a serious failure",e )
534
+
535
+ if 'timetracker' in todolist_conf:
536
+
537
+ dbg("Save Session to "+ todolist_conf['timetracker'])
538
+ try:
539
+
540
+ timetracker_conf = conf.user['timetrackers'][todolist_conf['timetracker']]
541
+ save_thread = threading.Thread(target=conf.timetracker_connectors[timetracker_conf['type']].save_session, args=(session,timetracker_conf) )
542
+ save_thread.start()
543
+ except Exception as e:
544
+ error_notice('Error Saving Time Data'," Recording timetracking for "+ task['label']+" in "+timetracker_conf['label']+" had a serious failure",e )
535
545
 
536
546
  session['timetracker'] = todolist_conf['timetracker']
537
547
 
@@ -71,6 +71,7 @@ prototype_settings = {
71
71
  'max_top_level_menu_items':10,
72
72
  'hours_search_timeframe':'this year',
73
73
  'invoice_hourly_rate':0,
74
+ 'default_list_for_new_tasks':'Most recently used list',
74
75
 
75
76
  "custom_pomodoro_intervals": {
76
77
  "email":7 #minutes
@@ -18,7 +18,7 @@ def add_new_task(user_conf,parent_list,task_label):
18
18
  headers = {'content-type': 'application/json'}
19
19
  data = {'title': task_label}
20
20
 
21
- utils.dbg('add_new_task() parent_list',parent_list, l=-1)
21
+ utils.dbg('add new task to ',parent_list, l=-1)
22
22
 
23
23
  response = requests.put(user_conf['url']+"api/v1/projects/"+str(parent_list['data']['id'])+"/tasks",json=data, headers=head)
24
24
 
@@ -103,11 +103,17 @@ def get_todos(user_conf):
103
103
 
104
104
 
105
105
  headers = {"Authorization": "Bearer "+user_conf['token']}
106
- projects_lists = requests.get(user_conf['url']+"api/v1/projects", headers=headers).json()
107
-
106
+ response = requests.get(user_conf['url']+"api/v1/projects", headers=headers)
107
+ # print("response.headers")
108
+ # print(response.headers)
109
+ projects_lists = response.json()
108
110
  # print("vikunja projects")
109
111
  # print(json.dumps(projects_lists, indent=4))
110
112
 
113
+ if 'message' in projects_lists:
114
+ # This occurs with an expired token
115
+ utils.dbg(projects_lists['message'], notification="Vikunja API Error: "+projects_lists['message'], s='vikunja', l=0)
116
+
111
117
  # for i in projects_list:
112
118
  for i, itemIter in enumerate(projects_lists):
113
119
 
@@ -155,54 +161,6 @@ def get_todos(user_conf):
155
161
  if lists[parent_id]['status'] == -1:
156
162
  lists[id]['status'] = -1
157
163
 
158
-
159
-
160
- # NOTE: Originally tasks where queried per list, however this no longer works well because it requires a 'view' id, which comes wih various un-predictable baked-in filters (like done=true). Using the tasks/all api endpoint avoids this but quickly hits the default maxitemsperpage limit (which could, less obviously, show up in the per list method).
161
-
162
- # else: print('parent_project_id', p['parent_project_id'], 'not in projects_lists')
163
-
164
- # view_id = p['views'][0]['id']
165
- # utils.dbg({'view_id':view_id},'vikunja')
166
-
167
-
168
- # Including 'done' tasks (Doesn't seem o includ them actually)'
169
- # projects_tasks = requests.get(user_conf['url']+"api/v1/projects/"+str(p['id'])+"/views/"+str(view_id)+"/tasks?sort_by[]=position&order_by[]=asc&equalspage=1", headers=headers).json()
170
-
171
- # Filtering out done tasks, Works
172
- # projects_tasks = requests.get(user_conf['url']+"api/v1/projects/"+str(p['id'])+"/views/"+str(view_id)+"/tasks?sort_by[]=position&order_by[]=asc&filter_by[]=done&filter_value[]=false&filter_comparator[]=equals&filter_comparator[]=equalspage=1", headers=headers).json()
173
-
174
- # projects_tasks = requests.get(user_conf['url']+"api/v1/projects/"+str(p['id'])+"/tasks", headers=headers).json()
175
-
176
- # print("vikunja projects_tasks")
177
- # print(json.dumps(projects_tasks, indent=4))
178
- # utils.pretty_print(projects_tasks)
179
-
180
- # for i, itemIter in enumerate(projects_tasks):
181
-
182
- # t = projects_tasks[i]
183
- # t['id'] = str(t['id'])
184
-
185
- # tasks[t['id']] = {
186
- # 'id':t['id'],
187
- # 'label':t['title'],
188
- # 'parent_id':p['id'],
189
- # 'parent_label':p['title'],
190
- # 'todolist':user_conf['id'],
191
- # 'data':t
192
- # }
193
-
194
- # if t['done'] == True:
195
- # tasks[t['id']]['status'] = False
196
-
197
-
198
- # # Show favorites in main menu
199
- # if t['is_favorite'] == True:
200
- # tasks[t['id']]['priority'] = 1
201
- # elif t['priority'] in priorities:
202
- # print("Priority task", t['title'])
203
- # tasks[t['id']]['priority'] = priorities[t['priority']]
204
- # print(tasks[t['id']])
205
-
206
164
  # beware per_page config ( generally in /opt/vikunja/config.yml) maxitemsperpage defaults to 20 so this really won't work on a default setup
207
165
  # maxitemsperpage: 5000
208
166
  # ?per_page=10000
@@ -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 -f
6
+ Exec=nowfocus
7
7
  Icon=nowfocus
8
8
  Terminal=false
9
9
  Type=Application
@@ -80,7 +80,6 @@ def copy_desktop_integration_files():
80
80
  (home+'/.config/autostart/','nowfocus.desktop')
81
81
  )
82
82
 
83
-
84
83
  for file in files:
85
84
  try:
86
85
  Path(file[0]).mkdir(parents=True, exist_ok=True)
@@ -44,10 +44,9 @@ class NewTaskWDialog(Gtk.Dialog):
44
44
  box.add(Gtk.Box(border_width=10)) #spacer
45
45
 
46
46
  default_list = get_most_recent_list(self.app.session)
47
- if default_list:
48
- self.selected_list = db_get_item_by_id(default_list,table='lists')
49
47
 
50
- box.add(choose_from_lists(self.select_list, default_list))
48
+ box.add(choose_from_lists(self.select_list, default_list['id']))
49
+ self.selected_list = default_list
51
50
 
52
51
  box.add(Gtk.Box(border_width=10)) #spacer
53
52
 
@@ -80,7 +79,9 @@ class NewTaskWDialog(Gtk.Dialog):
80
79
  task_label = self.task_label_entry.get_text().strip()
81
80
 
82
81
  if not task_label:
83
- error_notice("Please Enter a name before saving new task")
82
+ self.task_label_entry.grab_focus()
83
+ self.task_label_entry.set_placeholder_text('Please enter a task')
84
+ # error_notice("Please Enter a name before saving new task")
84
85
  return False
85
86
 
86
87
  parent_list = self.selected_list
@@ -923,7 +923,7 @@ class EditAddConnectorDialog(Gtk.Dialog):
923
923
 
924
924
 
925
925
  if key in ['file']:
926
- self.entries[key] = Gtk.FileChooserButton(title="Select Todo File")
926
+ self.entries[key] = Gtk.FileChooserButton(title="Select "+connector_category+" file")
927
927
  self.entries[key].set_action(Gtk.FileChooserAction.OPEN)
928
928
 
929
929
  else:
@@ -0,0 +1,99 @@
1
+ import conf
2
+ from utils import *
3
+ # from pkg_resources import parse_version
4
+
5
+
6
+ def do_upgrades(app):
7
+ db_schema_version = get_system_db_value('db_schema_version')
8
+
9
+ if db_schema_version == '0.2':
10
+
11
+ db_query("ALTER TABLE lists DROP COLUMN status")
12
+ db_query("ALTER TABLE tasks DROP COLUMN status")
13
+
14
+ db_query("ALTER TABLE lists ADD COLUMN status INTEGER DEFAULT 1")
15
+ db_query("ALTER TABLE tasks ADD COLUMN status INTEGER DEFAULT 1")
16
+
17
+ db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.3')")
18
+
19
+ db_schema_version = '0.3'
20
+
21
+
22
+ if db_schema_version == '0.3':
23
+ db_query("ALTER TABLE sessions ADD COLUMN timetracker TEXT")
24
+ db_query("ALTER TABLE sessions ADD COLUMN notes TEXT")
25
+ db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.4')")
26
+ print('adding timetracker column to session table')
27
+
28
+ for todolist_id, todo in conf.user['todolists'].items():
29
+ db_query("UPDATE sessions SET timetracker = ? WHERE todolist = ?",(todo['timetracker'],todolist_id) )
30
+
31
+ db_schema_version = '0.4'
32
+
33
+ if db_schema_version == '0.4':
34
+ dbg('Schema Update from', db_schema_version, 'to 0.5',l=-1)
35
+
36
+ print('Adding priority column to session table')
37
+
38
+ db_query("ALTER TABLE sessions ADD COLUMN priority INTEGER DEFAULT 0")
39
+
40
+ for session in db_query("SELECT DISTINCT task_id, extended_label FROM sessions"):
41
+ t = db_get_item_by_id(session['task_id'],dgb_error_level_for_failure=3)
42
+ if t and t['priority'] > 0:
43
+ print("setting",session['extended_label']," session priority to ",t['priority'] )
44
+
45
+ db_query("UPDATE sessions SET priority = ? WHERE task_id = ?",(t['priority'],session['task_id']))
46
+
47
+ print("Total hours priority sessions",round(divide(db_query("SELECT SUM(duration) as total FROM sessions WHERE priority > 0")[0]['total'],3600),2))
48
+ db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
49
+
50
+ db_schema_version = '0.5'
51
+
52
+ if db_schema_version == '0.5':
53
+ dbg('Schema Update from', db_schema_version, 'to 0.5.6',l=-1)
54
+
55
+ from install import copy_desktop_integration_files
56
+ copy_desktop_integration_files()
57
+
58
+ db_schema_version = "0.5.6"
59
+ set_system_db_value("db_schema_version",db_schema_version)
60
+
61
+ # if db_schema_version == 0.5:
62
+ # db_query("ALTER TABLE tasks ADD COLUMN tags TEXT DEFAULT '{}'")
63
+ # db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
64
+ # db_schema_version = 0.6
65
+
66
+
67
+ dbg('db_schema_version updated to', db_schema_version,s='db')
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+ # update time_target format
82
+ # for id, tt in user['time_targets']['lists'].items():
83
+ # if 'within_value' not in tt:
84
+ # print("Updating time target to new format ",tt)
85
+ # tt['within_value'] = tt['num_days']
86
+ # tt['within_unit'] = 'days'
87
+ # print(tt)
88
+ # if 'status' not in tt:
89
+ # tt['status'] = True
90
+
91
+
92
+ # for id, tt in user['time_targets']['tasks'].items():
93
+ # if 'within_value' not in tt:
94
+ # print("Updating time target to new format ",tt)
95
+ # tt['within_value'] = tt['num_days']
96
+ # tt['within_unit'] = 'days'
97
+ # print(tt)
98
+ # if 'status' not in tt:
99
+ # tt['status'] = True
@@ -7,7 +7,6 @@ from datetime import datetime, timezone, timedelta
7
7
  from dateutil.relativedelta import relativedelta
8
8
  import copy
9
9
  import threading
10
-
11
10
  import sqlite3
12
11
  from contextlib import closing
13
12
  from pathlib import Path
@@ -30,11 +29,11 @@ notify.init(conf.app_name)
30
29
  lists = {}
31
30
 
32
31
 
33
- def dbg(*data, s="", l=2, e=None, notify=None):
32
+ def dbg(*data, s="", l=2, e=None, notification=None):
34
33
  ''' Any number of positional args optionally
35
34
  l: level {-1: Default output, 0:Error, 1:Warning, 2:Info, 3:Details}
36
35
  s: system (Debuggable systems: 'taskwindow','signals','todoloading','user_settings', 'targets', 'performance')
37
- notify: Show a notification with the content of notify.
36
+ notification: Show a notification with the content of notification.
38
37
  e (Exception object) traceback will be printed
39
38
 
40
39
  '''
@@ -47,8 +46,8 @@ def dbg(*data, s="", l=2, e=None, notify=None):
47
46
 
48
47
  if "all" in conf.debug_systems or system in conf.debug_systems or level <= conf.debug_level:
49
48
 
50
- if notify:
51
- notify.Notification.new(conf.app_name+" "+levels[level], str(notify), None).show()
49
+ if notification:
50
+ notify.Notification.new(conf.app_name+" "+levels[level], str(notification), None).show()
52
51
 
53
52
  filter(None,data)
54
53
  if len(data) == 1:
@@ -68,7 +67,7 @@ def dbg(*data, s="", l=2, e=None, notify=None):
68
67
 
69
68
  if e and isinstance(e,Exception):
70
69
  traceback.print_tb(e.__traceback__)
71
- print(e)
70
+ print(e,"\n")
72
71
 
73
72
  def error_notice(title, details = None, e = None):
74
73
  print('ERROR',title,details,e)
@@ -268,20 +267,20 @@ def extended_label(i):
268
267
  return o
269
268
 
270
269
  # TODO cleanup this
271
- def lists_cache(new_lists = None):
272
- '''Set or get a dict of (all) lists. This is useful but very gimpy. If new_lists is supplied it will exclusively use that (until it is replaced or the global lists var is emptied)'''
270
+ def lists_cache(new_lists = None, reset = False):
271
+ '''Set or get a dict of (all) lists. If new_lists is supplied it will exclusively use that (until it is replaced or the global lists var is emptied) This is useful (because it can be used wih input when the values are not yet available in the databse) but wonky. '''
272
+
273
273
  global lists
274
274
 
275
275
  if(new_lists):
276
276
  lists = new_lists
277
- # TODO: add this to the existing rather than overwriting...
277
+ # TODO: add a param to append to the existing lists rather than overwriting...
278
278
 
279
- elif(lists == {}):
280
- lists = db_query('SELECT * FROM lists WHERE status = 1 ORDER BY extended_label DESC',None, 'id') # TODO: limit to active todolists
279
+ elif reset or lists == {}:
280
+ lists = db_query('SELECT * FROM lists WHERE status = 1 ORDER BY extended_label DESC',None, 'id')
281
281
 
282
- # print('list_cache l',lists)
283
- for l in lists.items():
284
- lists[l['id']]['data'] = json.loads(l['data'])
282
+ for id, l in lists.items():
283
+ lists[l['id']] = proc_db_item(l,'lists')
285
284
 
286
285
  return lists
287
286
 
@@ -336,7 +335,7 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
336
335
  return result
337
336
 
338
337
  except Exception as e:
339
- dbg(e,"sql",sql,'parameters',parameters,s="db",l=0)
338
+ dbg("sql",sql,'parameters',parameters,s="db",l=0,e=e)
340
339
  if error_handling > 0:
341
340
  error_notice("database error",str(e))
342
341
  if error_handling > 1:
@@ -347,88 +346,6 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
347
346
  return []
348
347
 
349
348
 
350
- def db_schema_update():
351
-
352
- try:
353
- db_schema_version = float(db_query("SELECT field, value FROM system WHERE field = 'db_schema_version' ",error_handling=2)[0]['value'])
354
- except Exception as e:
355
- print("Updating db_schema_version to 0.2")
356
-
357
- db_query("CREATE TABLE system (field TEXT PRIMARY KEY NOT NULL, value TEXT)")
358
- db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.2')")
359
-
360
- db_schema_version = float(db_query("SELECT field, value FROM system WHERE field = 'db_schema_version' ")[0]['value'])
361
-
362
- db_query("ALTER TABLE lists ADD COLUMN status INTEGER DEFAULT 1")
363
- db_query("ALTER TABLE tasks ADD COLUMN status INTEGER DEFAULT 1")
364
-
365
- db_query("ALTER TABLE lists ADD COLUMN extended_label TEXT")
366
- db_query("ALTER TABLE tasks ADD COLUMN extended_label TEXT")
367
-
368
- db_query("ALTER TABLE sessions ADD COLUMN extended_label TEXT")
369
-
370
- # Since these column are just a cache, replace instead of copying columns
371
- db_query("ALTER TABLE tasks DROP COLUMN priority")
372
- db_query("ALTER TABLE tasks DROP COLUMN data")
373
- db_query("ALTER TABLE lists DROP COLUMN priority")
374
- db_query("ALTER TABLE lists DROP COLUMN data")
375
-
376
- db_query("ALTER TABLE lists ADD COLUMN priority INTEGER DEFAULT 0")
377
- db_query("ALTER TABLE lists ADD COLUMN data TEXT DEFAULT '{}'")
378
- db_query("ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0")
379
- db_query("ALTER TABLE tasks ADD COLUMN data TEXT DEFAULT '{}'")
380
-
381
-
382
- if db_schema_version == 0.2:
383
-
384
- db_query("ALTER TABLE lists DROP COLUMN status")
385
- db_query("ALTER TABLE tasks DROP COLUMN status")
386
-
387
- db_query("ALTER TABLE lists ADD COLUMN status INTEGER DEFAULT 1")
388
- db_query("ALTER TABLE tasks ADD COLUMN status INTEGER DEFAULT 1")
389
-
390
- db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.3')")
391
-
392
- db_schema_version = 0.3
393
-
394
-
395
- if db_schema_version == 0.3:
396
- db_query("ALTER TABLE sessions ADD COLUMN timetracker TEXT")
397
- db_query("ALTER TABLE sessions ADD COLUMN notes TEXT")
398
- db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.4')")
399
- print('adding timetracker column to session table')
400
-
401
- for todolist_id, todo in conf.user['todolists'].items():
402
- db_query("UPDATE sessions SET timetracker = ? WHERE todolist = ?",(todo['timetracker'],todolist_id) )
403
-
404
- db_schema_version = 0.4
405
-
406
- if db_schema_version == 0.4:
407
- print('Adding priority column to session table')
408
-
409
- db_query("ALTER TABLE sessions ADD COLUMN priority INTEGER DEFAULT 0")
410
-
411
- for session in db_query("SELECT DISTINCT task_id, extended_label FROM sessions"):
412
- t = db_get_item_by_id(session['task_id'],dgb_error_level_for_failure=3)
413
- if t and t['priority'] > 0:
414
- print("setting",session['extended_label']," session priority to ",t['priority'] )
415
-
416
- db_query("UPDATE sessions SET priority = ? WHERE task_id = ?",(t['priority'],session['task_id']))
417
-
418
- print("Total hours priority sessions",round(divide(db_query("SELECT SUM(duration) as total FROM sessions WHERE priority > 0")[0]['total'],3600),2))
419
- db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
420
-
421
- db_schema_version = 0.5
422
-
423
- # if db_schema_version == 0.5:
424
- # db_query("ALTER TABLE tasks ADD COLUMN tags TEXT DEFAULT '{}'")
425
- # db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
426
- # db_schema_version = 0.6
427
-
428
-
429
- dbg('db_schema_version', db_schema_version,s='db')
430
-
431
-
432
349
  def reindex(t=None):
433
350
  if t:
434
351
  thread = threading.Thread(target=reindex_one,args=(t,))
@@ -447,7 +364,9 @@ def reindex_all():
447
364
  for tid, t in get_timetarget_priority_tasks().items():
448
365
  db_query("UPDATE taskindex set priority = ? WHERE id = ?",(t['priority'],t['id']))
449
366
 
450
- db_query("REPLACE INTO system(field, value) VALUES(?,?)",('taskindex_update_time',now().strftime("%Y-%m-%d %H:%M:%S")))
367
+
368
+ set_system_db_value('taskindex_update_time', now().strftime("%Y-%m-%d %H:%M:%S"))
369
+ # db_query("REPLACE INTO system(field, value) VALUES(?,?)",('taskindex_update_time',now().strftime("%Y-%m-%d %H:%M:%S")))
451
370
  # print('reindex done')
452
371
  timeit()
453
372
 
@@ -464,14 +383,18 @@ def reindex_one(t):
464
383
 
465
384
  db_query("INSERT INTO taskindex(id, extended_label, priority, status) SELECT id, extended_label, ?, status FROM tasks WHERE id = ? ",(time_target_priority(t),t['id'],))
466
385
 
467
- print("reindexed",t['label'])
386
+ dbg("reindexed",t['label'])
468
387
  # print(db_query("SELECT * FROM taskindex where id = ?",(t['id'],)))
469
- db_query("REPLACE INTO system(field, value) VALUES(?,?)",('taskindex_update_time',now().strftime("%Y-%m-%d %H:%M:%S")))
388
+ # db_query("REPLACE INTO system(field, value) VALUES(?,?)",('taskindex_update_time',now().strftime("%Y-%m-%d %H:%M:%S")))
389
+ set_system_db_value('taskindex_update_time', now().strftime("%Y-%m-%d %H:%M:%S"))
390
+
470
391
  return True
471
392
 
472
393
 
473
394
  def taskindex_updated_time():
474
395
  ''' returns system db taskindex_update_time string formatted as %Y-%m-%d %H:%M:%S '''
396
+ get_system_db_value('taskindex_update_time')
397
+
475
398
  update_time = db_query("SELECT value FROM system WHERE field = 'taskindex_update_time'")[0]['value']
476
399
 
477
400
  # print('taskindex_updated_time',update_time)
@@ -631,7 +554,7 @@ def db_set_todolist(todolist_id,lists,tasks):
631
554
  insert_tasks[id] = db_prepare_item(i,'tasks')
632
555
 
633
556
  # Clear incomplete lists cache
634
- lists_cache({})
557
+ lists_cache(reset=True)
635
558
 
636
559
  # for testing
637
560
  # insert_tasks = {'3':insert_tasks['3'],'294':insert_tasks['294']}
@@ -709,13 +632,17 @@ def db_cleanup(widget = None):
709
632
 
710
633
  # exit()
711
634
 
712
- def db_get_item_by_id(id,table = 'tasks',dgb_error_level_for_failure=0):
635
+ def db_get_item_by_id(id, table = 'tasks',dgb_error_level_for_failure=0):
713
636
 
714
637
  if table in ['task','list']:
715
638
  table = table+'s'
716
639
  if table not in ['tasks','lists']:
717
640
  dbg('bad table "'+str(table)+'" passed to db_get_item_by_id',id,l=0)
718
641
  return {}
642
+
643
+ # in case an item dict was passed instead of an id
644
+ if isinstance(id, dict):
645
+ return id
719
646
 
720
647
  try:
721
648
  data = db_query("SELECT * FROM "+table+" WHERE id = ?",(id,))
@@ -865,7 +792,8 @@ def db_set_session_cache(s):
865
792
  ''' Add active session to system db table. Not to be confused with db_save_session '''
866
793
  db_session = copy.deepcopy(s)
867
794
  db_session['start_time'] = db_session['start_time'].strftime("%Y-%m-%d %H:%M:%S.%f%z")
868
- db_query("REPLACE INTO system(field, value) VALUES(:field, :value)", {'field':'session','value':json.dumps(db_session)})
795
+
796
+ set_system_db_value('session', json.dumps(db_session))
869
797
 
870
798
 
871
799
  def get_total_time(id, category = 'tasks', start_time = None, end_time = None, get_minutes = None):
@@ -1057,17 +985,17 @@ def refresh_todolist(todo, catch_errors = False):
1057
985
  ''' Refresh a single todo. runs db_set_todolist and returns todos. Exceptions must be handled my caller '''
1058
986
 
1059
987
  try:
1060
- dbg('Refreshing '+todo['label'],l=-1)
988
+ dbg('Refreshing '+todo['label'],l=2)
1061
989
 
1062
990
  todos = conf.todo_connectors[todo['type']].get_todos(todo)
1063
991
 
1064
- dbg('Done Refreshing '+todo['label'],l=-1)
992
+ dbg('Refreshing '+todo['label'],l=-1)
1065
993
 
1066
994
  db_set_todolist(todo['id'],todos['lists'],todos['tasks'])
1067
995
 
1068
996
  except Exception as e:
1069
997
  if catch_errors:
1070
- error_notice('Error Loading '+todo['label']+' i refresh_todolist',e)
998
+ dbb(notify='Error Loading '+todo['label']+' i refresh_todolist',e=e)
1071
999
  todos = {'lists': {}, 'tasks':{}}
1072
1000
 
1073
1001
  else:
@@ -1088,13 +1016,13 @@ def handle_todo_read_error(todo_conf,e):
1088
1016
  if isinstance(e, (FileExistsError, FileNotFoundError, PermissionError)):
1089
1017
  handle_todo_file_access_error(todo_conf,e)
1090
1018
 
1091
- dbg(todo_conf['label']+ " Access Error",e,l=0,notify=True)
1019
+ dbg(todo_conf['label']+ " Access Error",e,l=0,notification=True)
1092
1020
 
1093
1021
 
1094
1022
  def handle_todo_file_read_error(todo_conf,e):
1095
1023
  conf.user['todolists'][todo_conf['id']]['status'] = False
1096
1024
  save_user_settings()
1097
- dbg(todo_conf['label']+ " Deactivated",e,l=-1,notify=True)
1025
+ dbg(todo_conf['label']+ " Deactivated",e,l=-1,notification=True)
1098
1026
 
1099
1027
 
1100
1028
  def get_todolists(use_db_cache = False):
@@ -1125,12 +1053,12 @@ def get_todolists(use_db_cache = False):
1125
1053
  if conf.debug_level == 3 or 'todoloading' in conf.debug_systems or todo['type'] in conf.debug_systems :
1126
1054
  raise e
1127
1055
 
1128
- error_notice('Error refreshing '+todo['label'],e)
1056
+ dbg(notification='Error refreshing '+todo['label'],e=e)
1129
1057
 
1130
1058
  try:
1131
1059
  todos = db_get_todolist(todo['id'])
1132
1060
  except Exception as e:
1133
- error_notice('Also Failed to load '+todo['label']+' From cache' ,e)
1061
+ error_notice('Also Failed to load '+todo['label']+' From cache' ,e=e)
1134
1062
  todos = {'lists': {}, 'tasks':{}}
1135
1063
 
1136
1064
  tasks.update(todos['tasks'])
@@ -1147,14 +1075,76 @@ def get_todolists(use_db_cache = False):
1147
1075
  return o
1148
1076
 
1149
1077
 
1078
+ def get_system_db_value(field, json = False):
1079
+ try:
1080
+ o = db_query("SELECT value FROM system WHERE field = ? ",(field,))
1081
+ if o:
1082
+ o = o[0]['value']
1083
+ else:
1084
+ dbg('no system_db_value for', field, l=1)
1085
+
1086
+ if json:
1087
+ o = json.loads(o)
1088
+ return o
1089
+ except Exception as e:
1090
+ dbg("Error getting",field, 'from system db',e,l=0)
1091
+
1092
+
1093
+ def set_system_db_value(field, value, json = False):
1094
+ try:
1095
+ if json:
1096
+ value = json.dumps(value)
1097
+
1098
+ db_query("REPLACE INTO system(field, value) VALUES(:field, :value)", {'field':field,'value':value})
1099
+
1100
+ except Exception as e:
1101
+ dbg("Error setting", field, value, 'in system db',e,l=0)
1102
+
1103
+
1104
+ def get_session_from_system_db():
1105
+ try:
1106
+ db_session = db_query("SELECT value FROM system WHERE field = 'session'")
1107
+
1108
+ if db_session:
1109
+ s = json.loads(db_session[0]['value'])
1110
+ s['start_time'] = datetime.strptime(s['start_time'],'%Y-%m-%d %H:%M:%S.%f')
1111
+ return s
1112
+ except Exception as e:
1113
+ dbg("Error in get_session_from_system_db session",e,l=1)
1114
+
1115
+
1150
1116
  def get_most_recent_list(session = None):
1117
+ ''' returns a list object '''
1118
+ lists = lists_cache()
1119
+ # print(lists.keys())
1151
1120
 
1152
- if session and 'task' in session and 'parent_id' in session['task']:
1153
- return session['task']['parent_id']
1121
+ try:
1122
+ return lists[session['task']['parent_id']]
1154
1123
 
1155
- last_session = db_query("SELECT parent_id FROM sessions WHERE parent_id IN (SELECT id FROM lists WHERE status = 1) ORDER BY sessions.start_time DESC LIMIT 1")
1156
- if last_session:
1157
- return last_session[0]['parent_id']
1124
+ except Exception as e:
1125
+ pass
1126
+
1127
+ try:
1128
+ return lists[get_session_from_system_db()['task']['parent_id']]
1129
+
1130
+ except Exception as e:
1131
+ pass
1132
+
1133
+ try:
1134
+ return lists[db_query("SELECT parent_id FROM sessions WHERE parent_id IN (SELECT id FROM lists WHERE status = 1) ORDER BY sessions.start_time DESC LIMIT 1")[0]['parent_id']]
1135
+
1136
+ except Exception as e:
1137
+ return lists.items()[0]
1138
+
1139
+
1140
+
1141
+ def get_default_list_for_new_tasks():
1142
+ # if conf.user['default_list_for_new_tasks'] != 'Most recently used list':
1143
+ # try:
1144
+
1145
+ # List that was last added to
1146
+
1147
+ return get_most_recent_list()
1158
1148
 
1159
1149
 
1160
1150
  def choose_from_lists(callback, selected_list_id = None, session = None, accepts_tasks = True, show_todolist_headers = True, truncate_labels_to_chars = 100):
@@ -1175,25 +1165,20 @@ def choose_from_lists(callback, selected_list_id = None, session = None, accepts
1175
1165
  # scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
1176
1166
  scrolled_window.add(box)
1177
1167
 
1178
- todolist = None
1179
-
1180
- conditions = " "
1181
-
1182
- # if skip_top_level_lists:
1183
- # conditions += " AND parent_label IS NOT NULL "
1184
-
1185
- lists = db_query("SELECT lists.*, (SELECT start_time FROM sessions WHERE todolist = lists.todolist ORDER BY start_time DESC LIMIT 1 ) as last_session_on_todo FROM lists WHERE status = '1' "+conditions+" ORDER BY last_session_on_todo DESC, CASE WHEN parent_id IS NULL THEN 1 ELSE 2 END, extended_label ASC ", None, 'id')
1168
+ lists = db_query("SELECT lists.*, (SELECT start_time FROM sessions WHERE todolist = lists.todolist ORDER BY start_time DESC LIMIT 1 ) as last_session_on_todo FROM lists WHERE status = '1' ORDER BY last_session_on_todo DESC, CASE WHEN parent_id IS NULL THEN 1 ELSE 2 END, extended_label ASC ", None, 'id')
1186
1169
 
1187
1170
  r_button = None
1188
1171
  todolist_id = None
1189
1172
 
1190
1173
  for id, l in lists.items():
1191
1174
  try:
1192
- l['data'] = json.loads(l['data'])
1175
+ l = proc_db_item(l)
1193
1176
  if len(l['label']) > truncate_labels_to_chars:
1194
1177
  l['label'] = l['label'][:truncate_labels_to_chars] + '…'
1178
+
1179
+ nested_list_count = l['extended_label'].count(' > ')
1195
1180
 
1196
- label = f'{" · " * l['extended_label'].count(' > ')}' + GLib.markup_escape_text(l['label'])
1181
+ label = f'{" · " * nested_list_count}' + GLib.markup_escape_text(l['label'])
1197
1182
 
1198
1183
  if todolist_id != l['todolist']:
1199
1184
  todolist_id = l['todolist']
@@ -1210,14 +1195,12 @@ def choose_from_lists(callback, selected_list_id = None, session = None, accepts
1210
1195
 
1211
1196
  if accepts_tasks and l['data']['accepts_tasks'] == False:
1212
1197
  label_widget.set_markup(' '+label)
1213
-
1214
1198
  box.add(label_widget)
1215
1199
 
1216
1200
  else:
1217
1201
  r_button = Gtk.RadioButton(group=r_button)
1218
1202
  r_button.add(label_widget)
1219
1203
 
1220
-
1221
1204
  if l['id'] == selected_list_id:
1222
1205
  r_button.set_active(True)
1223
1206
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nowfocus
3
- Version: 0.5.6
3
+ Version: 0.5.7
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
@@ -58,7 +58,7 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
58
58
  - Task prioritization
59
59
  - Infinitely nestable lists
60
60
  - Pomodoro timer
61
- - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
61
+ - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow-through
62
62
  - Randomness interrupt bell (optional) to keep you on track with tracking your time
63
63
  - Keyboard-driven interface
64
64
  - Offline to-do list cache
@@ -13,9 +13,9 @@ src/nowfocus/sessions.csv
13
13
  src/nowfocus/settings.py
14
14
  src/nowfocus/styles.css
15
15
  src/nowfocus/task_window.py
16
+ src/nowfocus/upgrade.py
16
17
  src/nowfocus/user_idle_time.py
17
18
  src/nowfocus/utils.py
18
- src/nowfocus/version_migrator.py
19
19
  src/nowfocus.egg-info/PKG-INFO
20
20
  src/nowfocus.egg-info/SOURCES.txt
21
21
  src/nowfocus.egg-info/dependency_links.txt
@@ -1,20 +0,0 @@
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
File without changes
File without changes