20220217-welcome_todo_my_nightmare.md (7087B)
1 I have always been one to make todo lists. My process has changed a lot over the years, from when I used actual paper to using only digital lists. My current setup is a bit hacky but works for me, so I thought I would share it. 2 3 On my PC I started using a [simple script](https://git.z3bra.org/scripts/file/todo.html){target="_blank" rel="noreferrer"} (thanks to z3bra) to add tasks to a file, by default ~/.todo. There is nothing special about this todo file, it is simple plain text. I am aware of the [todo.txt method](http://todotxt.org/){target="_blank" rel="noreferrer"} but after trying it for a while I felt like it was not working for me. I have tried [kanban boards](https://en.wikipedia.org/wiki/Kanban_board){target="_blank" rel="noreferrer"} after using similar methods in work, but I didn't find it worked very well for my own personal tasks. 4 5 My personal note and wiki tool of choice is [vimwiki](http://vimwiki.github.io/){target="_blank" rel="noreferrer"} so I tried keeping my todo list in that. This worked well for a while but I couldn't find a quick and comfortable workflow with it. 6 7 Other notable attempts include [Taskwarrior](https://taskwarrior.org/){target="_blank" rel="noreferrer"} and [calcurse](https://calcurse.org/){target="_blank" rel="noreferrer"}. 8 9 At the moment I am happy with the simple `todo` script. I spend a lot of time in [tmux](https://github.com/tmux/tmux/wiki){target="_blank" rel="noreferrer"}, so I have added a couple of keybindings to my config. I have one keybind to display the todo list in a tmux popup and another to start a command prompt with a prefix so I can write out a task, close the single quotes, and hit enter. If you're interested the relevant config is 10 ``` 11 # toggle todo list popup 12 unbind t 13 bind t display-popup -w 75 -h 13 ~/bin/todo 14 15 # add task to todo list 16 unbind T 17 bind T command-prompt -I "run-shell '~/bin/todo " 18 ``` 19 20 Adjust the width and height of the popup to your preference, and change the path to your script if required. 21 22 This workflow was working quite well when I was at my PC, but what happens when I am not? On my phone I run [termux](https://termux.com/){target="_blank" rel="noreferrer"} so I can easily log in to my PC from anywhere (with a VPN) and add an entry to my todo list, but sometimes this process is a bit slow, or I may not have a network connection at all. 23 24 I have [Markor](https://gsantner.net/project/markor.html?source=github){target="_blank" rel="noreferrer"} installed for taking notes so I thought I could sync my todo file to my phone to modify in Markor. I didn't want to install a tool such as [syncthing](https://syncthing.net/){target="_blank" rel="noreferrer"} on both my PC and phone for a single file so I started using `rsync` in a script periodically run by `cron`. I set my crontab to push the todo file from my PC to my phone at the end of the workday, then pull from my phone before work each workday. 25 26 This worked well as long as I only edited the file on my PC during working hours and on my phone outside of working hours, which is _usually_ the case. I knew it would bite me in the ass at some point though so I started looking at a way to sync them properly. I came across `incron`, which is like `cron` but is triggered by filesystem events instead of at specified times. This looked like a good start, so I installed it on my PC and configured incrontab to push the todo file to my phone whenever it is modified. I immediately [hit a bug](https://github.com/danfruehauf/incron/issues/12){target="_blank" rel="noreferrer"} which caused `incron` to run once and then not run again. 27 28 Disappointed by this I decided to hack together something similar myself using `inotifywait` from the [inotify-tools](https://github.com/inotify-tools/inotify-tools/wiki){target="_blank" rel="noreferrer"} package. This tool is really easy to use, and is available in termux. I set a script on both my PC and my phone to watch the todo file and `rsync` it to the opposite device if it changes. 29 ``` 30 #!/bin/sh 31 32 LOCAL_TODO="~/.todo" 33 REMOTE_TODO="/path/to/markor/todo.txt" 34 REMOTE_HOST="pyratephone" 35 36 exec inotifywait -e close_wait -m $LOCAL_TODO | while read TODOFILE ; do 37 rsync $LOCAL_TODO $REMOTE_HOST:$REMOTE_TODO 38 done 39 ``` 40 41 I daemonised this on my PC and created a service on termux using [termux-services](https://wiki.termux.com/wiki/Termux-services){target="_blank" rel="noreferrer"}. With the package installed creating a service is straight forward; create a service directory and `run` script 42 ``` 43 mkdir -p $PREFIX/var/service/todod/log 44 ln -sf $PREFIX/share/termux-services/svlogger $PREFIX/var/service/todod/log/run 45 cat >> $PREFIX/var/service/todod/run << EOF 46 #!/data/data/com.termux/files/usr/bin/sh 47 48 LOCAL_TODO="/path/to/markor/todo.txt" 49 REMOTE_TODO=".todo" 50 REMOTE_HOST="pyratepc" 51 52 exec 2>&1 53 exec inotifywait -e close_write -m $LOCAL_TODO | while read TODOFILE 54 do 55 rsync -e "ssh -i /path/to/sshkey" $LOCAL_TODO $REMOTE_HOST:$REMOTE_TODO 56 done 57 EOF 58 chmod +x $PREFIX/var/service/todod/run 59 sv start todod 60 ``` 61 62 I immediately hit another issue, the DELETE_SELF file event. When you pass the `-d` flag to the `todo` script to delete a line it uses the command 63 ``` 64 sed -i "${1}d" $TODOFILE 65 ``` 66 67 Unfortunately this command causes the file to be replaced with a new file, which generates the DELETE_SELF event. This means `inotifywait` sees the original file it was monitoring as deleted and can't monitor the file anymore. It doesn't look at the filename therefore does not recognise that the new todo file is "the same". To overcome this I switched the use of `sed` with `ed`. The `delete` function in the `todo` script now looks like this 68 ``` 69 delete() { 70 test -n "$1" || exit 1 71 ed $TODO << EOF >/dev/null 72 ${1}d 73 w 74 q 75 EOF 76 } 77 ``` 78 79 Using `ed` means the file is opened, the line deleted, and the file closed causing a CLOSE_WAIT event. You can find my version of the `todo` script on my [git server](https://git.pyratebeard.net/dotfiles/file/bin/bin/todo.html){target="_blank" rel="noreferrer"}. 80 81 The same issue occurs with `rsync`, the file is replaced with a new file causing a DELETE_SELF event. The quickest way I thought to fix this was to restart the daemon on the opposite device after the `rsync`. My script now looks like this 82 ``` 83 #!/bin/sh 84 85 LOCAL_TODO="~/.todo" 86 REMOTE_TODO="/path/to/markor/todo.txt" 87 REMOTE_HOST="pyratephone" 88 DAEMON_RESTART="SVDIR=/data/data/com.termux/files/usr/var/service sv restart todod" 89 90 exec inotifywait -e close_wait -m $LOCAL_TODO | while read TODOFILE ; do 91 rsync $LOCAL_TODO $REMOTE_HOST:$REMOTE_TODO 92 ssh $REMOTE_HOST "${DAEMON_RESTART}" 93 done 94 ``` 95 96 The `run` script on my phone has a different $DAEMON_RESTART variable to restart the script on my PC, and specifies the IdentityFile like I did with the `rsync` command. 97 98 So now I have a sync of sorts, and the workflow on my PC works well with the tmux keybindings. I expect at some point I will need to consider what happens when I make a change to the file and there is no network connection to the other device but that is a task for another day, it's on the todo list.