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/update | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) (limited to 'core/update') 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