From d3b396b17602ad71a62cbc2eaf595a1d94579c4a Mon Sep 17 00:00:00 2001 From: lonkaars Date: Tue, 12 Mar 2024 09:02:24 +0100 Subject: make state update atomic and clean up some code --- core/config | 2 ++ core/help | 3 ++- core/lib | 19 +++++++++++++++++++ core/pause | 2 +- core/reset | 8 +++++++- core/start | 3 ++- core/state | 2 +- core/update | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 8 files changed, 77 insertions(+), 14 deletions(-) diff --git a/core/config b/core/config index 34c96b8..aedd4f3 100644 --- a/core/config +++ b/core/config @@ -1,4 +1,5 @@ #!/bin/sh + # initialize default configuration values export POMODORO_STATE_PATH="${POMODORO_STATE_PATH:-$XDG_CACHE_HOME/$progname}" # ~/.cache/dppt export POMODORO_NORMAL_DURATION="${POMODORO_NORMAL_DURATION:-$(( 25 * 60 ))}" # 25 minutes @@ -6,3 +7,4 @@ export POMODORO_BREAK_SHORT_DURATION="${POMODORO_BREAK_SHORT_DURATION:-$(( 5 * 6 export POMODORO_BREAK_LONG_DURATION="${POMODORO_BREAK_LONG_DURATION:-$(( 15 * 60 ))}" # 15 minutes export POMODORO_BREAK_SHORT_INTERVAL="${POMODORO_BREAK_INTERVAL:-2}" # every other lap export POMODORO_BREAK_LONG_INTERVAL="${POMODORO_BREAK_INTERVAL:-6}" # every 3rd break + diff --git a/core/help b/core/help index 98abd1e..4bc5496 100755 --- a/core/help +++ b/core/help @@ -1,5 +1,5 @@ #!/bin/sh -[ "$1" = "info" ] && echo "show this very message" && exit 2 +[ "$1" = "info" ] && echo "show help" && exit 2 cat << EOF $progname -- daemonless posix pomodoro timer @@ -35,3 +35,4 @@ cat << EOF if no action is provided, the \`state\` action is chosen by default EOF + diff --git a/core/lib b/core/lib index 9892663..21c64f9 100644 --- a/core/lib +++ b/core/lib @@ -16,3 +16,22 @@ err() { # debug: # bc() { tee /dev/stderr | /usr/bin/bc ; } +fmt_time() { + ss="$(echo "$1 / 1" | bc)" + mm="$(echo "$ss / 60" | bc)" + ss="$(echo "$ss % 60" | bc)" + hh="$(echo "$mm / 60" | bc)" + mm="$(echo "$mm % 60" | bc)" + _time_fmt +} + +_time_fmt() { # [HH:]MM:SS + [ $hh -gt 0 ] && printf '%02d:' $hh + printf "%02d:%02d\n" $mm $ss +} + +# _time_fmt() { # [HHh]MMmSSs +# [ $hh -gt 0 ] && printf '%02dh' $hh +# printf "%02dm%02ds\n" $mm $ss +# } + diff --git a/core/pause b/core/pause index a55bd30..3037c1c 100755 --- a/core/pause +++ b/core/pause @@ -6,7 +6,7 @@ [ "$state" != 'running' ] && err "timer is not running" -time="$("$time - $now" | bc)" +time="$(echo "$time - $now" | bc)" state='paused' save_state diff --git a/core/reset b/core/reset index 5492dce..a4c245e 100755 --- a/core/reset +++ b/core/reset @@ -1,5 +1,11 @@ #!/bin/sh [ "$1" = "info" ] && echo "reset the timer and lap counter" && exit 2 -rm -rf "$POMODORO_STATE_PATH" +lap=0 +state='reset' +update_time=1 + +. "$core_path/update" + +save_state diff --git a/core/start b/core/start index ae471ad..dbbabc0 100755 --- a/core/start +++ b/core/start @@ -28,8 +28,8 @@ done if [ "$state" = 'running' ] ; then [ $allow_skip -eq 0 ] && err "timer is already running, use -s to skip lap" - time="0.0" lap=$(( $lap + 1 )) + update_time=1 . "$core_path/update" fi @@ -37,3 +37,4 @@ time="$(echo "$now + $time" | bc)" state='running' save_state + diff --git a/core/state b/core/state index fcf327d..580afc6 100755 --- a/core/state +++ b/core/state @@ -17,5 +17,5 @@ EOF } . "$core_path/update" -echo "[$state] $lap: ${remaining}s" +echo "lap $lap, $state, $(fmt_time $remaining)" diff --git a/core/update b/core/update index 04fd29c..fbe0e86 100644 --- a/core/update +++ b/core/update @@ -1,13 +1,12 @@ #!/bin/sh export now="$(date +%s.%N)" -update_time=0 - +# load current state mkdir -p "$POMODORO_STATE_PATH" load_or_init() { property="$1" default_value="$2" - property_path="$POMODORO_STATE_PATH/$property" + property_path="$POMODORO_STATE_PATH/current/$property" if [ -f "$property_path" ] ; then eval $property='"$(cat "$property_path")"' else @@ -17,7 +16,13 @@ load_or_init() { [ -z "$lap" ] && load_or_init lap 0 [ -z "$state" ] && load_or_init state 'reset' [ -z "$time" ] && load_or_init time 0.0 -[ "$time" = "0.0" ] && update_time=1 + +# save state (for diff) +diff_state() { printf '%s:' "$lap" "$state" "$time" ; } +original_state="$(diff_state)" + +# allow overriding this value manually +[ -z "$update_time" ] && update_time=0 # calculate remaining time on timer if [ -z "$remaining" ] ; then @@ -34,6 +39,7 @@ fi # update remaining time if in reset [ "$state" = 'reset' ] && update_time=1 +[ "$time" = "0.0" ] && update_time=1 if [ $update_time -eq 1 ] ; then time="$POMODORO_NORMAL_DURATION" @@ -42,12 +48,40 @@ if [ $update_time -eq 1 ] ; then remaining="$time" fi -# save state save_state() { - mkdir -p "$POMODORO_STATE_PATH" - echo "$lap" > "$POMODORO_STATE_PATH/lap" - echo "$state" > "$POMODORO_STATE_PATH/state" - echo "$time" > "$POMODORO_STATE_PATH/time" + # do not save files if state didn't change + [ "$original_state" = "$(diff_state)" ] && return + + # The state update needs to be atomic since other instances may access the + # state during an update. This is accomplished by replacing a single symlink, + # which should be atomic on most POSIX-compatible platforms, and is less + # complicated than a lockfile. $POMODORO_STATE_PATH contains three items: + # + # 1. current -- a symlink to `primary` or `secondary` + # 2. primary -- folder containing current state* + # 3. secondary -- temporary folder used write new state + # + # * `primary` will only contain stale state during an update (i.e. while + # `current` is pointing to `secondary`) + + mkdir -p "$POMODORO_STATE_PATH/primary" "$POMODORO_STATE_PATH/secondary" + + echo "$lap" > "$POMODORO_STATE_PATH/secondary/lap" + echo "$state" > "$POMODORO_STATE_PATH/secondary/state" + echo "$time" > "$POMODORO_STATE_PATH/secondary/time" + + ln -sf "secondary" "$POMODORO_STATE_PATH/secondary/current" + + # this is the atomic update that replaces the `current` symlink with the new + # state + mv "$POMODORO_STATE_PATH/secondary/current" "$POMODORO_STATE_PATH" + + # next, replace the old `primary` state with the new state from `secondary`, + # and update the symlink a second time to now point to `primary` again (this + # does not change the state) + cp -r "$POMODORO_STATE_PATH/secondary/." "$POMODORO_STATE_PATH/primary/" + ln -sf "primary" "$POMODORO_STATE_PATH/primary/current" + mv "$POMODORO_STATE_PATH/primary/current" "$POMODORO_STATE_PATH" } # export state variables -- cgit v1.2.3