nowfocus 0.4.0__py3-none-any.whl → 0.4.4__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 +168 -153
- nowfocus/conf.py +41 -48
- nowfocus/connectors/vikunja.py +16 -17
- nowfocus/desktop-extras/nowfocus.desktop +1 -1
- nowfocus/example-todo.txt +1 -2
- nowfocus/install.py +92 -0
- nowfocus/session_options.py +1 -1
- nowfocus/task_window.py +42 -22
- nowfocus/utils.py +68 -42
- nowfocus/version_migrator.py +20 -0
- {nowfocus-0.4.0.dist-info → nowfocus-0.4.4.dist-info}/METADATA +21 -28
- {nowfocus-0.4.0.dist-info → nowfocus-0.4.4.dist-info}/RECORD +16 -14
- {nowfocus-0.4.0.dist-info → nowfocus-0.4.4.dist-info}/WHEEL +0 -0
- {nowfocus-0.4.0.dist-info → nowfocus-0.4.4.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.4.0.dist-info → nowfocus-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.4.0.dist-info → nowfocus-0.4.4.dist-info}/top_level.txt +0 -0
nowfocus/connectors/vikunja.py
CHANGED
|
@@ -9,8 +9,6 @@ import requests
|
|
|
9
9
|
import conf
|
|
10
10
|
import utils
|
|
11
11
|
|
|
12
|
-
# UPDATE sessions SET task_id = "Vikunja_t" || task_id WHERE todolist = 'Vikunja';
|
|
13
|
-
# UPDATE sessions SET parent_id = "Vikunja_l" || parent_id WHERE todolist = 'Vikunja';
|
|
14
12
|
|
|
15
13
|
def vikunja_item_id(id, item_type, user_conf):
|
|
16
14
|
return user_conf['id'] +'_'+ item_type[0] + str(id)
|
|
@@ -20,7 +18,7 @@ def add_new_task(user_conf,parent_list,task_label):
|
|
|
20
18
|
headers = {'content-type': 'application/json'}
|
|
21
19
|
data = {'title': task_label}
|
|
22
20
|
|
|
23
|
-
utils.dbg('add_new_task() parent_list',parent_list, l
|
|
21
|
+
utils.dbg('add_new_task() parent_list',parent_list, l=-1)
|
|
24
22
|
|
|
25
23
|
response = requests.put(user_conf['url']+"api/v1/projects/"+str(parent_list['data']['id'])+"/tasks",json=data, headers=head)
|
|
26
24
|
|
|
@@ -104,7 +102,6 @@ def get_todos(user_conf):
|
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
|
|
107
|
-
priorities = {'DO NOW':1,'Urgent':2,'high':3}
|
|
108
105
|
headers = {"Authorization": "Bearer "+user_conf['token']}
|
|
109
106
|
projects_lists = requests.get(user_conf['url']+"api/v1/projects", headers=headers).json()
|
|
110
107
|
|
|
@@ -139,6 +136,9 @@ def get_todos(user_conf):
|
|
|
139
136
|
|
|
140
137
|
lists[id]['data']['accepts_tasks'] = True
|
|
141
138
|
|
|
139
|
+
# print(p['title']+' is_archived:',p['is_archived'])
|
|
140
|
+
# BUG: archived list aren't provided (But used to be ?) so sublists end up orphaned and dangerous...
|
|
141
|
+
|
|
142
142
|
if p['is_archived']:
|
|
143
143
|
lists[id]['status'] = -1
|
|
144
144
|
else:
|
|
@@ -152,6 +152,10 @@ def get_todos(user_conf):
|
|
|
152
152
|
if parent_id in lists:
|
|
153
153
|
lists[id]['parent_label'] = lists[parent_id]['label']
|
|
154
154
|
|
|
155
|
+
if lists[parent_id]['status'] == -1:
|
|
156
|
+
lists[id]['status'] = -1
|
|
157
|
+
|
|
158
|
+
|
|
155
159
|
|
|
156
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).
|
|
157
161
|
|
|
@@ -204,7 +208,6 @@ def get_todos(user_conf):
|
|
|
204
208
|
# ?per_page=10000
|
|
205
209
|
|
|
206
210
|
|
|
207
|
-
|
|
208
211
|
response = requests.get(user_conf['url']+"api/v1/tasks/all", headers=headers)
|
|
209
212
|
all_tasks = response.json()
|
|
210
213
|
|
|
@@ -218,8 +221,6 @@ def get_todos(user_conf):
|
|
|
218
221
|
# print(json.dumps(all_tasks, indent=4))
|
|
219
222
|
utils.dbg("Got ",len(all_tasks),"Vikunja tasks", s='vikunja', l=2 )
|
|
220
223
|
|
|
221
|
-
utils.dbg("Got ",len(all_tasks),"Vikunja tasks", s='vikunja', l=2 )
|
|
222
|
-
|
|
223
224
|
for i, itemIter in enumerate(all_tasks):
|
|
224
225
|
|
|
225
226
|
t = all_tasks[i]
|
|
@@ -228,7 +229,7 @@ def get_todos(user_conf):
|
|
|
228
229
|
|
|
229
230
|
# utils.dbg(t, s='vikunja', l=3)
|
|
230
231
|
|
|
231
|
-
# Limit to
|
|
232
|
+
# Limit to tasks assigned, created by, or favorated by user
|
|
232
233
|
if user_conf["username"]:
|
|
233
234
|
if t['is_favorite'] != True:
|
|
234
235
|
|
|
@@ -264,13 +265,12 @@ def get_todos(user_conf):
|
|
|
264
265
|
tasks[id]['status'] = -1
|
|
265
266
|
|
|
266
267
|
except Exception as e:
|
|
267
|
-
|
|
268
|
+
utils.dbg("No list found for", t['title']," list_id",list_id, s='vikunja')
|
|
268
269
|
# raise e
|
|
269
270
|
|
|
270
271
|
if t['done'] == True:
|
|
271
272
|
tasks[id]['status'] = 0
|
|
272
273
|
|
|
273
|
-
# Prioritize if "due_date" is soon or passed (and not: "0001-01-01T00:00:00Z")
|
|
274
274
|
if 'due_date' in t and t['due_date'] != "0001-01-01T00:00:00Z":
|
|
275
275
|
|
|
276
276
|
due_date = datetime.strptime(t['due_date'],'%Y-%m-%dT%H:%M:%SZ')
|
|
@@ -282,14 +282,13 @@ def get_todos(user_conf):
|
|
|
282
282
|
if due_seconds > -86400:
|
|
283
283
|
tasks[id]['priority'] = 2
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|
|
285
|
+
if t['priority'] and t['priority'] > 1:
|
|
286
|
+
# Vikunja priority is 1 = lowest, 5 = highest
|
|
287
|
+
#
|
|
288
|
+
tasks[id]['priority'] = utils.invert_number_scale(utils.clamp(utils.force_number(t['priority']),0,5))
|
|
289
|
+
elif t['is_favorite'] == True:
|
|
287
290
|
tasks[id]['priority'] = 2
|
|
288
|
-
|
|
289
|
-
print("Priority task", t['title'])
|
|
290
|
-
tasks[id]['priority'] = priorities[t['priority']]
|
|
291
|
-
# print(tasks[id])
|
|
292
|
-
|
|
291
|
+
|
|
293
292
|
todos = {'lists':lists,'tasks':tasks}
|
|
294
293
|
return todos
|
|
295
294
|
|
nowfocus/example-todo.txt
CHANGED
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, 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.4')")
|
|
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
|
+
|
nowfocus/session_options.py
CHANGED
|
@@ -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
|
|
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
|
@@ -80,7 +80,6 @@ class TaskWindow(Gtk.Window):
|
|
|
80
80
|
|
|
81
81
|
self.l_sidebar = Gtk.VBox(spacing=5)
|
|
82
82
|
self.l_sidebar.get_style_context().add_class("subtle")
|
|
83
|
-
|
|
84
83
|
self.outer_box.add(self.l_sidebar)
|
|
85
84
|
|
|
86
85
|
self.center_box = Gtk.VBox(spacing=10)
|
|
@@ -94,18 +93,11 @@ class TaskWindow(Gtk.Window):
|
|
|
94
93
|
|
|
95
94
|
self.recreate_header()
|
|
96
95
|
|
|
97
|
-
self.r_sidebar_outer = Gtk.ScrolledWindow()
|
|
98
|
-
self.r_sidebar_outer.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
99
|
-
self.outer_box.add(self.r_sidebar_outer)
|
|
100
96
|
|
|
101
97
|
self.r_sidebar = Gtk.VBox(spacing=5)
|
|
102
98
|
self.r_sidebar.get_style_context().add_class("subtle")
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.r_sidebar.add_events(Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
|
106
|
-
self.r_sidebar.connect("enter-notify-event",self.test)
|
|
107
|
-
|
|
108
|
-
self.r_sidebar_outer.add(self.r_sidebar)
|
|
99
|
+
self.outer_box.add(self.r_sidebar)
|
|
100
|
+
|
|
109
101
|
|
|
110
102
|
self.tick_timer = GLib.timeout_add_seconds(1, self.tick)
|
|
111
103
|
|
|
@@ -125,15 +117,15 @@ class TaskWindow(Gtk.Window):
|
|
|
125
117
|
|
|
126
118
|
if passed_data:
|
|
127
119
|
dbg('taskwindow passed_data',passed_data,s='taskwindow')
|
|
128
|
-
if '
|
|
120
|
+
if 'user_inactive_time' in passed_data:
|
|
129
121
|
|
|
130
|
-
last_active_time = datetime.now() - timedelta(seconds=passed_data['
|
|
122
|
+
last_active_time = datetime.now() - timedelta(seconds=passed_data['user_inactive_time'])
|
|
131
123
|
|
|
132
124
|
last_active_str = time.strftime('%H:%M', last_active_time.timetuple())
|
|
133
125
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
self.header.add(
|
|
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)
|
|
137
129
|
|
|
138
130
|
pause_then_button = Gtk.Button(label="Finish Then")
|
|
139
131
|
|
|
@@ -230,6 +222,7 @@ class TaskWindow(Gtk.Window):
|
|
|
230
222
|
|
|
231
223
|
self.connect("destroy", self.on_destroy)
|
|
232
224
|
|
|
225
|
+
|
|
233
226
|
def test(self=None, one=None, two=None, **kwargs):
|
|
234
227
|
print('task_window test')
|
|
235
228
|
print(one)
|
|
@@ -385,24 +378,48 @@ class TaskWindow(Gtk.Window):
|
|
|
385
378
|
except Exception:
|
|
386
379
|
pass
|
|
387
380
|
|
|
381
|
+
|
|
388
382
|
def create_sidebars(self):
|
|
389
383
|
timeit()
|
|
384
|
+
|
|
385
|
+
self.l_sidebar.foreach(lambda child: child.destroy())
|
|
386
|
+
self.r_sidebar.foreach(lambda child: child.destroy())
|
|
387
|
+
|
|
390
388
|
if conf.user['open_task_window_fullscreen'] and conf.user['show_task_window_sidebars']:
|
|
391
|
-
self.r_sidebar_outer.set_size_request(350, -1)
|
|
392
389
|
self.l_sidebar.set_size_request(350, -1)
|
|
390
|
+
self.r_sidebar.set_size_request(350, -1)
|
|
393
391
|
|
|
394
|
-
|
|
392
|
+
focus_percent_label = Gtk.Label()
|
|
393
|
+
focus_percent_label.set_markup("<b>Focus: "+str(utils.get_percent_time_focused())+"%</b>")
|
|
394
|
+
self.r_sidebar.pack_start(focus_percent_label, False, True, 25)
|
|
395
395
|
|
|
396
|
-
|
|
397
|
-
self.
|
|
396
|
+
self.sessions_scrolledwindow = Gtk.ScrolledWindow()
|
|
397
|
+
self.sessions_scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
398
|
+
self.r_sidebar.pack_start(self.sessions_scrolledwindow, True, True, 0)
|
|
399
|
+
|
|
400
|
+
self.sessions_box = Gtk.VBox(spacing=5)
|
|
401
|
+
# self.sessions_box.get_style_context().add_class("subtle")
|
|
402
|
+
self.sessions_scrolledwindow.add(self.sessions_box)
|
|
403
|
+
|
|
404
|
+
# self.r_sidebar.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK)
|
|
405
|
+
# self.r_sidebar.add_events(Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
|
406
|
+
# self.r_sidebar.connect("enter-notify-event",self.test)
|
|
407
|
+
|
|
398
408
|
|
|
399
409
|
self.l_sidebar.add(choose_from_lists(self.select_list_callback, 'None', None, False))
|
|
400
410
|
|
|
401
|
-
#
|
|
402
|
-
show_sessions(None, self, self.
|
|
411
|
+
self.SessionEditDialog = SessionEditDialog # passed to show_sessions
|
|
412
|
+
show_sessions(None, self, self.sessions_box, None, 'start_time', '30',None, 30)
|
|
413
|
+
|
|
403
414
|
self.l_sidebar.show_all()
|
|
404
415
|
self.r_sidebar.show_all()
|
|
405
|
-
|
|
416
|
+
else:
|
|
417
|
+
self.r_sidebar.set_size_request(-1, -1)
|
|
418
|
+
self.l_sidebar.set_size_request(-1, -1)
|
|
419
|
+
|
|
420
|
+
self.l_sidebar.hide()
|
|
421
|
+
self.r_sidebar.hide()
|
|
422
|
+
timeit()
|
|
406
423
|
|
|
407
424
|
|
|
408
425
|
def on_notes_changed(self,buffer):
|
|
@@ -628,11 +645,14 @@ class TaskWindow(Gtk.Window):
|
|
|
628
645
|
self.unfullscreen()
|
|
629
646
|
self.get_style_context().remove_class("large")
|
|
630
647
|
conf.user['open_task_window_fullscreen'] = False
|
|
648
|
+
self.create_sidebars()
|
|
631
649
|
|
|
632
650
|
else:
|
|
633
651
|
self.fullscreen()
|
|
634
652
|
self.get_style_context().add_class("large")
|
|
635
653
|
conf.user['open_task_window_fullscreen'] = True
|
|
654
|
+
self.create_sidebars()
|
|
655
|
+
|
|
636
656
|
|
|
637
657
|
utils.save_user_settings()
|
|
638
658
|
|
nowfocus/utils.py
CHANGED
|
@@ -29,8 +29,6 @@ notify.init(conf.app_name)
|
|
|
29
29
|
|
|
30
30
|
lists = {}
|
|
31
31
|
|
|
32
|
-
db_file = conf.user_data_dir+"/data.db"
|
|
33
|
-
|
|
34
32
|
|
|
35
33
|
def dbg(*data, **kwargs):
|
|
36
34
|
''' Any number of positional args then kwargs
|
|
@@ -318,7 +316,7 @@ def first(i, default = None):
|
|
|
318
316
|
def save_user_settings():
|
|
319
317
|
print("Save updated user_settings")
|
|
320
318
|
dbg(conf.user)
|
|
321
|
-
with open(conf.
|
|
319
|
+
with open(conf.settings_file,"w") as settings_file:
|
|
322
320
|
json.dump(conf.user, settings_file)
|
|
323
321
|
|
|
324
322
|
|
|
@@ -326,8 +324,7 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
|
326
324
|
'''error_handling: 2 = raise error, 1 = notify error, 0 = ignore errors '''
|
|
327
325
|
# print('db_query parameters type: ',type(parameters))
|
|
328
326
|
# print('first(parameters)',first(parameters))
|
|
329
|
-
|
|
330
|
-
c = sqlite3.connect(db_file)
|
|
327
|
+
c = sqlite3.connect(conf.db_file)
|
|
331
328
|
c.row_factory = sqlite3.Row
|
|
332
329
|
try:
|
|
333
330
|
with c:
|
|
@@ -368,23 +365,6 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
|
368
365
|
return []
|
|
369
366
|
|
|
370
367
|
|
|
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
368
|
def db_schema_update():
|
|
389
369
|
|
|
390
370
|
try:
|
|
@@ -464,7 +444,7 @@ def reindex(t=None):
|
|
|
464
444
|
|
|
465
445
|
def reindex_all():
|
|
466
446
|
|
|
467
|
-
db_query("DROP TABLE taskindex",None,None,0)
|
|
447
|
+
db_query("DROP TABLE IF EXISTS taskindex",None,None,0)
|
|
468
448
|
db_query("CREATE VIRTUAL TABLE taskindex USING fts5(id, extended_label, priority, status)")
|
|
469
449
|
db_query("INSERT INTO taskindex(id, extended_label, priority, status) SELECT id, extended_label, priority, status FROM tasks WHERE tasks.status IS NOT '-1' ")
|
|
470
450
|
|
|
@@ -903,6 +883,19 @@ def get_total_time(id, category = 'tasks', start_time = None, end_time = None, g
|
|
|
903
883
|
return result
|
|
904
884
|
|
|
905
885
|
|
|
886
|
+
def get_percent_time_focused():
|
|
887
|
+
|
|
888
|
+
rand_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE sessions.task_id = 'Randomness' AND "+ sessions_timeframe_sql())[0]['seconds']
|
|
889
|
+
|
|
890
|
+
focus_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE sessions.task_id != 'Randomness' AND "+ sessions_timeframe_sql())[0]['seconds']
|
|
891
|
+
|
|
892
|
+
percent = round(divide(focus_seconds,(focus_seconds + rand_seconds)) * 100, 2)
|
|
893
|
+
|
|
894
|
+
dbg('get_percent_time_focused', str(percent)+"%", 'focus_seconds', focus_seconds, 'rand_seconds', rand_seconds)
|
|
895
|
+
|
|
896
|
+
return percent
|
|
897
|
+
|
|
898
|
+
|
|
906
899
|
def get_recent_tasks(count = 15):
|
|
907
900
|
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,))
|
|
908
901
|
o = {}
|
|
@@ -964,6 +957,25 @@ def force_number(i):
|
|
|
964
957
|
return o
|
|
965
958
|
|
|
966
959
|
|
|
960
|
+
def divide(a, b):
|
|
961
|
+
a = force_number(a)
|
|
962
|
+
b = force_number(b)
|
|
963
|
+
|
|
964
|
+
if a == 0 or b == 0:
|
|
965
|
+
return 0
|
|
966
|
+
else:
|
|
967
|
+
return (a / b)
|
|
968
|
+
|
|
969
|
+
def clamp(value, min_limit, max_limit):
|
|
970
|
+
return max(min_limit, min(value, max_limit))
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def invert_number_scale(i, max=5):
|
|
974
|
+
''' Flips a positive number within a given range '''
|
|
975
|
+
o = abs(i - max )
|
|
976
|
+
return o
|
|
977
|
+
|
|
978
|
+
|
|
967
979
|
def num_is_multiple_of(i,devisor = 2):
|
|
968
980
|
try:
|
|
969
981
|
i = int(i)
|
|
@@ -1157,7 +1169,11 @@ def choose_from_lists(callback, selected_list_id = None, session = None, accepts
|
|
|
1157
1169
|
# new buttongroup for new todolist
|
|
1158
1170
|
todolist = l['todolist']
|
|
1159
1171
|
l['header'] = Gtk.Label()
|
|
1160
|
-
|
|
1172
|
+
try:
|
|
1173
|
+
l['header'].set_markup("<b>"+conf.user['todolists'][todolist]["label"]+"</b>")
|
|
1174
|
+
except Exception as e:
|
|
1175
|
+
dbg('Exception creating button group header for list',l, e=e, l=0)
|
|
1176
|
+
|
|
1161
1177
|
box.add(l['header'])
|
|
1162
1178
|
|
|
1163
1179
|
if l['id'] == selected_list_id:
|
|
@@ -1216,7 +1232,7 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1216
1232
|
# separator after priority tasks
|
|
1217
1233
|
target_menu.prepend(Gtk.SeparatorMenuItem.new())
|
|
1218
1234
|
|
|
1219
|
-
# Create
|
|
1235
|
+
# Create menu for each list
|
|
1220
1236
|
for list_id, l in lists.items():
|
|
1221
1237
|
if l['status'] != 1:
|
|
1222
1238
|
continue
|
|
@@ -1228,19 +1244,25 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1228
1244
|
list_menu_items[list_id] = Gtk.MenuItem.new_with_label(lists[list_id]['label']) # the "item" that gets added
|
|
1229
1245
|
list_menu_items[list_id].set_submenu(list_menus[list_id])
|
|
1230
1246
|
|
|
1231
|
-
# Add
|
|
1247
|
+
# Add each list to parents (sub_menus)
|
|
1232
1248
|
for list_id, l in lists.items():
|
|
1233
1249
|
if l['status'] == 1:
|
|
1234
1250
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1251
|
+
try:
|
|
1252
|
+
|
|
1253
|
+
if not toplevel_todos and l['id'] == l['todolist']:
|
|
1254
|
+
continue
|
|
1255
|
+
|
|
1256
|
+
if 'priority' in l and l['priority']:
|
|
1257
|
+
target_menu.prepend(list_menu_items[list_id])
|
|
1258
|
+
elif (toplevel_todos and l['id'] == l['todolist']) or (not toplevel_todos and l['parent_id'] == l['todolist']):
|
|
1259
|
+
target_menu.append(list_menu_items[list_id])
|
|
1260
|
+
else:
|
|
1261
|
+
list_menus[l['parent_id']].append(list_menu_items[list_id])
|
|
1237
1262
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
target_menu.append(list_menu_items[list_id])
|
|
1242
|
-
else:
|
|
1243
|
-
list_menus[l['parent_id']].append(list_menu_items[list_id])
|
|
1263
|
+
except Exception as e:
|
|
1264
|
+
dbg('Error adding list to menu',l,e=e,l=0)
|
|
1265
|
+
|
|
1244
1266
|
|
|
1245
1267
|
|
|
1246
1268
|
for id, t in tasks.items():
|
|
@@ -1288,27 +1310,29 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1288
1310
|
|
|
1289
1311
|
target_menu.show_all()
|
|
1290
1312
|
|
|
1313
|
+
|
|
1291
1314
|
def datetime_minus_calendar_unit(unit = 'days', num = 1, ref_date = None):
|
|
1292
1315
|
''' returns a datetime for the start of the unit num units ago (from the optional ref_date)
|
|
1293
1316
|
unit can be days, weeks, months, or years (plural or singular)
|
|
1294
|
-
|
|
1295
1317
|
'''
|
|
1296
|
-
|
|
1318
|
+
|
|
1297
1319
|
num = num - 1
|
|
1320
|
+
|
|
1298
1321
|
if not ref_date:
|
|
1299
1322
|
ref_date = start_of_day()
|
|
1323
|
+
|
|
1300
1324
|
if unit in ['day','days']:
|
|
1301
1325
|
o = ref_date - timedelta(days=num)
|
|
1302
|
-
|
|
1303
|
-
if unit in ['week','weeks']:
|
|
1326
|
+
elif unit in ['week','weeks']:
|
|
1304
1327
|
o = ref_date - timedelta(days=ref_date.weekday())
|
|
1305
1328
|
if num > 0: o = o + relativedelta(dt1= o, weeks= - num)
|
|
1306
|
-
|
|
1329
|
+
elif unit in ['month','months']:
|
|
1307
1330
|
o = ref_date.replace(day=1)
|
|
1308
1331
|
if num > 0: o = o + relativedelta(dt1= o, months= - num)
|
|
1309
|
-
|
|
1332
|
+
elif unit in ['year','years']:
|
|
1310
1333
|
o = ref_date.replace(month=1,day=1)
|
|
1311
1334
|
if num > 0: o = o + relativedelta(dt1= o, years= - num)
|
|
1335
|
+
|
|
1312
1336
|
return o
|
|
1313
1337
|
|
|
1314
1338
|
def get_time_target_data(i, item_type = 'tasks'):
|
|
@@ -1359,11 +1383,11 @@ def hours_search_timeframes(frame_name = None):
|
|
|
1359
1383
|
''' returns a tuple with start and end time for given frame (if one is provided) or a dict of frame_options.
|
|
1360
1384
|
'''
|
|
1361
1385
|
|
|
1362
|
-
default = "
|
|
1386
|
+
default = "all time"
|
|
1363
1387
|
|
|
1364
1388
|
frames = {
|
|
1365
1389
|
'today':(datetime_minus_calendar_unit('day',1),now()),
|
|
1366
|
-
'yesterday':(datetime_minus_calendar_unit('day',1)),
|
|
1390
|
+
'yesterday':(datetime_minus_calendar_unit('day',2),datetime_minus_calendar_unit('day',1)),
|
|
1367
1391
|
'this week':(datetime_minus_calendar_unit('week',1),now()),
|
|
1368
1392
|
'last week':(datetime_minus_calendar_unit('week',2),datetime_minus_calendar_unit('week',1)),
|
|
1369
1393
|
'7 days':(datetime_minus_calendar_unit('days',7),now()),
|
|
@@ -1392,4 +1416,6 @@ def sessions_timeframe_sql():
|
|
|
1392
1416
|
timeframe = hours_search_timeframes(conf.user['hours_search_timeframe'])
|
|
1393
1417
|
|
|
1394
1418
|
timeframe_sql = " sessions.start_time > '"+timeframe[0].strftime("%Y-%m-%d %H:%M:%S")+"' AND sessions.start_time < '"+timeframe[1].strftime("%Y-%m-%d %H:%M:%S")+"' "
|
|
1419
|
+
|
|
1420
|
+
# dbg('sessions_timeframe_sql',timeframe_sql,s='search')
|
|
1395
1421
|
return timeframe_sql
|
|
@@ -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
|