nowfocus 0.2.8__tar.gz → 0.2.12__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.
- {nowfocus-0.2.8/src/nowfocus.egg-info → nowfocus-0.2.12}/PKG-INFO +9 -6
- {nowfocus-0.2.8 → nowfocus-0.2.12}/README.md +7 -5
- {nowfocus-0.2.8 → nowfocus-0.2.12}/pyproject.toml +3 -5
- nowfocus-0.2.12/src/nowfocus/__init__.py +1 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/__main__.py +42 -24
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/conf.py +12 -5
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/todotxt.py +2 -19
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/txt.py +31 -20
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/vikunja.py +6 -4
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/new_task_dialog.py +4 -1
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/settings.py +55 -25
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/task_window.py +4 -4
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/utils.py +143 -21
- {nowfocus-0.2.8 → nowfocus-0.2.12/src/nowfocus.egg-info}/PKG-INFO +9 -6
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/requires.txt +1 -0
- nowfocus-0.2.8/src/nowfocus/__init__.py +0 -68
- {nowfocus-0.2.8 → nowfocus-0.2.12}/LICENSE +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/setup.cfg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/activitywatch.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/caldav.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/csv.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/psc_timetracker.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/taskwarrior.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/timewarrior.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/todo_template.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/trello.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/desktop-extras/nowfocus.desktop +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/desktop-extras/nowfocus.png +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/desktop-extras/nowfocus.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/example-todo.txt +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/cancel.png +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/cancel.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/edit.png +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/edit.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-0.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-1.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-2.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-3.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-4.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-5.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-6.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-7.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-8.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-9.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-red.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/mark-done.png +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/mark-done.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/pause.png +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/pause.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/settings.png +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/settings.svg +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/session_edit_dialog.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/session_options.py +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sessions.csv +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sound/bell-xylophone-g.mp3 +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sound/dinner-bell.mp3 +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sound/xylophone-chord.mp3 +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/styles.css +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/SOURCES.txt +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/dependency_links.txt +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/entry_points.txt +0 -0
- {nowfocus-0.2.8 → nowfocus-0.2.12}/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.2.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Summary: nowfocus: the open source task-tracking self-control panel.
|
|
5
5
|
Author: GitFr33
|
|
6
6
|
Project-URL: Homepage, https://gitlab.com/GitFr33/nowfocus
|
|
@@ -42,6 +42,7 @@ Requires-Dist: tzlocal
|
|
|
42
42
|
Requires-Dist: urllib3
|
|
43
43
|
Requires-Dist: vobject
|
|
44
44
|
Requires-Dist: x-wr-timezone
|
|
45
|
+
Requires-Dist: watchdog
|
|
45
46
|
Dynamic: license-file
|
|
46
47
|
|
|
47
48
|
<div align="center"><img src="https://gitlab.com/GitFr33/nowfocus/-/raw/main/nowfocus.svg" width="60" align="center">
|
|
@@ -52,15 +53,15 @@ Dynamic: license-file
|
|
|
52
53
|
|
|
53
54
|
</div>
|
|
54
55
|
|
|
55
|
-
nowfocus is a clean, keyboard-driven project time tracker
|
|
56
|
+
nowfocus is a clean, keyboard-driven project time tracker built with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
|
|
56
57
|
|
|
57
58
|
## Features
|
|
58
59
|
- Unlimited flexible combinations of to-do lists and time tracking systems
|
|
59
60
|
- Infinitely nestable lists
|
|
60
61
|
- Inactivity detection that automatically pauses time tracking
|
|
61
62
|
- Pomodoro timer
|
|
62
|
-
- Task prioritization
|
|
63
|
-
- Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
|
|
63
|
+
- Task prioritization
|
|
64
|
+
- Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
|
|
64
65
|
- Randomness interrupt bell (optional) to keep you on track with tracking your time
|
|
65
66
|
- Keyboard-driven interface
|
|
66
67
|
- Offline to-do list cache
|
|
@@ -76,7 +77,7 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
|
|
|
76
77
|
|
|
77
78
|
### Currently Supported To-do List Backends
|
|
78
79
|
|
|
79
|
-
- Simple text
|
|
80
|
+
- Simple text file with indentation based sub-lists
|
|
80
81
|
- Any to-do list that supports [CalDav todos](https://en.wikipedia.org/wiki/CalDAV)
|
|
81
82
|
- [todotxt format](http://todotxt.org/)
|
|
82
83
|
- [TaskWarrior](https://taskwarrior.org/)
|
|
@@ -99,7 +100,9 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
|
|
|
99
100
|
# Install dependencies
|
|
100
101
|
sudo apt install pipx gir1.2-appindicator3-0.1 meson libdbus-glib-1-dev patchelf libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev
|
|
101
102
|
|
|
102
|
-
#
|
|
103
|
+
# note gir1.2-ayatanaappindicator3-0.1 can be substituted for gir1.2-appindicator3-0.1
|
|
104
|
+
|
|
105
|
+
# Set up pipx
|
|
103
106
|
pipx ensurepath
|
|
104
107
|
|
|
105
108
|
# At this point you may need to restart your terminal window
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
nowfocus is a clean, keyboard-driven project time tracker
|
|
9
|
+
nowfocus is a clean, keyboard-driven project time tracker built with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
- Unlimited flexible combinations of to-do lists and time tracking systems
|
|
13
13
|
- Infinitely nestable lists
|
|
14
14
|
- Inactivity detection that automatically pauses time tracking
|
|
15
15
|
- Pomodoro timer
|
|
16
|
-
- Task prioritization
|
|
17
|
-
- Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
|
|
16
|
+
- Task prioritization
|
|
17
|
+
- Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
|
|
18
18
|
- Randomness interrupt bell (optional) to keep you on track with tracking your time
|
|
19
19
|
- Keyboard-driven interface
|
|
20
20
|
- Offline to-do list cache
|
|
@@ -30,7 +30,7 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
|
|
|
30
30
|
|
|
31
31
|
### Currently Supported To-do List Backends
|
|
32
32
|
|
|
33
|
-
- Simple text
|
|
33
|
+
- Simple text file with indentation based sub-lists
|
|
34
34
|
- Any to-do list that supports [CalDav todos](https://en.wikipedia.org/wiki/CalDAV)
|
|
35
35
|
- [todotxt format](http://todotxt.org/)
|
|
36
36
|
- [TaskWarrior](https://taskwarrior.org/)
|
|
@@ -53,7 +53,9 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
|
|
|
53
53
|
# Install dependencies
|
|
54
54
|
sudo apt install pipx gir1.2-appindicator3-0.1 meson libdbus-glib-1-dev patchelf libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev
|
|
55
55
|
|
|
56
|
-
#
|
|
56
|
+
# note gir1.2-ayatanaappindicator3-0.1 can be substituted for gir1.2-appindicator3-0.1
|
|
57
|
+
|
|
58
|
+
# Set up pipx
|
|
57
59
|
pipx ensurepath
|
|
58
60
|
|
|
59
61
|
# At this point you may need to restart your terminal window
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nowfocus"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.12"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="GitFr33" },
|
|
6
6
|
]
|
|
@@ -15,12 +15,9 @@ classifiers = [
|
|
|
15
15
|
"Intended Audience :: End Users/Desktop",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
license-files = ["LICENSE"]
|
|
19
19
|
|
|
20
20
|
dependencies = [
|
|
21
|
-
# "pycairo",
|
|
22
|
-
# "PyGObject",
|
|
23
|
-
|
|
24
21
|
"psutil",
|
|
25
22
|
"pygobject == 3.50.0",
|
|
26
23
|
"pycairo == 1.27",
|
|
@@ -52,6 +49,7 @@ dependencies = [
|
|
|
52
49
|
"urllib3",
|
|
53
50
|
"vobject",
|
|
54
51
|
"x-wr-timezone",
|
|
52
|
+
"watchdog",
|
|
55
53
|
]
|
|
56
54
|
|
|
57
55
|
[project.urls]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -50,7 +50,6 @@ from session_options import SessionOptionsDialog
|
|
|
50
50
|
dbg(conf.user,l=3,s='user_settings')
|
|
51
51
|
|
|
52
52
|
setproctitle.setproctitle(conf.app_name)
|
|
53
|
-
pipe_file = os.path.dirname(os.path.realpath(__file__))+"/"+conf.app_id+"_pipe"
|
|
54
53
|
|
|
55
54
|
print(conf.app_name +" running from " + os.path.dirname(os.path.realpath(__file__)))
|
|
56
55
|
|
|
@@ -106,6 +105,8 @@ class Application(Gtk.Application):
|
|
|
106
105
|
except Exception as e:
|
|
107
106
|
dbg("Error resuming session",e,l=1)
|
|
108
107
|
|
|
108
|
+
utils.start_todo_file_watchers()
|
|
109
|
+
|
|
109
110
|
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
110
111
|
self.pipethread.daemon = True
|
|
111
112
|
self.pipethread.start()
|
|
@@ -163,11 +164,11 @@ class Application(Gtk.Application):
|
|
|
163
164
|
print('Caching active session', self.session['label'])
|
|
164
165
|
db_set_session_cache(self.session)
|
|
165
166
|
try:
|
|
166
|
-
# print("before os.remove(
|
|
167
|
-
os.remove(
|
|
167
|
+
# print("before os.remove(conf.pipe)")
|
|
168
|
+
os.remove(conf.pipe)
|
|
168
169
|
print("Pipe removed")
|
|
169
170
|
except Exception as e:
|
|
170
|
-
print("Error removing
|
|
171
|
+
print("Error removing conf.pipe in quit",e)
|
|
171
172
|
|
|
172
173
|
notify.uninit()
|
|
173
174
|
Gtk.main_quit()
|
|
@@ -217,6 +218,8 @@ class Application(Gtk.Application):
|
|
|
217
218
|
if (int(time_difference(conf.todo_sync_time)) / 60) > conf.user['todolist_refresh_interval'] * 60 :
|
|
218
219
|
self.async_refresh()
|
|
219
220
|
|
|
221
|
+
# TODO: use individual todo_refresh_times
|
|
222
|
+
|
|
220
223
|
minutes = (int(self.session['duration']) / 60)
|
|
221
224
|
|
|
222
225
|
if(self.is_running == False):
|
|
@@ -320,6 +323,13 @@ class Application(Gtk.Application):
|
|
|
320
323
|
# https://lazka.github.io/pgi-docs/#AyatanaAppIndicator3-0.1/classes/Indicator.html#AyatanaAppIndicator3.Indicator.set_label
|
|
321
324
|
indicator.set_label(label, "Wide")
|
|
322
325
|
|
|
326
|
+
for todo in conf.todo_sync_required:
|
|
327
|
+
# print('tick noticed a todo needing refreshment, time since refresh: ',time_difference(conf.todo_sync_times[todo]))
|
|
328
|
+
|
|
329
|
+
if time_difference(conf.todo_sync_times[todo]) > 4:
|
|
330
|
+
# print('tick noticed a todo needing refreshment')
|
|
331
|
+
self.async_refresh(None,conf.user['todolists'][todo])
|
|
332
|
+
conf.todo_sync_required = {}
|
|
323
333
|
return True
|
|
324
334
|
|
|
325
335
|
|
|
@@ -431,13 +441,23 @@ class Application(Gtk.Application):
|
|
|
431
441
|
|
|
432
442
|
def mark_done(self, w=None, task = None):
|
|
433
443
|
''' second (task) argument is required and must be a task object '''
|
|
444
|
+
|
|
434
445
|
print("Mark Task done")
|
|
435
446
|
print(task)
|
|
436
447
|
|
|
437
448
|
todolist_conf = conf.user['todolists'][task['todolist']]
|
|
438
449
|
|
|
439
450
|
try:
|
|
451
|
+
|
|
440
452
|
done_thread = threading.Thread(target=conf.todo_connectors[todolist_conf['type']].mark_task_done, args=(task,) )
|
|
453
|
+
conf.todo_sync_times[todolist_conf['id']] = now() # this is to avoid causing a refresh, perhaps not the best though
|
|
454
|
+
|
|
455
|
+
# Other Options:
|
|
456
|
+
# make a custom class extending Thread with callback method that runs del conf.file_watch_ignores[todolist_conf['id']]
|
|
457
|
+
# Complicated
|
|
458
|
+
# deal with file_watch_ignores in the connector
|
|
459
|
+
# poor seperation
|
|
460
|
+
#
|
|
441
461
|
done_thread.start()
|
|
442
462
|
|
|
443
463
|
db_query("UPDATE tasks set status = '0' WHERE id = ? ",(task['id'],) )
|
|
@@ -454,7 +474,7 @@ class Application(Gtk.Application):
|
|
|
454
474
|
|
|
455
475
|
|
|
456
476
|
def stop_task(self, w = '', action = 'save', custom_end_time=None):
|
|
457
|
-
''' supported actions are save,cancel,mark_done '''
|
|
477
|
+
''' supported actions are save, cancel, mark_done '''
|
|
458
478
|
|
|
459
479
|
list_menus = self.list_menus
|
|
460
480
|
menu_tasks = self.menu_tasks
|
|
@@ -543,7 +563,6 @@ class Application(Gtk.Application):
|
|
|
543
563
|
i = Gtk.ImageMenuItem.new_with_label("Edit Session")
|
|
544
564
|
i.set_image(Gtk.Image.new_from_file(os.path.abspath('icon/edit.svg')))
|
|
545
565
|
|
|
546
|
-
# i.set_image(Gtk.Image.new_from_file('/usr/share/icons/Yaru/scalable/status/software-installed-symbolic.svg'))
|
|
547
566
|
i.set_always_show_image(True)
|
|
548
567
|
i.connect("activate", self.open_session_options_dialog,'from_menu')
|
|
549
568
|
self.menu.insert(i,0)
|
|
@@ -551,33 +570,34 @@ class Application(Gtk.Application):
|
|
|
551
570
|
i = Gtk.ImageMenuItem.new_with_label("Pause" )
|
|
552
571
|
i.set_image(Gtk.Image.new_from_file(os.path.abspath('icon/pause.svg')))
|
|
553
572
|
|
|
554
|
-
# i.set_image(Gtk.Image.new_from_file('/usr/share/icons/Yaru/scalable/multimedia/pause-symbolic.svg'))
|
|
555
573
|
i.set_always_show_image(True)
|
|
556
574
|
i.connect("activate", self.stop_task)
|
|
557
575
|
self.menu.insert(i,0)
|
|
558
576
|
|
|
559
577
|
|
|
560
|
-
def async_refresh(self, w=None):
|
|
578
|
+
def async_refresh(self, w=None, single_todo = None):
|
|
561
579
|
|
|
562
580
|
self.indicator.set_label("Refreshing Todolists", "Wide")
|
|
563
581
|
menu_item = Gtk.MenuItem.new_with_label("Refreshing Todolists")
|
|
564
582
|
self.menu.append(menu_item)
|
|
565
583
|
self.menu.show_all()
|
|
566
584
|
|
|
567
|
-
connectors_thread = threading.Thread(target=self.async_refresh_inner)
|
|
585
|
+
connectors_thread = threading.Thread(target=self.async_refresh_inner,args=(single_todo,))
|
|
568
586
|
connectors_thread.start()
|
|
569
587
|
|
|
570
588
|
|
|
571
|
-
def async_refresh_inner(self):
|
|
589
|
+
def async_refresh_inner(self, single_todo = None):
|
|
572
590
|
# dbg("async refresh started",s="todoloading",l=3)
|
|
573
|
-
|
|
591
|
+
if single_todo:
|
|
592
|
+
utils.refresh_todolist(single_todo)
|
|
593
|
+
utils.reindex()
|
|
594
|
+
|
|
595
|
+
else:
|
|
596
|
+
utils.get_todolists()
|
|
597
|
+
|
|
574
598
|
dbg("async refresh complete",s="todoloading",l=3)
|
|
575
599
|
GLib.idle_add(self.update_menu)
|
|
576
600
|
|
|
577
|
-
try:
|
|
578
|
-
GLib.idle_add(self.taskwindow.refresh_search_cache)
|
|
579
|
-
except Exception:
|
|
580
|
-
pass
|
|
581
601
|
|
|
582
602
|
|
|
583
603
|
def update_menu(self, w = ''):
|
|
@@ -727,10 +747,10 @@ class Application(Gtk.Application):
|
|
|
727
747
|
dbg("no handler for received signal",s='signals',l=3)
|
|
728
748
|
|
|
729
749
|
def check_pipe(self):
|
|
730
|
-
# print("Listening to pipe at ",
|
|
750
|
+
# print("Listening to pipe at ",conf.pipe)
|
|
731
751
|
|
|
732
752
|
try:
|
|
733
|
-
with open(
|
|
753
|
+
with open(conf.pipe, "r") as pipe:
|
|
734
754
|
data = pipe.read().strip()
|
|
735
755
|
|
|
736
756
|
print("check_pipe ")
|
|
@@ -739,10 +759,8 @@ class Application(Gtk.Application):
|
|
|
739
759
|
pipe.close()
|
|
740
760
|
|
|
741
761
|
# TODO: add registry of special commands
|
|
762
|
+
# How to handle function arguments? (for example refresh a todolist with it's id)
|
|
742
763
|
# if data in ['quit','open_task_window']:
|
|
743
|
-
# TODO: how to all dynamic function name here?
|
|
744
|
-
# GLib.idle_add(self.open_task_window)
|
|
745
|
-
|
|
746
764
|
|
|
747
765
|
if data == 'quit':
|
|
748
766
|
GLib.idle_add(self.quit)
|
|
@@ -795,13 +813,13 @@ def startup():
|
|
|
795
813
|
if args.force:
|
|
796
814
|
print("Lanched with --force flag, forcibly deleting old pipe")
|
|
797
815
|
try:
|
|
798
|
-
os.remove(
|
|
816
|
+
os.remove(conf.pipe)
|
|
799
817
|
except Exception as e:
|
|
800
818
|
print(e)
|
|
801
819
|
|
|
802
820
|
|
|
803
821
|
try:
|
|
804
|
-
os.mkfifo(
|
|
822
|
+
os.mkfifo(conf.pipe)
|
|
805
823
|
dbg("Named pipe created successfully!", s="cli")
|
|
806
824
|
|
|
807
825
|
signal.signal(signal.SIGUSR1, Application.signal_handler)
|
|
@@ -809,7 +827,7 @@ def startup():
|
|
|
809
827
|
|
|
810
828
|
if args.task:
|
|
811
829
|
print("Writing args.task to pipe", args.task)
|
|
812
|
-
with open(
|
|
830
|
+
with open(conf.pipe, "w") as pipeout:
|
|
813
831
|
pipeout.write(args.task)
|
|
814
832
|
pipeout.close()
|
|
815
833
|
|
|
@@ -827,7 +845,7 @@ def startup():
|
|
|
827
845
|
|
|
828
846
|
dbg("Writing arg to pipe ",pipe_line, s="cli")
|
|
829
847
|
|
|
830
|
-
with open(
|
|
848
|
+
with open(conf.pipe, "w") as pipeout:
|
|
831
849
|
pipeout.write(pipe_line)
|
|
832
850
|
pipeout.close()
|
|
833
851
|
|
|
@@ -22,20 +22,24 @@ Path(user_settings_dir).mkdir(parents=True, exist_ok=True)
|
|
|
22
22
|
|
|
23
23
|
debug_level = 1 # dev value
|
|
24
24
|
debug_systems = []
|
|
25
|
+
pipe = "/tmp/"+app_id+"-pipe" # Will that work?
|
|
25
26
|
|
|
26
27
|
# key and type must be the same, (Seems redundant but it's is quite helpful)
|
|
27
28
|
connectors = {
|
|
28
29
|
"todolists":{
|
|
29
|
-
'txt':{'id':'txt','type':'txt','label':'','file':'',"status":True,'timetracker':''},
|
|
30
|
+
'txt':{'id':'txt','type':'txt','label':'','file':'', "watch_file":True, "status":True,'timetracker':''},
|
|
30
31
|
|
|
31
32
|
'trello':{'id':'trello','type':'trello','label':'Trello','APIKey':'aecf1d7791b4f1a4bb7d6ca5827ba0d3', 'token':'',"status":True,'timetracker':''},
|
|
32
33
|
|
|
33
34
|
'vikunja':{'id':'vikunja','type':'vikunja','label':'Vikunja','url':'http://localhost:3456/', 'token':'',"username":"","status":True,'timetracker':''},
|
|
34
35
|
|
|
35
36
|
'caldav':{'id':'caldav','type':'caldav','label':'CalDav Todo','url':'http://localhost:3456/','password': "",'username': '',"status":True,'timetracker':''},
|
|
37
|
+
|
|
36
38
|
'psc_timetracker':{'id':'psc_timetracker','type':'psc_timetracker','label':'Timetracker','url':'https://photosynth.ca/timetracker/','key':'',"status":True,'timetracker':'Timetracker'},
|
|
39
|
+
|
|
37
40
|
'taskwarrior':{'id':'taskwarrior','type':'taskwarrior','label':'TaskWarrior',"status":True,'timetracker':''},
|
|
38
|
-
|
|
41
|
+
|
|
42
|
+
'todotxt':{'id':'todotxt','type':'todotxt','label':'','file':"", "watch_file":True, "status":True,'timetracker':''},
|
|
39
43
|
},
|
|
40
44
|
"timetrackers":{
|
|
41
45
|
'csv':{'id':'csv','type':'csv','label':'CSV file','file':'sessions.csv',"status":True},
|
|
@@ -46,18 +50,21 @@ connectors = {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
todo_sync_time = datetime.now()
|
|
53
|
+
todo_sync_times = {}
|
|
49
54
|
|
|
50
55
|
todo_sync_required = {}
|
|
56
|
+
file_watchers = {}
|
|
57
|
+
file_watch_ignores = {}
|
|
51
58
|
|
|
52
59
|
prototype_settings = {
|
|
53
|
-
"pomodoro_interval":
|
|
60
|
+
"pomodoro_interval": 40,
|
|
54
61
|
"open_task_window_fullscreen": True,
|
|
55
62
|
"randomness_interrupt_interval":5,
|
|
56
63
|
"default_text": "What am I doing?",
|
|
57
|
-
"todolist_refresh_interval":
|
|
64
|
+
"todolist_refresh_interval":1,
|
|
58
65
|
"version":0.2,
|
|
59
66
|
"display_todolist_as_top_level_list":'auto',
|
|
60
|
-
'max_top_level_menu_items':
|
|
67
|
+
'max_top_level_menu_items':10,
|
|
61
68
|
# 'tick_interval':1,
|
|
62
69
|
'hours_search_timeframe':'this year',
|
|
63
70
|
'invoice_hourly_rate':0,
|
|
@@ -20,24 +20,6 @@ import utils
|
|
|
20
20
|
# }
|
|
21
21
|
# }
|
|
22
22
|
|
|
23
|
-
# def watch(user_conf):
|
|
24
|
-
# import pyinotify
|
|
25
|
-
|
|
26
|
-
# """Watch for modifications of the todo file with pyinotify."""
|
|
27
|
-
# wm = pyinotify.WatchManager()
|
|
28
|
-
# notifier = pyinotify.ThreadedNotifier(wm, mark_changed, user_conf)
|
|
29
|
-
# notifier.start()
|
|
30
|
-
|
|
31
|
-
# # wm.add_watch(os.path.dirname(user_conf['file']), pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO) # Watch whole folder. Perhaps more reliable
|
|
32
|
-
# wm.add_watch(user_conf['file'], pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO) # just the file. Perhaps better
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# def mark_changed(event, user_conf):
|
|
36
|
-
# ''' This is in a thread '''
|
|
37
|
-
# if event.pathname == user_conf['file']:
|
|
38
|
-
# conf.todo_sync_required[user_conf['id']] = True
|
|
39
|
-
|
|
40
|
-
|
|
41
23
|
|
|
42
24
|
def task_id(user_conf, t):
|
|
43
25
|
''' The task parameter must have at least "label", "todolist" and "parent_id" '''
|
|
@@ -71,7 +53,8 @@ def add_new_task(user_conf,list,task_label):
|
|
|
71
53
|
}
|
|
72
54
|
}
|
|
73
55
|
return t
|
|
74
|
-
|
|
56
|
+
|
|
57
|
+
|
|
75
58
|
def mark_task_done(task):
|
|
76
59
|
'''Return True on success False on error'''
|
|
77
60
|
|
|
@@ -11,24 +11,32 @@ def add_new_task(user_conf,list,task_label):
|
|
|
11
11
|
# read a list of lines into data
|
|
12
12
|
data = file.readlines()
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
# New Version
|
|
15
|
+
|
|
16
|
+
line_no = get_line_no(list,data) + 1
|
|
17
|
+
new_line = list['data']['indent']+task_label+"\n"
|
|
18
|
+
|
|
19
|
+
# # Old version without recorded indent
|
|
20
|
+
|
|
21
|
+
# if list['id'] == user_conf['id']:
|
|
22
|
+
# # Top level insert
|
|
23
|
+
# new_line = task_label+"\n"
|
|
24
|
+
# line_no = len(data) + 1
|
|
25
|
+
# else:
|
|
26
|
+
# Sub list insert
|
|
27
|
+
# TODO: Add it at the end of the list rather than the start ...
|
|
28
|
+
# Yup. rjust(indent + len)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# indent = len(list['data']['original_line']) - len(list['data']['original_line'].lstrip()) + 4 #BUG: check indent of next line instead of arbitrarily using 4 spaces
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# new_line = task_label+"\n"
|
|
34
|
+
# # I'm sure there's a better way to add a bunch of spaces to to a string
|
|
35
|
+
# while indent > 0:
|
|
36
|
+
# new_line = " " + new_line
|
|
37
|
+
# indent = indent - 1
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
# line_no = get_line_no(list,data) + 1
|
|
32
40
|
|
|
33
41
|
data.insert(line_no,new_line)
|
|
34
42
|
|
|
@@ -128,7 +136,8 @@ def get_todos(user_conf):
|
|
|
128
136
|
'data':{
|
|
129
137
|
'line_no':0,
|
|
130
138
|
'original_line':'',
|
|
131
|
-
'accepts_tasks':True
|
|
139
|
+
'accepts_tasks':True,
|
|
140
|
+
'indent':'',
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
|
|
@@ -149,7 +158,7 @@ def get_todos(user_conf):
|
|
|
149
158
|
# file_uri = "todo.txt"
|
|
150
159
|
line_no = -1
|
|
151
160
|
|
|
152
|
-
with open(file_uri) as file:
|
|
161
|
+
with open(file_uri,"r") as file:
|
|
153
162
|
for line in file:
|
|
154
163
|
|
|
155
164
|
line_no = line_no + 1
|
|
@@ -171,11 +180,13 @@ def get_todos(user_conf):
|
|
|
171
180
|
#TODO: use markdown title syntax as list name in addition to indentation
|
|
172
181
|
|
|
173
182
|
indent = len(line) - len(line.lstrip())
|
|
183
|
+
indent_str = line[0:indent]
|
|
174
184
|
|
|
175
185
|
if indent > prev_indent:
|
|
176
|
-
# prev item is a sub_list header,
|
|
186
|
+
# prev item is a sub_list header, copy it to lists
|
|
177
187
|
lists[prev_id] = tasks[prev_id]
|
|
178
|
-
|
|
188
|
+
lists[prev_id]['data']['indent'] = indent_str
|
|
189
|
+
# del tasks[prev_id]
|
|
179
190
|
current_list.append(prev_id)
|
|
180
191
|
current_list_label.append(prev_label)
|
|
181
192
|
current_indent.append(indent)
|
|
@@ -294,14 +294,16 @@ def get_todos(user_conf):
|
|
|
294
294
|
return todos
|
|
295
295
|
|
|
296
296
|
|
|
297
|
-
def launch(user_conf, item = None, category =
|
|
297
|
+
def launch(user_conf, item = None, category = 'task'):
|
|
298
298
|
''' Open Vikunja '''
|
|
299
|
+
utils.dbg("vikunja.py launch","user_conf", user_conf,"item",item,'category',category,l=3, s='vikunja')
|
|
300
|
+
|
|
299
301
|
url = user_conf['url']
|
|
300
302
|
|
|
301
|
-
if item
|
|
302
|
-
if category
|
|
303
|
+
if item:
|
|
304
|
+
if category in ('list','lists'):
|
|
303
305
|
url += 'projects/'+str(item['data']['id'])
|
|
304
|
-
|
|
306
|
+
else:
|
|
305
307
|
url += 'tasks/'+str(item['data']['id'])
|
|
306
308
|
|
|
307
309
|
utils.open_external(url)
|
|
@@ -77,7 +77,7 @@ class NewTaskWDialog(Gtk.Dialog):
|
|
|
77
77
|
|
|
78
78
|
if response == 1 or response == 2:
|
|
79
79
|
|
|
80
|
-
task_label = self.task_label_entry.get_text()
|
|
80
|
+
task_label = self.task_label_entry.get_text().strip()
|
|
81
81
|
|
|
82
82
|
if not task_label:
|
|
83
83
|
error_notice("Please Enter a name before saving new task")
|
|
@@ -86,7 +86,10 @@ class NewTaskWDialog(Gtk.Dialog):
|
|
|
86
86
|
parent_list = self.selected_list
|
|
87
87
|
todolist_conf = conf.user['todolists'][parent_list['todolist']]
|
|
88
88
|
try:
|
|
89
|
+
|
|
90
|
+
conf.file_watch_ignores[todolist_conf['id']] = True
|
|
89
91
|
task = conf.todo_connectors[todolist_conf['type']].add_new_task(todolist_conf,parent_list,task_label)
|
|
92
|
+
del conf.file_watch_ignores[todolist_conf['id']]
|
|
90
93
|
|
|
91
94
|
dbg('connector add task response',task)
|
|
92
95
|
|